Spaces:
Running
Running
Update templates/index.html
Browse files- templates/index.html +340 -165
templates/index.html
CHANGED
|
@@ -195,7 +195,7 @@
|
|
| 195 |
|
| 196 |
/* ββ Turns ββ */
|
| 197 |
.turn { display: flex; gap: 10px; margin-bottom: var(--turn-gap); align-items: flex-start; }
|
| 198 |
-
.turn.new-turn { animation: fadeUp
|
| 199 |
.turn.user { justify-content: flex-end; }
|
| 200 |
.avatar {
|
| 201 |
width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
|
|
@@ -208,7 +208,7 @@
|
|
| 208 |
max-width: min(620px, calc(100vw - 100px)); border: 1px solid var(--border);
|
| 209 |
border-radius: var(--radius-lg); padding: var(--bubble-padding);
|
| 210 |
line-height: 1.6; font-size: var(--font-size-base);
|
| 211 |
-
|
| 212 |
}
|
| 213 |
.turn.assistant .bubble { border-radius: var(--radius-xs) var(--radius-lg) var(--radius-lg) var(--radius-lg); }
|
| 214 |
.turn.user .bubble {
|
|
@@ -236,46 +236,43 @@
|
|
| 236 |
border-radius: var(--radius-xs) var(--radius-lg) var(--radius-lg) var(--radius-lg);
|
| 237 |
padding: var(--bubble-padding); background: rgba(45,212,191,.04);
|
| 238 |
line-height: 1.6; font-size: var(--font-size-base);
|
| 239 |
-
|
| 240 |
}
|
| 241 |
.best-answer-meta { margin-top: var(--space-1); display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
| 242 |
|
| 243 |
/* ββ Shared Markdown Elements ββ */
|
| 244 |
-
.
|
|
|
|
| 245 |
|
| 246 |
-
.
|
| 247 |
-
.best-answer-bubble h1, .best-answer-bubble h2, .best-answer-bubble h3,
|
| 248 |
-
.best-answer-bubble h4, .best-answer-bubble h5, .best-answer-bubble h6,
|
| 249 |
-
.other-answer-text h1, .other-answer-text h2, .other-answer-text h3 {
|
| 250 |
line-height: 1.3; margin: 8px 0 3px; white-space: normal;
|
| 251 |
}
|
| 252 |
-
.
|
| 253 |
-
.
|
| 254 |
-
.
|
| 255 |
-
.
|
| 256 |
-
.
|
| 257 |
|
| 258 |
-
.
|
| 259 |
-
.
|
| 260 |
-
.bubble li, .best-answer-bubble li, .other-answer-text li { margin: 1px 0; white-space: normal; }
|
| 261 |
|
| 262 |
.task-item { list-style: none; margin-left: -18px; }
|
| 263 |
.task-item input[type="checkbox"] { accent-color: var(--accent2); margin-right: 6px; pointer-events: none; cursor: default; }
|
| 264 |
|
| 265 |
-
.
|
| 266 |
font-family: var(--mono); font-size: .87em;
|
| 267 |
background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.1);
|
| 268 |
border-radius: var(--radius-xs); padding: 1px 5px;
|
| 269 |
}
|
| 270 |
|
| 271 |
.code-block-wrapper { position: relative; margin: 6px 0; }
|
| 272 |
-
.
|
| 273 |
background: rgba(0,0,0,.38); border: 1px solid var(--border);
|
| 274 |
border-radius: var(--radius-sm); padding: 10px 12px;
|
| 275 |
overflow-x: auto; white-space: pre; font-family: var(--mono);
|
| 276 |
font-size: .84em; line-height: 1.5; margin: 0;
|
| 277 |
}
|
| 278 |
-
.
|
| 279 |
background: none; border: none; padding: 0; font-size: inherit;
|
| 280 |
}
|
| 281 |
.code-lang-label {
|
|
@@ -296,26 +293,38 @@
|
|
| 296 |
.copy-code-btn:hover { color: var(--text); border-color: rgba(108,131,255,.4); }
|
| 297 |
.copy-code-btn.copied { color: var(--good); border-color: rgba(45,212,191,.4); opacity: 1; }
|
| 298 |
|
| 299 |
-
.
|
| 300 |
border-left: 3px solid var(--accent); background: rgba(124,166,255,.04);
|
| 301 |
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
| 302 |
margin: 6px 0; padding: 6px 12px; color: var(--muted); white-space: normal; font-style: italic;
|
| 303 |
}
|
| 304 |
-
.
|
| 305 |
-
.
|
| 306 |
-
.
|
| 307 |
-
.
|
| 308 |
-
.
|
| 309 |
-
.
|
| 310 |
-
.bubble th, .best-answer-bubble th { background: rgba(255,255,255,.05); font-weight: 600; }
|
| 311 |
sup { font-size: .75em; vertical-align: super; line-height: 0; }
|
| 312 |
sub { font-size: .75em; vertical-align: sub; line-height: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
.md-img {
|
| 314 |
-
display: block; max-width: 100%;
|
| 315 |
-
width:
|
| 316 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
}
|
| 318 |
-
.md-img:hover { opacity: .9; }
|
| 319 |
p.md-gap { min-height: 0.35em; margin: 0 !important; padding: 0; }
|
| 320 |
|
| 321 |
.quality-dots { display: inline-flex; gap: 2px; align-items: center; margin-left: 4px; }
|
|
@@ -323,34 +332,47 @@
|
|
| 323 |
.quality-dot.filled { background: var(--accent2); }
|
| 324 |
|
| 325 |
/* ββ Thinking Block ββ */
|
| 326 |
-
.thinking-dropdown { margin:
|
| 327 |
.thinking-summary {
|
| 328 |
display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
| 329 |
background: rgba(255,255,255,.04); border: 1px solid var(--border);
|
| 330 |
border-radius: var(--radius-md); cursor: pointer; user-select: none;
|
| 331 |
font-size: 12px; font-family: var(--mono); color: var(--muted);
|
| 332 |
-
transition: background
|
| 333 |
list-style: none;
|
| 334 |
}
|
|
|
|
| 335 |
.thinking-summary:hover { background: rgba(255,255,255,.06); border-color: var(--border2); }
|
| 336 |
.thinking-summary::-webkit-details-marker { display: none; }
|
| 337 |
.thinking-summary .thinking-arrow {
|
| 338 |
display: inline-block; transition: transform 200ms var(--ease-out); font-size: 10px;
|
| 339 |
}
|
| 340 |
details.thinking-dropdown[open] .thinking-arrow { transform: rotate(90deg); }
|
|
|
|
|
|
|
|
|
|
| 341 |
.thinking-summary .thinking-spinner {
|
| 342 |
width: 12px; height: 12px; border: 2px solid var(--border2);
|
| 343 |
border-top-color: var(--accent); border-radius: 50%;
|
| 344 |
animation: spin-thinking .8s linear infinite;
|
| 345 |
}
|
| 346 |
@keyframes spin-thinking { to { transform: rotate(360deg); } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
.thinking-content {
|
| 348 |
-
padding: 10px 14px; font-size: 13px; line-height: 1.6;
|
| 349 |
color: var(--muted); border: 1px solid var(--border); border-top: none;
|
| 350 |
border-radius: 0 0 var(--radius-md) var(--radius-md);
|
| 351 |
-
background: rgba(255,255,255,.02);
|
| 352 |
-
|
| 353 |
}
|
|
|
|
| 354 |
.thinking-content p { color: var(--muted); }
|
| 355 |
|
| 356 |
/* ββ Vote ββ */
|
|
@@ -464,7 +486,7 @@
|
|
| 464 |
.other-answer-card {
|
| 465 |
border: 1px solid var(--border); border-radius: var(--radius-md);
|
| 466 |
padding: 10px 12px; margin-top: 6px; background: rgba(255,255,255,.02);
|
| 467 |
-
animation: fadeUp
|
| 468 |
}
|
| 469 |
.other-answer-card.related {
|
| 470 |
background: linear-gradient(180deg, rgba(108,131,255,.05), rgba(255,255,255,.02));
|
|
@@ -474,7 +496,7 @@
|
|
| 474 |
display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
|
| 475 |
color: var(--muted); font-family: var(--mono); font-size: 10px; margin-bottom: 6px;
|
| 476 |
}
|
| 477 |
-
.other-answer-text { font-size: 13px; line-height: 1.6;
|
| 478 |
|
| 479 |
/* ββ Preview lines ββ */
|
| 480 |
.preview-block { display: flex; gap: 8px; align-items: flex-start; margin-top: 6px; }
|
|
@@ -513,7 +535,7 @@
|
|
| 513 |
.version-card {
|
| 514 |
border: 1px solid var(--border); background: rgba(255,255,255,.02);
|
| 515 |
border-radius: var(--radius-md); padding: 8px 10px; margin-top: var(--space-1);
|
| 516 |
-
animation: fadeUp
|
| 517 |
}
|
| 518 |
.version-head {
|
| 519 |
font-size: 10px; color: var(--muted); font-family: var(--mono);
|
|
@@ -551,7 +573,7 @@
|
|
| 551 |
/* ββ Typing indicator ββ */
|
| 552 |
.typing-indicator {
|
| 553 |
display: flex; gap: 10px; margin-bottom: 6px; align-items: flex-start;
|
| 554 |
-
animation: fadeUp
|
| 555 |
}
|
| 556 |
.typing-dots {
|
| 557 |
display: flex; gap: 4px; align-items: center; padding: 12px 16px;
|
|
@@ -685,10 +707,15 @@
|
|
| 685 |
}
|
| 686 |
.anim-option.active { border-color: rgba(108,131,255,.4); background: rgba(108,131,255,.08); color: var(--accent); }
|
| 687 |
.anim-preview { width: 24px; height: 8px; border-radius: 4px; background: var(--border2); overflow: hidden; position: relative; }
|
| 688 |
-
.anim-
|
| 689 |
content: ""; position: absolute; inset: 0;
|
| 690 |
background: linear-gradient(90deg, var(--accent), var(--accent2));
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
}
|
| 693 |
@keyframes anim-preview-slide { from { transform: translateX(-100%); } to { transform: translateX(0); } }
|
| 694 |
|
|
@@ -968,8 +995,19 @@
|
|
| 968 |
animMode: localStorage.getItem('hi_anim') || 'none',
|
| 969 |
density: localStorage.getItem('hi_density') || 'comfortable',
|
| 970 |
lastAction: null, originalTitle: document.title,
|
|
|
|
|
|
|
|
|
|
| 971 |
};
|
| 972 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 973 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 974 |
UTILITIES
|
| 975 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
|
@@ -1004,6 +1042,25 @@
|
|
| 1004 |
try { await fn(...a); } finally { setTimeout(() => { blocked = false; }, ms); }
|
| 1005 |
};
|
| 1006 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1007 |
|
| 1008 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 1009 |
TOAST / STATUS
|
|
@@ -1057,9 +1114,14 @@
|
|
| 1057 |
/!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)|\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
|
| 1058 |
(m, imgAlt, imgSrc, linkText, linkHref) => {
|
| 1059 |
const idx = tokens.length;
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1063 |
return `\x00${idx}\x00`;
|
| 1064 |
}
|
| 1065 |
);
|
|
@@ -1090,27 +1152,28 @@
|
|
| 1090 |
* Returns { segments: [ {type:'text'|'thinking', content:string} ] }
|
| 1091 |
*/
|
| 1092 |
function extractThinkingBlocks(md) {
|
|
|
|
| 1093 |
const segments = [];
|
| 1094 |
const openTag = '<|thinking|>';
|
| 1095 |
const closeTag = '</|thinking|>';
|
| 1096 |
let cursor = 0;
|
| 1097 |
-
while (cursor <
|
| 1098 |
-
const openIdx =
|
| 1099 |
if (openIdx === -1) {
|
| 1100 |
-
segments.push({ type: 'text', content:
|
| 1101 |
break;
|
| 1102 |
}
|
| 1103 |
if (openIdx > cursor) {
|
| 1104 |
-
segments.push({ type: 'text', content:
|
| 1105 |
}
|
| 1106 |
-
const closeIdx =
|
| 1107 |
if (closeIdx === -1) {
|
| 1108 |
// Unclosed thinking block β treat rest as thinking
|
| 1109 |
-
segments.push({ type: 'thinking', content:
|
| 1110 |
-
cursor =
|
| 1111 |
break;
|
| 1112 |
}
|
| 1113 |
-
segments.push({ type: 'thinking', content:
|
| 1114 |
cursor = closeIdx + closeTag.length;
|
| 1115 |
}
|
| 1116 |
return segments;
|
|
@@ -1218,7 +1281,7 @@
|
|
| 1218 |
out.push(`<p>${renderInlineMarkdown(raw)}</p>`);
|
| 1219 |
}
|
| 1220 |
closeLists(); closeQuote(); closeTable();
|
| 1221 |
-
if (inCode) out.push(`<div class="code-block-wrapper"><pre><code>${codeBuf.join('\n')}</code></pre></div>`);
|
| 1222 |
return out.join('');
|
| 1223 |
}
|
| 1224 |
|
|
@@ -1227,20 +1290,19 @@
|
|
| 1227 |
* Returns HTML with <details> dropdowns for each thinking block.
|
| 1228 |
* `thinkingDuration` is the number of seconds to display (null = still thinking).
|
| 1229 |
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1230 |
function renderMarkdownWithThinking(md) {
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
if (
|
| 1234 |
-
|
| 1235 |
-
|
| 1236 |
-
|
| 1237 |
-
return `<details class="thinking-dropdown">
|
| 1238 |
-
<summary class="thinking-summary"><span class="thinking-arrow" aria-hidden="true">βΆ</span> <span>thought</span></summary>
|
| 1239 |
-
<div class="thinking-content">${thinkingHtml}</div>
|
| 1240 |
-
</details>`;
|
| 1241 |
-
}
|
| 1242 |
-
const trimmed = seg.content.trim();
|
| 1243 |
-
return trimmed ? renderMarkdown(trimmed) : '';
|
| 1244 |
}).join('');
|
| 1245 |
}
|
| 1246 |
|
|
@@ -1313,106 +1375,112 @@
|
|
| 1313 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 1314 |
ANIMATE TEXT (chunked autoregressive)
|
| 1315 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 1316 |
-
|
| 1317 |
-
|
| 1318 |
-
|
| 1319 |
-
|
| 1320 |
-
|
| 1321 |
-
|
| 1322 |
-
|
| 1323 |
-
|
| 1324 |
-
|
| 1325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1326 |
finalizeThinkingBlocks(el);
|
| 1327 |
-
|
| 1328 |
-
return;
|
| 1329 |
}
|
| 1330 |
|
| 1331 |
el.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1332 |
|
| 1333 |
-
for (const seg of segments) {
|
| 1334 |
if (seg.type === 'thinking') {
|
| 1335 |
-
await animateThinkingBlock(el,
|
| 1336 |
-
|
| 1337 |
-
|
| 1338 |
-
el.appendChild(textContainer);
|
| 1339 |
-
await animateMarkdownChunked(textContainer, seg.content, delay);
|
| 1340 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1341 |
}
|
| 1342 |
|
| 1343 |
finalizeThinkingBlocks(el);
|
| 1344 |
-
|
| 1345 |
}
|
| 1346 |
|
|
|
|
|
|
|
| 1347 |
|
| 1348 |
-
/**
|
| 1349 |
-
* Animate a thinking block: show a "thinkingβ¦" dropdown that updates
|
| 1350 |
-
* in real-time, then when complete, display final duration.
|
| 1351 |
-
*/
|
| 1352 |
-
async function animateThinkingBlock(parentEl, thinkingText, delay) {
|
| 1353 |
const details = document.createElement('details');
|
| 1354 |
-
details.className = 'thinking-dropdown';
|
| 1355 |
-
details.
|
| 1356 |
-
|
| 1357 |
-
const summary = document.createElement('summary');
|
| 1358 |
-
summary.className = 'thinking-summary';
|
| 1359 |
-
summary.innerHTML = `<span class="thinking-spinner" aria-hidden="true"></span> <span class="thinking-label">thinkingβ¦</span>`;
|
| 1360 |
-
|
| 1361 |
-
const contentDiv = document.createElement('div');
|
| 1362 |
-
contentDiv.className = 'thinking-content';
|
| 1363 |
-
|
| 1364 |
-
details.appendChild(summary);
|
| 1365 |
-
details.appendChild(contentDiv);
|
| 1366 |
parentEl.appendChild(details);
|
|
|
|
|
|
|
| 1367 |
scrollBottom();
|
| 1368 |
|
|
|
|
| 1369 |
const startTime = performance.now();
|
| 1370 |
-
|
| 1371 |
-
|
|
|
|
|
|
|
| 1372 |
|
| 1373 |
const elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1374 |
|
| 1375 |
-
|
| 1376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1377 |
}
|
| 1378 |
|
| 1379 |
-
|
| 1380 |
-
|
| 1381 |
-
|
| 1382 |
-
*/
|
| 1383 |
-
async function animateMarkdownChunked(el, mdText, delay) {
|
| 1384 |
-
if (!mdText.trim()) return;
|
| 1385 |
|
| 1386 |
-
const lines =
|
| 1387 |
let buffer = '';
|
| 1388 |
-
// We'll accumulate lines and re-render periodically
|
| 1389 |
-
// Chunk size: 1-3 lines at a time for natural feel
|
| 1390 |
-
const chunkSize = delay > 40 ? 1 : delay > 15 ? 2 : 3;
|
| 1391 |
|
| 1392 |
-
for (let i = 0; i < lines.length; i += chunkSize) {
|
| 1393 |
-
|
|
|
|
| 1394 |
buffer += (buffer ? '\n' : '') + chunk;
|
| 1395 |
|
| 1396 |
-
|
| 1397 |
-
|
| 1398 |
-
el.innerHTML = html;
|
| 1399 |
-
bindCodeCopyButtons(el);
|
| 1400 |
scrollBottom();
|
| 1401 |
|
| 1402 |
-
if (
|
| 1403 |
-
await
|
|
|
|
| 1404 |
}
|
| 1405 |
}
|
|
|
|
| 1406 |
}
|
| 1407 |
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
|
| 1411 |
-
|
| 1412 |
-
|
| 1413 |
-
|
| 1414 |
-
|
| 1415 |
-
// for instant mode. For animated mode, animateThinkingBlock handles it.
|
| 1416 |
}
|
| 1417 |
|
| 1418 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -1458,6 +1526,43 @@
|
|
| 1458 |
});
|
| 1459 |
});
|
| 1460 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1461 |
|
| 1462 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 1463 |
ANSWER HELPERS
|
|
@@ -1550,7 +1655,7 @@
|
|
| 1550 |
<div id="writeEditorPane" role="tabpanel" aria-labelledby="writeTabEdit">
|
| 1551 |
<textarea class="write-textarea" id="writeTextarea" placeholder="Write your answer here⦠Markdown is supported." rows="4" aria-label="Your answer" maxlength="5000"></textarea>
|
| 1552 |
</div>
|
| 1553 |
-
<div id="writePreviewPane" role="tabpanel" aria-labelledby="writeTabPreview" class="write-preview"></div>
|
| 1554 |
<div class="char-count" id="writeCharCount"><span id="writeCharCur">0</span> / 5000</div>
|
| 1555 |
<div class="write-actions">
|
| 1556 |
<button class="write-submit" id="writeSubmit">Submit answer</button>
|
|
@@ -1564,7 +1669,7 @@
|
|
| 1564 |
const rawText = v.text||'';
|
| 1565 |
const label = isBest ? `<span class="chip good">β best answer</span>` : `<span class="chip muted">answer ${idx+1}</span>`;
|
| 1566 |
const bubbleId = isBest ? 'id="bestAnswerText"' : '';
|
| 1567 |
-
const bubbleClass = isBest ? 'best-answer-bubble' : 'bubble';
|
| 1568 |
const glowClass = isBest ? 'answer-new-glow' : '';
|
| 1569 |
return `
|
| 1570 |
<div ${bubbleId} class="${bubbleClass} ${glowClass}" tabindex="-1">${isBest ? '' : renderMarkdownWithThinking(rawText)}</div>
|
|
@@ -1595,7 +1700,7 @@
|
|
| 1595 |
<span>${esc(v.author||'Anonymous')}</span><span aria-hidden="true">Β·</span>
|
| 1596 |
<span>${relativeTime(v.created_at)}</span> ${renderQualityDots(v.text||'')}
|
| 1597 |
</div>
|
| 1598 |
-
<div class="other-answer-text">${renderMarkdownWithThinking(v.text||'')}</div>
|
| 1599 |
${renderVoteRow(a.id, v)}
|
| 1600 |
<div style="display:flex;gap:var(--space-1);flex-wrap:wrap;margin-top:var(--space-1);">
|
| 1601 |
${renderVersions(a)} ${renderPropose(a.id)}
|
|
@@ -1637,19 +1742,20 @@
|
|
| 1637 |
MAIN RENDER
|
| 1638 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 1639 |
async function renderConversation(questionText, doAnimate) {
|
|
|
|
| 1640 |
const tr=$('transcript'), wl=$('welcome');
|
| 1641 |
const frag = document.createDocumentFragment();
|
| 1642 |
|
| 1643 |
if (!S.conversation) {
|
| 1644 |
wl.style.display=''; tr.replaceChildren();
|
| 1645 |
setJumpLatest(false); document.title=S.originalTitle;
|
| 1646 |
-
updateWelcomeState(); return;
|
| 1647 |
}
|
| 1648 |
|
| 1649 |
wl.style.display='none';
|
| 1650 |
const q = questionText||S.conversation.question||'';
|
| 1651 |
document.title = q.slice(0,60)+' β '+S.originalTitle;
|
| 1652 |
-
if (S.conversation.id) history.replaceState({cid:S.conversation.id},'',
|
| 1653 |
|
| 1654 |
const isNew = !S.conversation.created_at || (Date.now()-new Date(S.conversation.created_at).getTime())<10000;
|
| 1655 |
const questionNote = isNew
|
|
@@ -1686,24 +1792,29 @@
|
|
| 1686 |
}
|
| 1687 |
|
| 1688 |
tr.replaceChildren(frag);
|
|
|
|
| 1689 |
|
| 1690 |
if (answers.length) {
|
| 1691 |
const bestV = activeVersion(answers[0]);
|
| 1692 |
if (bestV) {
|
| 1693 |
const el = $('bestAnswerText');
|
| 1694 |
-
if (doAnimate) {
|
| 1695 |
-
await animateText(el, bestV.text||'');
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
|
|
|
|
| 1699 |
}
|
| 1700 |
}
|
| 1701 |
}
|
|
|
|
| 1702 |
|
| 1703 |
const rm = $('relatedMount');
|
| 1704 |
if (rm && S.relatedAnswers.length) rm.innerHTML = renderRelated(S.relatedAnswers);
|
| 1705 |
|
|
|
|
| 1706 |
bindHandlers(); scrollBottom(); restoreDraft();
|
|
|
|
| 1707 |
}
|
| 1708 |
|
| 1709 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -1768,8 +1879,6 @@
|
|
| 1768 |
if (e.target.closest('#writeSubmit')) { await handleWriteSubmit(); return; }
|
| 1769 |
if (e.target.closest('#writeTabEdit')) { handleWriteTab('edit'); return; }
|
| 1770 |
if (e.target.closest('#writeTabPreview')) { handleWriteTab('preview'); return; }
|
| 1771 |
-
const img = e.target.closest('.md-img');
|
| 1772 |
-
if (img) { openLightbox(img.src, img.alt); return; }
|
| 1773 |
});
|
| 1774 |
|
| 1775 |
tr.addEventListener('input', e => {
|
|
@@ -1780,8 +1889,9 @@
|
|
| 1780 |
updateCharCount(ta,'writeCharCur',5000);
|
| 1781 |
saveDraft(ta.value);
|
| 1782 |
const preview=$('writePreviewPane');
|
| 1783 |
-
if (preview?.classList.contains('write-preview') && !preview.style.display?.includes('none'))
|
| 1784 |
-
preview
|
|
|
|
| 1785 |
} else {
|
| 1786 |
const panel = ta.closest('.propose-panel');
|
| 1787 |
if (panel) {
|
|
@@ -1925,8 +2035,12 @@
|
|
| 1925 |
editorPane.style.display='none'; previewPane.style.display='';
|
| 1926 |
previewPane.classList.add('active');
|
| 1927 |
const ta=$('writeTextarea');
|
| 1928 |
-
previewPane.innerHTML =
|
| 1929 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1930 |
}
|
| 1931 |
}
|
| 1932 |
|
|
@@ -2000,6 +2114,7 @@
|
|
| 2000 |
ASK / SUBMIT
|
| 2001 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 2002 |
async function askQuestion(q) {
|
|
|
|
| 2003 |
showStatusWithEscalation(); showTyping();
|
| 2004 |
S.loading=true; $('sendBtn').disabled=true; closeAutocomplete();
|
| 2005 |
S.lastAction=()=>askQuestion(q);
|
|
@@ -2009,7 +2124,7 @@
|
|
| 2009 |
S.conversation=res.conversation; S.currentQuestion=q;
|
| 2010 |
S.relatedAnswers=Array.isArray(res.related)?res.related:[];
|
| 2011 |
save(); toast(res.matched?'β Existing answer found':'β New question created','good');
|
| 2012 |
-
await renderConversation(q,
|
| 2013 |
}
|
| 2014 |
|
| 2015 |
async function submitPrompt() {
|
|
@@ -2027,8 +2142,7 @@
|
|
| 2027 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 2028 |
LOAD SAVED
|
| 2029 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 2030 |
-
|
| 2031 |
-
const id=localStorage.getItem('hi_last_cid'); if(!id) return;
|
| 2032 |
const tr=$('transcript'), wl=$('welcome');
|
| 2033 |
wl.style.display='none';
|
| 2034 |
tr.innerHTML=`<div class="skeleton-wrap">
|
|
@@ -2036,13 +2150,50 @@
|
|
| 2036 |
<div class="skeleton skeleton-line long"></div>
|
| 2037 |
<div class="skeleton skeleton-line medium"></div>
|
| 2038 |
<div class="skeleton skeleton-line short"></div></div>`;
|
| 2039 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2040 |
const res=await callAPI('get_conversation',{conversation_id:id});
|
| 2041 |
hideStatus();
|
| 2042 |
if(res.ok&&res.conversation){
|
| 2043 |
S.conversation=res.conversation; S.currentQuestion=res.conversation.question||'';
|
| 2044 |
-
S.relatedAnswers=[];
|
| 2045 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2046 |
}
|
| 2047 |
|
| 2048 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -2052,11 +2203,13 @@
|
|
| 2052 |
if(qsa('textarea').some(t=>t.value.trim())){
|
| 2053 |
if(!await confirmModal('Start a new chat?','You have unsaved content. It will be lost.')) return;
|
| 2054 |
}
|
|
|
|
| 2055 |
S.conversation=null; S.currentQuestion=''; S.relatedAnswers=[]; S.atBottom=true;
|
|
|
|
| 2056 |
localStorage.removeItem('hi_last_cid');
|
| 2057 |
$('transcript').innerHTML=''; $('welcome').style.display='';
|
| 2058 |
updateWelcomeState(); setJumpLatest(false); $('prompt').value='';
|
| 2059 |
-
autoGrow($('prompt')); history.replaceState({},'',
|
| 2060 |
document.title=S.originalTitle; $('prompt').focus();
|
| 2061 |
}
|
| 2062 |
|
|
@@ -2089,10 +2242,30 @@
|
|
| 2089 |
// Animation options
|
| 2090 |
const animOpts=qsa('.anim-option',$('animSegment'));
|
| 2091 |
function syncAnim(){
|
| 2092 |
-
animOpts.forEach(o=>{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2093 |
}
|
| 2094 |
animOpts.forEach(o=>{
|
| 2095 |
-
o.addEventListener('click',()=>{
|
| 2096 |
o.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();o.click();}});
|
| 2097 |
});
|
| 2098 |
|
|
@@ -2197,14 +2370,15 @@
|
|
| 2197 |
function initPullToRefresh(){
|
| 2198 |
const chat=$('chat'); let pullStart=0;
|
| 2199 |
chat.addEventListener('touchstart',e=>{pullStart=chat.scrollTop===0?e.touches[0].clientY:0;},{passive:true});
|
| 2200 |
-
chat.addEventListener('touchend',e=>{
|
| 2201 |
if(!pullStart) return;
|
| 2202 |
if(e.changedTouches[0].clientY-pullStart>80&&S.conversation){
|
| 2203 |
-
pullStart=0;
|
| 2204 |
-
|
| 2205 |
-
|
| 2206 |
-
|
| 2207 |
});
|
|
|
|
| 2208 |
}
|
| 2209 |
pullStart=0;
|
| 2210 |
},{passive:true});
|
|
@@ -2250,11 +2424,12 @@
|
|
| 2250 |
|
| 2251 |
const d=window.__HI_INIT__||{};
|
| 2252 |
if(d.client_id) S.clientId=d.client_id;
|
| 2253 |
-
|
|
|
|
| 2254 |
}
|
| 2255 |
|
| 2256 |
init();
|
| 2257 |
})();
|
| 2258 |
</script>
|
| 2259 |
</body>
|
| 2260 |
-
</html>
|
|
|
|
| 195 |
|
| 196 |
/* ββ Turns ββ */
|
| 197 |
.turn { display: flex; gap: 10px; margin-bottom: var(--turn-gap); align-items: flex-start; }
|
| 198 |
+
.turn.new-turn { animation: fadeUp 380ms var(--ease-out) both; }
|
| 199 |
.turn.user { justify-content: flex-end; }
|
| 200 |
.avatar {
|
| 201 |
width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
|
|
|
|
| 208 |
max-width: min(620px, calc(100vw - 100px)); border: 1px solid var(--border);
|
| 209 |
border-radius: var(--radius-lg); padding: var(--bubble-padding);
|
| 210 |
line-height: 1.6; font-size: var(--font-size-base);
|
| 211 |
+
background: rgba(255,255,255,.03);
|
| 212 |
}
|
| 213 |
.turn.assistant .bubble { border-radius: var(--radius-xs) var(--radius-lg) var(--radius-lg) var(--radius-lg); }
|
| 214 |
.turn.user .bubble {
|
|
|
|
| 236 |
border-radius: var(--radius-xs) var(--radius-lg) var(--radius-lg) var(--radius-lg);
|
| 237 |
padding: var(--bubble-padding); background: rgba(45,212,191,.04);
|
| 238 |
line-height: 1.6; font-size: var(--font-size-base);
|
| 239 |
+
outline: none;
|
| 240 |
}
|
| 241 |
.best-answer-meta { margin-top: var(--space-1); display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
| 242 |
|
| 243 |
/* ββ Shared Markdown Elements ββ */
|
| 244 |
+
.md-body { white-space: normal; word-break: break-word; overflow-wrap: anywhere; }
|
| 245 |
+
.md-body p { margin: 3px 0; white-space: normal; }
|
| 246 |
|
| 247 |
+
.md-body h1, .md-body h2, .md-body h3, .md-body h4, .md-body h5, .md-body h6 {
|
|
|
|
|
|
|
|
|
|
| 248 |
line-height: 1.3; margin: 8px 0 3px; white-space: normal;
|
| 249 |
}
|
| 250 |
+
.md-body h1 { font-size: 1.35em; font-weight: 800; }
|
| 251 |
+
.md-body h2 { font-size: 1.2em; font-weight: 700; }
|
| 252 |
+
.md-body h3 { font-size: 1.05em; font-weight: 700; color: var(--accent); }
|
| 253 |
+
.md-body h4 { font-size: .95em; font-weight: 700; }
|
| 254 |
+
.md-body h5, .md-body h6 { font-size: .88em; font-weight: 700; }
|
| 255 |
|
| 256 |
+
.md-body ul, .md-body ol { margin: 3px 0 3px 18px; padding: 0; white-space: normal; }
|
| 257 |
+
.md-body li { margin: 1px 0; white-space: normal; }
|
|
|
|
| 258 |
|
| 259 |
.task-item { list-style: none; margin-left: -18px; }
|
| 260 |
.task-item input[type="checkbox"] { accent-color: var(--accent2); margin-right: 6px; pointer-events: none; cursor: default; }
|
| 261 |
|
| 262 |
+
.md-body code {
|
| 263 |
font-family: var(--mono); font-size: .87em;
|
| 264 |
background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.1);
|
| 265 |
border-radius: var(--radius-xs); padding: 1px 5px;
|
| 266 |
}
|
| 267 |
|
| 268 |
.code-block-wrapper { position: relative; margin: 6px 0; }
|
| 269 |
+
.md-body pre {
|
| 270 |
background: rgba(0,0,0,.38); border: 1px solid var(--border);
|
| 271 |
border-radius: var(--radius-sm); padding: 10px 12px;
|
| 272 |
overflow-x: auto; white-space: pre; font-family: var(--mono);
|
| 273 |
font-size: .84em; line-height: 1.5; margin: 0;
|
| 274 |
}
|
| 275 |
+
.md-body pre code {
|
| 276 |
background: none; border: none; padding: 0; font-size: inherit;
|
| 277 |
}
|
| 278 |
.code-lang-label {
|
|
|
|
| 293 |
.copy-code-btn:hover { color: var(--text); border-color: rgba(108,131,255,.4); }
|
| 294 |
.copy-code-btn.copied { color: var(--good); border-color: rgba(45,212,191,.4); opacity: 1; }
|
| 295 |
|
| 296 |
+
.md-body blockquote {
|
| 297 |
border-left: 3px solid var(--accent); background: rgba(124,166,255,.04);
|
| 298 |
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
| 299 |
margin: 6px 0; padding: 6px 12px; color: var(--muted); white-space: normal; font-style: italic;
|
| 300 |
}
|
| 301 |
+
.md-body hr { border: none; border-top: 1px solid var(--border2); margin: 8px 0; }
|
| 302 |
+
.md-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
|
| 303 |
+
.md-body a[href^="http"]::after { content: " β"; font-size: .75em; opacity: .5; }
|
| 304 |
+
.md-body table { border-collapse: collapse; width: 100%; margin: 8px 0; font-size: 13px; white-space: normal; }
|
| 305 |
+
.md-body th, .md-body td { border: 1px solid var(--border2); padding: 6px 10px; text-align: left; }
|
| 306 |
+
.md-body th { background: rgba(255,255,255,.05); font-weight: 600; }
|
|
|
|
| 307 |
sup { font-size: .75em; vertical-align: super; line-height: 0; }
|
| 308 |
sub { font-size: .75em; vertical-align: sub; line-height: 0; }
|
| 309 |
+
.md-figure {
|
| 310 |
+
display: inline-flex; flex-direction: column; align-items: flex-start; gap: 6px;
|
| 311 |
+
margin: 8px 0; padding: 8px; max-width: min(100%, 520px);
|
| 312 |
+
border-radius: var(--radius-lg); border: 1px solid rgba(255,255,255,.08);
|
| 313 |
+
background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
|
| 314 |
+
box-shadow: var(--shadow-soft);
|
| 315 |
+
}
|
| 316 |
.md-img {
|
| 317 |
+
display: block; max-width: 100%; max-height: 480px;
|
| 318 |
+
width: clamp(220px, 38vw, 420px); min-width: 0; height: auto;
|
| 319 |
+
border-radius: var(--radius-md); border: 1px solid var(--border);
|
| 320 |
+
background: rgba(0,0,0,.18); object-fit: contain; cursor: zoom-in;
|
| 321 |
+
transition: transform 180ms var(--ease-out), opacity 150ms ease, border-color 180ms var(--ease-out);
|
| 322 |
+
}
|
| 323 |
+
.md-img:hover { opacity: .94; transform: translateY(-1px); border-color: rgba(108,131,255,.28); }
|
| 324 |
+
.md-figcaption {
|
| 325 |
+
font-size: 11px; line-height: 1.45; color: var(--muted);
|
| 326 |
+
font-family: var(--mono);
|
| 327 |
}
|
|
|
|
| 328 |
p.md-gap { min-height: 0.35em; margin: 0 !important; padding: 0; }
|
| 329 |
|
| 330 |
.quality-dots { display: inline-flex; gap: 2px; align-items: center; margin-left: 4px; }
|
|
|
|
| 332 |
.quality-dot.filled { background: var(--accent2); }
|
| 333 |
|
| 334 |
/* ββ Thinking Block ββ */
|
| 335 |
+
.thinking-dropdown { margin: 8px 0; border-radius: var(--radius-md); overflow: hidden; }
|
| 336 |
.thinking-summary {
|
| 337 |
display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
| 338 |
background: rgba(255,255,255,.04); border: 1px solid var(--border);
|
| 339 |
border-radius: var(--radius-md); cursor: pointer; user-select: none;
|
| 340 |
font-size: 12px; font-family: var(--mono); color: var(--muted);
|
| 341 |
+
transition: background 180ms var(--ease-out), border-color 180ms var(--ease-out), color 180ms var(--ease-out);
|
| 342 |
list-style: none;
|
| 343 |
}
|
| 344 |
+
.thinking-summary .thinking-label { flex: 1; min-width: 0; }
|
| 345 |
.thinking-summary:hover { background: rgba(255,255,255,.06); border-color: var(--border2); }
|
| 346 |
.thinking-summary::-webkit-details-marker { display: none; }
|
| 347 |
.thinking-summary .thinking-arrow {
|
| 348 |
display: inline-block; transition: transform 200ms var(--ease-out); font-size: 10px;
|
| 349 |
}
|
| 350 |
details.thinking-dropdown[open] .thinking-arrow { transform: rotate(90deg); }
|
| 351 |
+
.thinking-dropdown.is-live .thinking-summary {
|
| 352 |
+
color: var(--text); border-color: rgba(108,131,255,.22); background: rgba(108,131,255,.05);
|
| 353 |
+
}
|
| 354 |
.thinking-summary .thinking-spinner {
|
| 355 |
width: 12px; height: 12px; border: 2px solid var(--border2);
|
| 356 |
border-top-color: var(--accent); border-radius: 50%;
|
| 357 |
animation: spin-thinking .8s linear infinite;
|
| 358 |
}
|
| 359 |
@keyframes spin-thinking { to { transform: rotate(360deg); } }
|
| 360 |
+
.thinking-body {
|
| 361 |
+
display: grid; grid-template-rows: 1fr; overflow: hidden;
|
| 362 |
+
transition: grid-template-rows 300ms var(--ease-out), opacity 260ms var(--ease-out);
|
| 363 |
+
opacity: 1;
|
| 364 |
+
}
|
| 365 |
+
.thinking-dropdown:not([open]) .thinking-body {
|
| 366 |
+
grid-template-rows: 0fr; opacity: 0;
|
| 367 |
+
}
|
| 368 |
.thinking-content {
|
| 369 |
+
min-height: 0; padding: 10px 14px; font-size: 13px; line-height: 1.6;
|
| 370 |
color: var(--muted); border: 1px solid var(--border); border-top: none;
|
| 371 |
border-radius: 0 0 var(--radius-md) var(--radius-md);
|
| 372 |
+
background: rgba(255,255,255,.02); max-height: min(38vh, 420px);
|
| 373 |
+
overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
|
| 374 |
}
|
| 375 |
+
.thinking-dropdown.is-live .thinking-content { max-height: min(45vh, 560px); }
|
| 376 |
.thinking-content p { color: var(--muted); }
|
| 377 |
|
| 378 |
/* ββ Vote ββ */
|
|
|
|
| 486 |
.other-answer-card {
|
| 487 |
border: 1px solid var(--border); border-radius: var(--radius-md);
|
| 488 |
padding: 10px 12px; margin-top: 6px; background: rgba(255,255,255,.02);
|
| 489 |
+
animation: fadeUp 260ms var(--ease-out) both; position: relative;
|
| 490 |
}
|
| 491 |
.other-answer-card.related {
|
| 492 |
background: linear-gradient(180deg, rgba(108,131,255,.05), rgba(255,255,255,.02));
|
|
|
|
| 496 |
display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
|
| 497 |
color: var(--muted); font-family: var(--mono); font-size: 10px; margin-bottom: 6px;
|
| 498 |
}
|
| 499 |
+
.other-answer-text { font-size: 13px; line-height: 1.6; }
|
| 500 |
|
| 501 |
/* ββ Preview lines ββ */
|
| 502 |
.preview-block { display: flex; gap: 8px; align-items: flex-start; margin-top: 6px; }
|
|
|
|
| 535 |
.version-card {
|
| 536 |
border: 1px solid var(--border); background: rgba(255,255,255,.02);
|
| 537 |
border-radius: var(--radius-md); padding: 8px 10px; margin-top: var(--space-1);
|
| 538 |
+
animation: fadeUp 220ms var(--ease-out) both;
|
| 539 |
}
|
| 540 |
.version-head {
|
| 541 |
font-size: 10px; color: var(--muted); font-family: var(--mono);
|
|
|
|
| 573 |
/* ββ Typing indicator ββ */
|
| 574 |
.typing-indicator {
|
| 575 |
display: flex; gap: 10px; margin-bottom: 6px; align-items: flex-start;
|
| 576 |
+
animation: fadeUp 320ms var(--ease-out) both;
|
| 577 |
}
|
| 578 |
.typing-dots {
|
| 579 |
display: flex; gap: 4px; align-items: center; padding: 12px 16px;
|
|
|
|
| 707 |
}
|
| 708 |
.anim-option.active { border-color: rgba(108,131,255,.4); background: rgba(108,131,255,.08); color: var(--accent); }
|
| 709 |
.anim-preview { width: 24px; height: 8px; border-radius: 4px; background: var(--border2); overflow: hidden; position: relative; }
|
| 710 |
+
.anim-preview::after {
|
| 711 |
content: ""; position: absolute; inset: 0;
|
| 712 |
background: linear-gradient(90deg, var(--accent), var(--accent2));
|
| 713 |
+
opacity: .4;
|
| 714 |
+
animation: anim-preview-slide var(--anim-preview-duration, 1000ms) var(--anim-preview-ease, ease) infinite alternate;
|
| 715 |
+
}
|
| 716 |
+
.anim-option.active .anim-preview::after { opacity: 1; }
|
| 717 |
+
.anim-option.anim-static .anim-preview::after {
|
| 718 |
+
animation: none; transform: translateX(-38%); opacity: .2;
|
| 719 |
}
|
| 720 |
@keyframes anim-preview-slide { from { transform: translateX(-100%); } to { transform: translateX(0); } }
|
| 721 |
|
|
|
|
| 995 |
animMode: localStorage.getItem('hi_anim') || 'none',
|
| 996 |
density: localStorage.getItem('hi_density') || 'comfortable',
|
| 997 |
lastAction: null, originalTitle: document.title,
|
| 998 |
+
routeConversationId: '',
|
| 999 |
+
renderSessionId: 0,
|
| 1000 |
+
rendering: false,
|
| 1001 |
};
|
| 1002 |
|
| 1003 |
+
const MOTION = Object.freeze({
|
| 1004 |
+
none: { chunkDelay: 0, chunkSize: 6, previewDuration: 0, previewEase: 'linear' },
|
| 1005 |
+
ai: { chunkDelay: 26, chunkSize: 3, previewDuration: 650, previewEase: 'cubic-bezier(.33,.84,.42,1)' },
|
| 1006 |
+
human: { chunkDelay: 44, chunkSize: 2, previewDuration: 980, previewEase: 'cubic-bezier(.22,.61,.36,1)' },
|
| 1007 |
+
diffusion: { chunkDelay: 66, chunkSize: 1, previewDuration: 1320, previewEase: 'cubic-bezier(.25,.46,.45,.94)' },
|
| 1008 |
+
'diffusion-v2': { chunkDelay: 90, chunkSize: 1, previewDuration: 1760, previewEase: 'cubic-bezier(.16,.84,.27,.99)' },
|
| 1009 |
+
});
|
| 1010 |
+
|
| 1011 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 1012 |
UTILITIES
|
| 1013 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
|
|
|
| 1042 |
try { await fn(...a); } finally { setTimeout(() => { blocked = false; }, ms); }
|
| 1043 |
};
|
| 1044 |
}
|
| 1045 |
+
function getMotion(mode = S.animMode) { return MOTION[mode] || MOTION.none; }
|
| 1046 |
+
function shouldAnimateResponses() { return S.animMode !== 'none'; }
|
| 1047 |
+
function beginRenderSession() { S.renderSessionId += 1; S.rendering = true; return S.renderSessionId; }
|
| 1048 |
+
function isRenderSessionActive(id) { return id === S.renderSessionId; }
|
| 1049 |
+
function finishRenderSession(id) { if (isRenderSessionActive(id)) S.rendering = false; }
|
| 1050 |
+
function cancelActiveRender() { S.renderSessionId += 1; S.rendering = false; removeTyping(); }
|
| 1051 |
+
function stripEdgeBlankLines(text) {
|
| 1052 |
+
return String(text || '')
|
| 1053 |
+
.replace(/^\s*\n+/, '')
|
| 1054 |
+
.replace(/\n+\s*$/, '')
|
| 1055 |
+
.trimEnd();
|
| 1056 |
+
}
|
| 1057 |
+
function normalizeConversationPath(conversationId = '') {
|
| 1058 |
+
return conversationId ? `/q/${conversationId}` : '/';
|
| 1059 |
+
}
|
| 1060 |
+
function isNearScrollEnd(el, threshold = 28) {
|
| 1061 |
+
if (!el) return true;
|
| 1062 |
+
return el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
|
| 1063 |
+
}
|
| 1064 |
|
| 1065 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 1066 |
TOAST / STATUS
|
|
|
|
| 1114 |
/!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)|\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
|
| 1115 |
(m, imgAlt, imgSrc, linkText, linkHref) => {
|
| 1116 |
const idx = tokens.length;
|
| 1117 |
+
if (imgSrc !== undefined) {
|
| 1118 |
+
const caption = String(imgAlt || '').trim();
|
| 1119 |
+
tokens.push(
|
| 1120 |
+
`<figure class="md-figure"><img class="md-img" src="${esc(imgSrc)}" alt="${esc(caption)}" loading="lazy">${caption ? `<figcaption class="md-figcaption">${esc(caption)}</figcaption>` : ''}</figure>`
|
| 1121 |
+
);
|
| 1122 |
+
} else {
|
| 1123 |
+
tokens.push(`<a href="${esc(linkHref)}" target="_blank" rel="noopener noreferrer">${esc(linkText)}</a>`);
|
| 1124 |
+
}
|
| 1125 |
return `\x00${idx}\x00`;
|
| 1126 |
}
|
| 1127 |
);
|
|
|
|
| 1152 |
* Returns { segments: [ {type:'text'|'thinking', content:string} ] }
|
| 1153 |
*/
|
| 1154 |
function extractThinkingBlocks(md) {
|
| 1155 |
+
const source = String(md || '');
|
| 1156 |
const segments = [];
|
| 1157 |
const openTag = '<|thinking|>';
|
| 1158 |
const closeTag = '</|thinking|>';
|
| 1159 |
let cursor = 0;
|
| 1160 |
+
while (cursor < source.length) {
|
| 1161 |
+
const openIdx = source.indexOf(openTag, cursor);
|
| 1162 |
if (openIdx === -1) {
|
| 1163 |
+
segments.push({ type: 'text', content: source.slice(cursor) });
|
| 1164 |
break;
|
| 1165 |
}
|
| 1166 |
if (openIdx > cursor) {
|
| 1167 |
+
segments.push({ type: 'text', content: source.slice(cursor, openIdx) });
|
| 1168 |
}
|
| 1169 |
+
const closeIdx = source.indexOf(closeTag, openIdx + openTag.length);
|
| 1170 |
if (closeIdx === -1) {
|
| 1171 |
// Unclosed thinking block β treat rest as thinking
|
| 1172 |
+
segments.push({ type: 'thinking', content: source.slice(openIdx + openTag.length) });
|
| 1173 |
+
cursor = source.length;
|
| 1174 |
break;
|
| 1175 |
}
|
| 1176 |
+
segments.push({ type: 'thinking', content: source.slice(openIdx + openTag.length, closeIdx) });
|
| 1177 |
cursor = closeIdx + closeTag.length;
|
| 1178 |
}
|
| 1179 |
return segments;
|
|
|
|
| 1281 |
out.push(`<p>${renderInlineMarkdown(raw)}</p>`);
|
| 1282 |
}
|
| 1283 |
closeLists(); closeQuote(); closeTable();
|
| 1284 |
+
if (inCode) out.push(`<div class="code-block-wrapper"><pre><code>${esc(codeBuf.join('\n'))}</code></pre></div>`);
|
| 1285 |
return out.join('');
|
| 1286 |
}
|
| 1287 |
|
|
|
|
| 1290 |
* Returns HTML with <details> dropdowns for each thinking block.
|
| 1291 |
* `thinkingDuration` is the number of seconds to display (null = still thinking).
|
| 1292 |
*/
|
| 1293 |
+
function renderThinkingDropdownHtml(thinkingText, label = 'Thoughts') {
|
| 1294 |
+
const content = stripEdgeBlankLines(thinkingText);
|
| 1295 |
+
if (!content) return '';
|
| 1296 |
+
return `<details class="thinking-dropdown"><summary class="thinking-summary"><span class="thinking-arrow" aria-hidden="true">βΆ</span><span class="thinking-label">${esc(label)}</span></summary><div class="thinking-body"><div class="thinking-content md-body">${renderMarkdown(content)}</div></div></details>`;
|
| 1297 |
+
}
|
| 1298 |
+
|
| 1299 |
function renderMarkdownWithThinking(md) {
|
| 1300 |
+
return extractThinkingBlocks(md).map(seg => {
|
| 1301 |
+
const cleaned = stripEdgeBlankLines(seg.content);
|
| 1302 |
+
if (!cleaned) return '';
|
| 1303 |
+
return seg.type === 'thinking'
|
| 1304 |
+
? renderThinkingDropdownHtml(cleaned)
|
| 1305 |
+
: renderMarkdown(cleaned);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1306 |
}).join('');
|
| 1307 |
}
|
| 1308 |
|
|
|
|
| 1375 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 1376 |
ANIMATE TEXT (chunked autoregressive)
|
| 1377 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 1378 |
+
function setThinkingOpen(details, open) {
|
| 1379 |
+
details._internalToggle = true;
|
| 1380 |
+
details.open = open;
|
| 1381 |
+
requestAnimationFrame(() => { details._internalToggle = false; });
|
| 1382 |
+
}
|
| 1383 |
+
async function waitForRenderDelay(ms, renderId) {
|
| 1384 |
+
if (ms <= 0) return isRenderSessionActive(renderId);
|
| 1385 |
+
await sleep(ms);
|
| 1386 |
+
return isRenderSessionActive(renderId);
|
| 1387 |
+
}
|
| 1388 |
+
async function animateText(el, text, renderId) {
|
| 1389 |
+
if (!el) return false;
|
| 1390 |
+
const motion = getMotion();
|
| 1391 |
+
if (motion.chunkDelay === 0) {
|
| 1392 |
+
renderMarkdownInto(el, text, true);
|
| 1393 |
finalizeThinkingBlocks(el);
|
| 1394 |
+
return true;
|
|
|
|
| 1395 |
}
|
| 1396 |
|
| 1397 |
el.innerHTML = '';
|
| 1398 |
+
for (const seg of extractThinkingBlocks(text)) {
|
| 1399 |
+
if (!isRenderSessionActive(renderId)) return false;
|
| 1400 |
+
const cleaned = stripEdgeBlankLines(seg.content);
|
| 1401 |
+
if (!cleaned) continue;
|
| 1402 |
|
|
|
|
| 1403 |
if (seg.type === 'thinking') {
|
| 1404 |
+
const ok = await animateThinkingBlock(el, cleaned, motion, renderId);
|
| 1405 |
+
if (!ok) return false;
|
| 1406 |
+
continue;
|
|
|
|
|
|
|
| 1407 |
}
|
| 1408 |
+
|
| 1409 |
+
const textContainer = document.createElement('div');
|
| 1410 |
+
el.appendChild(textContainer);
|
| 1411 |
+
const ok = await animateMarkdownChunked(textContainer, cleaned, motion, renderId);
|
| 1412 |
+
if (!ok) return false;
|
| 1413 |
}
|
| 1414 |
|
| 1415 |
finalizeThinkingBlocks(el);
|
| 1416 |
+
return isRenderSessionActive(renderId);
|
| 1417 |
}
|
| 1418 |
|
| 1419 |
+
async function animateThinkingBlock(parentEl, thinkingText, motion, renderId) {
|
| 1420 |
+
if (!thinkingText || !isRenderSessionActive(renderId)) return false;
|
| 1421 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1422 |
const details = document.createElement('details');
|
| 1423 |
+
details.className = 'thinking-dropdown is-live';
|
| 1424 |
+
details.innerHTML = `<summary class="thinking-summary"><span class="thinking-spinner" aria-hidden="true"></span><span class="thinking-label">Thinkingβ¦</span></summary><div class="thinking-body"><div class="thinking-content md-body"></div></div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1425 |
parentEl.appendChild(details);
|
| 1426 |
+
bindRichContent(details);
|
| 1427 |
+
setThinkingOpen(details, true);
|
| 1428 |
scrollBottom();
|
| 1429 |
|
| 1430 |
+
const contentDiv = qs('.thinking-content', details);
|
| 1431 |
const startTime = performance.now();
|
| 1432 |
+
const ok = await animateMarkdownChunked(contentDiv, thinkingText, motion, renderId, () => {
|
| 1433 |
+
if (details._followContent !== false) contentDiv.scrollTop = contentDiv.scrollHeight;
|
| 1434 |
+
});
|
| 1435 |
+
if (!ok || !isRenderSessionActive(renderId)) return false;
|
| 1436 |
|
| 1437 |
const elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
|
| 1438 |
+
details.classList.remove('is-live');
|
| 1439 |
+
const summary = qs('.thinking-summary', details);
|
| 1440 |
+
if (summary) {
|
| 1441 |
+
summary.innerHTML = `<span class="thinking-arrow" aria-hidden="true">βΆ</span><span class="thinking-label">Thoughts β’ ${elapsed}s</span>`;
|
| 1442 |
+
}
|
| 1443 |
|
| 1444 |
+
if (!details._userToggled && details.open) {
|
| 1445 |
+
const shouldKeepGoing = await waitForRenderDelay(900, renderId);
|
| 1446 |
+
if (!shouldKeepGoing || !isRenderSessionActive(renderId)) return false;
|
| 1447 |
+
if (!details._userToggled) setThinkingOpen(details, false);
|
| 1448 |
+
}
|
| 1449 |
+
|
| 1450 |
+
return true;
|
| 1451 |
}
|
| 1452 |
|
| 1453 |
+
async function animateMarkdownChunked(el, mdText, motion, renderId, afterChunk = null) {
|
| 1454 |
+
const cleaned = stripEdgeBlankLines(mdText);
|
| 1455 |
+
if (!cleaned) return true;
|
|
|
|
|
|
|
|
|
|
| 1456 |
|
| 1457 |
+
const lines = cleaned.replace(/\r\n/g,'\n').split('\n');
|
| 1458 |
let buffer = '';
|
|
|
|
|
|
|
|
|
|
| 1459 |
|
| 1460 |
+
for (let i = 0; i < lines.length; i += motion.chunkSize) {
|
| 1461 |
+
if (!isRenderSessionActive(renderId)) return false;
|
| 1462 |
+
const chunk = lines.slice(i, i + motion.chunkSize).join('\n');
|
| 1463 |
buffer += (buffer ? '\n' : '') + chunk;
|
| 1464 |
|
| 1465 |
+
renderMarkdownInto(el, buffer, false);
|
| 1466 |
+
afterChunk?.();
|
|
|
|
|
|
|
| 1467 |
scrollBottom();
|
| 1468 |
|
| 1469 |
+
if (i + motion.chunkSize < lines.length) {
|
| 1470 |
+
const stillActive = await waitForRenderDelay(motion.chunkDelay, renderId);
|
| 1471 |
+
if (!stillActive) return false;
|
| 1472 |
}
|
| 1473 |
}
|
| 1474 |
+
return true;
|
| 1475 |
}
|
| 1476 |
|
| 1477 |
+
function finalizeThinkingBlocks(ctx = document) {
|
| 1478 |
+
bindThinkingDropdowns(ctx);
|
| 1479 |
+
qsa('.thinking-dropdown', ctx).forEach(details => {
|
| 1480 |
+
details.classList.remove('is-live');
|
| 1481 |
+
const label = qs('.thinking-label', details);
|
| 1482 |
+
if (label && !label.textContent.trim()) label.textContent = 'Thoughts';
|
| 1483 |
+
});
|
|
|
|
| 1484 |
}
|
| 1485 |
|
| 1486 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 1526 |
});
|
| 1527 |
});
|
| 1528 |
}
|
| 1529 |
+
function bindThinkingDropdowns(ctx = document) {
|
| 1530 |
+
qsa('.thinking-dropdown', ctx).forEach(details => {
|
| 1531 |
+
if (details._thinkingBound) return;
|
| 1532 |
+
details._thinkingBound = true;
|
| 1533 |
+
details._userToggled = false;
|
| 1534 |
+
details._followContent = true;
|
| 1535 |
+
|
| 1536 |
+
const content = qs('.thinking-content', details);
|
| 1537 |
+
if (content) {
|
| 1538 |
+
content.addEventListener('scroll', () => {
|
| 1539 |
+
details._followContent = isNearScrollEnd(content, 32);
|
| 1540 |
+
}, { passive: true });
|
| 1541 |
+
}
|
| 1542 |
+
details.addEventListener('toggle', () => {
|
| 1543 |
+
if (details._internalToggle) return;
|
| 1544 |
+
details._userToggled = true;
|
| 1545 |
+
});
|
| 1546 |
+
});
|
| 1547 |
+
}
|
| 1548 |
+
function bindImageLightboxes(ctx = document) {
|
| 1549 |
+
qsa('.md-img', ctx).forEach(img => {
|
| 1550 |
+
if (img._lightboxBound) return;
|
| 1551 |
+
img._lightboxBound = true;
|
| 1552 |
+
img.addEventListener('click', () => openLightbox(img.currentSrc || img.src, img.alt));
|
| 1553 |
+
});
|
| 1554 |
+
}
|
| 1555 |
+
function bindRichContent(ctx = document) {
|
| 1556 |
+
bindCodeCopyButtons(ctx);
|
| 1557 |
+
bindImageLightboxes(ctx);
|
| 1558 |
+
bindThinkingDropdowns(ctx);
|
| 1559 |
+
}
|
| 1560 |
+
function renderMarkdownInto(el, text, withThinking = true) {
|
| 1561 |
+
if (!el) return;
|
| 1562 |
+
el.innerHTML = withThinking ? renderMarkdownWithThinking(text) : renderMarkdown(text);
|
| 1563 |
+
bindRichContent(el);
|
| 1564 |
+
if (withThinking) finalizeThinkingBlocks(el);
|
| 1565 |
+
}
|
| 1566 |
|
| 1567 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 1568 |
ANSWER HELPERS
|
|
|
|
| 1655 |
<div id="writeEditorPane" role="tabpanel" aria-labelledby="writeTabEdit">
|
| 1656 |
<textarea class="write-textarea" id="writeTextarea" placeholder="Write your answer here⦠Markdown is supported." rows="4" aria-label="Your answer" maxlength="5000"></textarea>
|
| 1657 |
</div>
|
| 1658 |
+
<div id="writePreviewPane" role="tabpanel" aria-labelledby="writeTabPreview" class="write-preview md-body"></div>
|
| 1659 |
<div class="char-count" id="writeCharCount"><span id="writeCharCur">0</span> / 5000</div>
|
| 1660 |
<div class="write-actions">
|
| 1661 |
<button class="write-submit" id="writeSubmit">Submit answer</button>
|
|
|
|
| 1669 |
const rawText = v.text||'';
|
| 1670 |
const label = isBest ? `<span class="chip good">β best answer</span>` : `<span class="chip muted">answer ${idx+1}</span>`;
|
| 1671 |
const bubbleId = isBest ? 'id="bestAnswerText"' : '';
|
| 1672 |
+
const bubbleClass = isBest ? 'best-answer-bubble md-body' : 'bubble md-body';
|
| 1673 |
const glowClass = isBest ? 'answer-new-glow' : '';
|
| 1674 |
return `
|
| 1675 |
<div ${bubbleId} class="${bubbleClass} ${glowClass}" tabindex="-1">${isBest ? '' : renderMarkdownWithThinking(rawText)}</div>
|
|
|
|
| 1700 |
<span>${esc(v.author||'Anonymous')}</span><span aria-hidden="true">Β·</span>
|
| 1701 |
<span>${relativeTime(v.created_at)}</span> ${renderQualityDots(v.text||'')}
|
| 1702 |
</div>
|
| 1703 |
+
<div class="other-answer-text md-body">${renderMarkdownWithThinking(v.text||'')}</div>
|
| 1704 |
${renderVoteRow(a.id, v)}
|
| 1705 |
<div style="display:flex;gap:var(--space-1);flex-wrap:wrap;margin-top:var(--space-1);">
|
| 1706 |
${renderVersions(a)} ${renderPropose(a.id)}
|
|
|
|
| 1742 |
MAIN RENDER
|
| 1743 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 1744 |
async function renderConversation(questionText, doAnimate) {
|
| 1745 |
+
const renderId = beginRenderSession();
|
| 1746 |
const tr=$('transcript'), wl=$('welcome');
|
| 1747 |
const frag = document.createDocumentFragment();
|
| 1748 |
|
| 1749 |
if (!S.conversation) {
|
| 1750 |
wl.style.display=''; tr.replaceChildren();
|
| 1751 |
setJumpLatest(false); document.title=S.originalTitle;
|
| 1752 |
+
updateWelcomeState(); finishRenderSession(renderId); return;
|
| 1753 |
}
|
| 1754 |
|
| 1755 |
wl.style.display='none';
|
| 1756 |
const q = questionText||S.conversation.question||'';
|
| 1757 |
document.title = q.slice(0,60)+' β '+S.originalTitle;
|
| 1758 |
+
if (S.conversation.id) history.replaceState({cid:S.conversation.id},'', normalizeConversationPath(S.conversation.id));
|
| 1759 |
|
| 1760 |
const isNew = !S.conversation.created_at || (Date.now()-new Date(S.conversation.created_at).getTime())<10000;
|
| 1761 |
const questionNote = isNew
|
|
|
|
| 1792 |
}
|
| 1793 |
|
| 1794 |
tr.replaceChildren(frag);
|
| 1795 |
+
bindRichContent(tr);
|
| 1796 |
|
| 1797 |
if (answers.length) {
|
| 1798 |
const bestV = activeVersion(answers[0]);
|
| 1799 |
if (bestV) {
|
| 1800 |
const el = $('bestAnswerText');
|
| 1801 |
+
if (doAnimate && shouldAnimateResponses()) {
|
| 1802 |
+
const completed = await animateText(el, bestV.text||'', renderId);
|
| 1803 |
+
if (!completed) return;
|
| 1804 |
+
} else if (el && isRenderSessionActive(renderId)) {
|
| 1805 |
+
renderMarkdownInto(el, bestV.text||'', true);
|
| 1806 |
+
finalizeThinkingBlocks(el);
|
| 1807 |
}
|
| 1808 |
}
|
| 1809 |
}
|
| 1810 |
+
if (!isRenderSessionActive(renderId)) return;
|
| 1811 |
|
| 1812 |
const rm = $('relatedMount');
|
| 1813 |
if (rm && S.relatedAnswers.length) rm.innerHTML = renderRelated(S.relatedAnswers);
|
| 1814 |
|
| 1815 |
+
bindRichContent(tr);
|
| 1816 |
bindHandlers(); scrollBottom(); restoreDraft();
|
| 1817 |
+
finishRenderSession(renderId);
|
| 1818 |
}
|
| 1819 |
|
| 1820 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 1879 |
if (e.target.closest('#writeSubmit')) { await handleWriteSubmit(); return; }
|
| 1880 |
if (e.target.closest('#writeTabEdit')) { handleWriteTab('edit'); return; }
|
| 1881 |
if (e.target.closest('#writeTabPreview')) { handleWriteTab('preview'); return; }
|
|
|
|
|
|
|
| 1882 |
});
|
| 1883 |
|
| 1884 |
tr.addEventListener('input', e => {
|
|
|
|
| 1889 |
updateCharCount(ta,'writeCharCur',5000);
|
| 1890 |
saveDraft(ta.value);
|
| 1891 |
const preview=$('writePreviewPane');
|
| 1892 |
+
if (preview?.classList.contains('write-preview') && !preview.style.display?.includes('none')) {
|
| 1893 |
+
renderMarkdownInto(preview, ta.value, true);
|
| 1894 |
+
}
|
| 1895 |
} else {
|
| 1896 |
const panel = ta.closest('.propose-panel');
|
| 1897 |
if (panel) {
|
|
|
|
| 2035 |
editorPane.style.display='none'; previewPane.style.display='';
|
| 2036 |
previewPane.classList.add('active');
|
| 2037 |
const ta=$('writeTextarea');
|
| 2038 |
+
previewPane.innerHTML = '';
|
| 2039 |
+
if (ta) {
|
| 2040 |
+
renderMarkdownInto(previewPane, ta.value, true);
|
| 2041 |
+
} else {
|
| 2042 |
+
previewPane.innerHTML = '<p style="color:var(--muted)">Nothing to preview.</p>';
|
| 2043 |
+
}
|
| 2044 |
}
|
| 2045 |
}
|
| 2046 |
|
|
|
|
| 2114 |
ASK / SUBMIT
|
| 2115 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 2116 |
async function askQuestion(q) {
|
| 2117 |
+
cancelActiveRender();
|
| 2118 |
showStatusWithEscalation(); showTyping();
|
| 2119 |
S.loading=true; $('sendBtn').disabled=true; closeAutocomplete();
|
| 2120 |
S.lastAction=()=>askQuestion(q);
|
|
|
|
| 2124 |
S.conversation=res.conversation; S.currentQuestion=q;
|
| 2125 |
S.relatedAnswers=Array.isArray(res.related)?res.related:[];
|
| 2126 |
save(); toast(res.matched?'β Existing answer found':'β New question created','good');
|
| 2127 |
+
await renderConversation(q, shouldAnimateResponses());
|
| 2128 |
}
|
| 2129 |
|
| 2130 |
async function submitPrompt() {
|
|
|
|
| 2142 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
| 2143 |
LOAD SAVED
|
| 2144 |
βββββββββββββββββββββββββββββββββββββββββββ */
|
| 2145 |
+
function showConversationSkeleton() {
|
|
|
|
| 2146 |
const tr=$('transcript'), wl=$('welcome');
|
| 2147 |
wl.style.display='none';
|
| 2148 |
tr.innerHTML=`<div class="skeleton-wrap">
|
|
|
|
| 2150 |
<div class="skeleton skeleton-line long"></div>
|
| 2151 |
<div class="skeleton skeleton-line medium"></div>
|
| 2152 |
<div class="skeleton skeleton-line short"></div></div>`;
|
| 2153 |
+
}
|
| 2154 |
+
async function loadConversationById(id, opts = {}) {
|
| 2155 |
+
const {
|
| 2156 |
+
clearLocalOnMissing = false,
|
| 2157 |
+
replaceHistoryOnMissing = false,
|
| 2158 |
+
showMissingToast = false,
|
| 2159 |
+
showSkeleton = true,
|
| 2160 |
+
statusText = 'Loading conversationβ¦',
|
| 2161 |
+
} = opts;
|
| 2162 |
+
if (!id) return false;
|
| 2163 |
+
|
| 2164 |
+
cancelActiveRender();
|
| 2165 |
+
if (showSkeleton) showConversationSkeleton();
|
| 2166 |
+
showStatus(statusText);
|
| 2167 |
const res=await callAPI('get_conversation',{conversation_id:id});
|
| 2168 |
hideStatus();
|
| 2169 |
if(res.ok&&res.conversation){
|
| 2170 |
S.conversation=res.conversation; S.currentQuestion=res.conversation.question||'';
|
| 2171 |
+
S.relatedAnswers=[]; save();
|
| 2172 |
+
await renderConversation(S.currentQuestion,false);
|
| 2173 |
+
return true;
|
| 2174 |
+
}
|
| 2175 |
+
|
| 2176 |
+
S.conversation = null; S.currentQuestion = ''; S.relatedAnswers = [];
|
| 2177 |
+
$('transcript').innerHTML=''; $('welcome').style.display='';
|
| 2178 |
+
updateWelcomeState();
|
| 2179 |
+
if(clearLocalOnMissing) localStorage.removeItem('hi_last_cid');
|
| 2180 |
+
if(replaceHistoryOnMissing) history.replaceState({},'',normalizeConversationPath());
|
| 2181 |
+
if(showMissingToast) toast('Conversation not found','bad');
|
| 2182 |
+
return false;
|
| 2183 |
+
}
|
| 2184 |
+
async function loadInitialConversation() {
|
| 2185 |
+
if (S.routeConversationId) {
|
| 2186 |
+
const fromRoute = await loadConversationById(S.routeConversationId, {
|
| 2187 |
+
replaceHistoryOnMissing: true,
|
| 2188 |
+
showMissingToast: true,
|
| 2189 |
+
});
|
| 2190 |
+
if (fromRoute) return;
|
| 2191 |
+
}
|
| 2192 |
+
|
| 2193 |
+
const savedId = localStorage.getItem('hi_last_cid');
|
| 2194 |
+
if (savedId) {
|
| 2195 |
+
await loadConversationById(savedId, { clearLocalOnMissing: true });
|
| 2196 |
+
}
|
| 2197 |
}
|
| 2198 |
|
| 2199 |
/* βββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 2203 |
if(qsa('textarea').some(t=>t.value.trim())){
|
| 2204 |
if(!await confirmModal('Start a new chat?','You have unsaved content. It will be lost.')) return;
|
| 2205 |
}
|
| 2206 |
+
cancelActiveRender();
|
| 2207 |
S.conversation=null; S.currentQuestion=''; S.relatedAnswers=[]; S.atBottom=true;
|
| 2208 |
+
S.routeConversationId='';
|
| 2209 |
localStorage.removeItem('hi_last_cid');
|
| 2210 |
$('transcript').innerHTML=''; $('welcome').style.display='';
|
| 2211 |
updateWelcomeState(); setJumpLatest(false); $('prompt').value='';
|
| 2212 |
+
autoGrow($('prompt')); history.replaceState({},'',normalizeConversationPath());
|
| 2213 |
document.title=S.originalTitle; $('prompt').focus();
|
| 2214 |
}
|
| 2215 |
|
|
|
|
| 2242 |
// Animation options
|
| 2243 |
const animOpts=qsa('.anim-option',$('animSegment'));
|
| 2244 |
function syncAnim(){
|
| 2245 |
+
animOpts.forEach(o=>{
|
| 2246 |
+
const mode = o.getAttribute('data-anim');
|
| 2247 |
+
const active = S.animMode===mode;
|
| 2248 |
+
const motion = getMotion(mode);
|
| 2249 |
+
o.classList.toggle('active',active);
|
| 2250 |
+
o.classList.toggle('anim-static', mode==='none');
|
| 2251 |
+
o.setAttribute('aria-checked',String(active));
|
| 2252 |
+
o.style.setProperty('--anim-preview-duration', `${Math.max(motion.previewDuration, 1)}ms`);
|
| 2253 |
+
o.style.setProperty('--anim-preview-ease', motion.previewEase);
|
| 2254 |
+
});
|
| 2255 |
+
}
|
| 2256 |
+
async function applyAnimMode(mode) {
|
| 2257 |
+
if (!mode || mode === S.animMode) return;
|
| 2258 |
+
const hadActiveRender = S.rendering;
|
| 2259 |
+
S.animMode=mode;
|
| 2260 |
+
localStorage.setItem('hi_anim',S.animMode);
|
| 2261 |
+
syncAnim();
|
| 2262 |
+
if (S.conversation && hadActiveRender) {
|
| 2263 |
+
cancelActiveRender();
|
| 2264 |
+
await renderConversation(S.currentQuestion, shouldAnimateResponses());
|
| 2265 |
+
}
|
| 2266 |
}
|
| 2267 |
animOpts.forEach(o=>{
|
| 2268 |
+
o.addEventListener('click',async ()=>{ await applyAnimMode(o.getAttribute('data-anim')); });
|
| 2269 |
o.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();o.click();}});
|
| 2270 |
});
|
| 2271 |
|
|
|
|
| 2370 |
function initPullToRefresh(){
|
| 2371 |
const chat=$('chat'); let pullStart=0;
|
| 2372 |
chat.addEventListener('touchstart',e=>{pullStart=chat.scrollTop===0?e.touches[0].clientY:0;},{passive:true});
|
| 2373 |
+
chat.addEventListener('touchend',async e=>{
|
| 2374 |
if(!pullStart) return;
|
| 2375 |
if(e.changedTouches[0].clientY-pullStart>80&&S.conversation){
|
| 2376 |
+
pullStart=0;
|
| 2377 |
+
const ok = await loadConversationById(S.conversation.id, {
|
| 2378 |
+
showSkeleton: false,
|
| 2379 |
+
statusText: 'Refreshingβ¦',
|
| 2380 |
});
|
| 2381 |
+
if (ok) toast('Refreshed','good');
|
| 2382 |
}
|
| 2383 |
pullStart=0;
|
| 2384 |
},{passive:true});
|
|
|
|
| 2424 |
|
| 2425 |
const d=window.__HI_INIT__||{};
|
| 2426 |
if(d.client_id) S.clientId=d.client_id;
|
| 2427 |
+
S.routeConversationId = String(d.conversation_id || '').trim();
|
| 2428 |
+
updateWelcomeState(); await loadInitialConversation(); prompt.focus();
|
| 2429 |
}
|
| 2430 |
|
| 2431 |
init();
|
| 2432 |
})();
|
| 2433 |
</script>
|
| 2434 |
</body>
|
| 2435 |
+
</html>
|