|
31 | 31 | * { margin: 0; padding: 0; box-sizing: border-box; } |
32 | 32 | body { font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); height: 100vh; height: 100dvh; display: flex; overflow: hidden; font-size: 15px; line-height: 1.55; } |
33 | 33 |
|
| 34 | +/* ─── Thinking badge ─── */ |
| 35 | +.thinking-badge { |
| 36 | + display: inline-flex; align-items: center; gap: 6px; |
| 37 | + padding: 5px 12px; background: var(--s2); border: 1px solid var(--border); |
| 38 | + border-radius: 20px; font-size: 11px; color: var(--muted); cursor: pointer; |
| 39 | + transition: border-color .15s, color .15s, background .15s; user-select: none; |
| 40 | + margin: 4px 0; |
| 41 | +} |
| 42 | +.thinking-badge:hover { border-color: var(--accent); color: var(--accent2); background: var(--abg); } |
| 43 | +.thinking-badge svg { width: 11px; height: 11px; flex-shrink: 0; } |
| 44 | +.mw.assistant.thinking-msg { background: transparent; padding: 2px 12px 2px 44px; } |
| 45 | +.mw.assistant.thinking-msg .msg { background: transparent; box-shadow: none; padding: 0; } |
| 46 | + |
| 47 | +/* ─── Tool result block ─── */ |
| 48 | +.tool-result-block { |
| 49 | + border: 1px solid var(--border); border-radius: var(--r-sm); |
| 50 | + background: var(--tool-bg); font-size: 11px; overflow: hidden; margin: 2px 0; |
| 51 | +} |
| 52 | +.tr-header { |
| 53 | + display: flex; align-items: center; gap: 6px; padding: 5px 10px; |
| 54 | + cursor: pointer; user-select: none; color: var(--muted); |
| 55 | + transition: background .1s; font-family: var(--font-mono); |
| 56 | +} |
| 57 | +.tr-header:hover { background: var(--s2); } |
| 58 | +.tr-header .tr-arrow { font-size: 10px; transition: transform .2s; flex-shrink: 0; } |
| 59 | +.tr-header.open .tr-arrow { transform: rotate(90deg); } |
| 60 | +.tr-content { margin: 0; padding: 8px 10px; border-top: 1px solid var(--border); overflow-x: auto; white-space: pre-wrap; word-break: break-all; max-height: 320px; overflow-y: auto; font-family: var(--font-mono); font-size: 11px; color: var(--text); display: none; } |
| 61 | +.tr-content.open { display: block; } |
| 62 | +.mw.user.tool-result-msg { background: transparent; padding: 2px 12px; } |
| 63 | +.mw.user.tool-result-msg .msg { background: transparent; box-shadow: none; padding: 0; border: none; } |
| 64 | + |
| 65 | +/* ─── Thinking modal ─── */ |
| 66 | +.thinking-modal-body { |
| 67 | + flex: 1; overflow-y: auto; padding: 16px 20px; min-height: 0; |
| 68 | +} |
| 69 | +.thinking-modal-pre { |
| 70 | + font-family: var(--font-mono); font-size: 13px; line-height: 1.6; |
| 71 | + white-space: pre-wrap; word-break: break-word; color: var(--text); |
| 72 | + background: var(--code-bg); border-radius: var(--r-sm); |
| 73 | + padding: 16px; border: 1px solid var(--border); margin: 0; |
| 74 | +} |
| 75 | + |
34 | 76 | /* ─── Focus visible (keyboard nav) ─── */ |
35 | 77 | :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; } |
36 | 78 | button:focus-visible, a:focus-visible { border-radius: 5px; } |
@@ -2810,6 +2852,31 @@ <h2 id="dirModalTitle" data-i18n="dir.title">Новий проект</h2> |
2810 | 2852 | </div> |
2811 | 2853 | </div> |
2812 | 2854 |
|
| 2855 | +<!-- Thinking Modal --> |
| 2856 | +<div class="modal-overlay hidden" id="thinkingModal" aria-hidden="true" onclick="if(event.target===this)closeThinkingModal()"> |
| 2857 | + <div class="modal" role="dialog" aria-modal="true" tabindex="-1" style="width:min(820px,95vw);max-height:88vh;max-height:88dvh;display:flex;flex-direction:column"> |
| 2858 | + <div class="modal-hdr"> |
| 2859 | + <h2 style="font-size:14px;display:flex;align-items:center;gap:7px"> |
| 2860 | + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg> |
| 2861 | + Chain of thought |
| 2862 | + <span id="thinkingModalMeta" style="font-size:11px;font-weight:400;color:var(--muted)"></span> |
| 2863 | + </h2> |
| 2864 | + <button class="modal-close" onclick="closeThinkingModal()">✕</button> |
| 2865 | + </div> |
| 2866 | + <div class="thinking-modal-body"> |
| 2867 | + <pre class="thinking-modal-pre" id="thinkingModalPre"></pre> |
| 2868 | + </div> |
| 2869 | + <div class="modal-footer" style="flex-shrink:0"> |
| 2870 | + <span style="font-size:11px;color:var(--muted)" id="thinkingCopyFeedback"></span> |
| 2871 | + <button class="bg" onclick="closeThinkingModal()">Закрити</button> |
| 2872 | + <button class="bp" onclick="copyThinkingContent()" style="margin-left:auto"> |
| 2873 | + <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg> |
| 2874 | + Копіювати |
| 2875 | + </button> |
| 2876 | + </div> |
| 2877 | + </div> |
| 2878 | +</div> |
| 2879 | + |
2813 | 2880 | <!-- CLI Import Modal --> |
2814 | 2881 | <div class="modal-overlay hidden" id="cliImportModal" aria-hidden="true" onclick="if(event.target===this)closeCliImport()"> |
2815 | 2882 | <div class="modal" role="dialog" aria-modal="true" tabindex="-1" style="width:560px;max-height:84vh;max-height:84dvh;display:flex;flex-direction:column"> |
@@ -7463,6 +7530,51 @@ <h3 id="rlModalTitle">Ліміт вичерпано</h3> |
7463 | 7530 | return card; |
7464 | 7531 | } |
7465 | 7532 | const hasReply = _allMsgs.slice(idx + 1).some(x => x.role === 'assistant'); |
| 7533 | + |
| 7534 | + // ── Thinking block: render as clickable badge ── |
| 7535 | + if (m.type === 'thinking') { |
| 7536 | + const w = document.createElement('div'); |
| 7537 | + w.className = 'mw assistant thinking-msg'; |
| 7538 | + if (m.id) w.dataset.msgId = String(m.id); |
| 7539 | + const charCount = (m.content || '').length; |
| 7540 | + const wordsEst = Math.round(charCount / 5); |
| 7541 | + const div = document.createElement('div'); |
| 7542 | + div.className = 'msg'; |
| 7543 | + const badge = document.createElement('button'); |
| 7544 | + badge.className = 'thinking-badge'; |
| 7545 | + badge.setAttribute('aria-label', 'View chain of thought'); |
| 7546 | + badge.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg><span>Chain of thought</span><span style="color:var(--muted)">·</span><span>${wordsEst.toLocaleString()} words</span><svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M7 17L17 7M7 7h10v10"/></svg>`; |
| 7547 | + const content = m.content || ''; |
| 7548 | + badge.onclick = () => openThinkingModal(content); |
| 7549 | + div.appendChild(badge); |
| 7550 | + w.appendChild(div); |
| 7551 | + if (prepend) { const f = msgsEl.querySelector('.mw, .agent-team-card'); if (f) msgsEl.insertBefore(w, f); else msgsEl.prepend(w); } |
| 7552 | + else msgsEl.appendChild(w); |
| 7553 | + return w; |
| 7554 | + } |
| 7555 | + |
| 7556 | + // ── Tool result: render as collapsible block ── |
| 7557 | + if (m.type === 'tool_result') { |
| 7558 | + const w = document.createElement('div'); |
| 7559 | + w.className = 'mw user tool-result-msg'; |
| 7560 | + if (m.id) w.dataset.msgId = String(m.id); |
| 7561 | + const raw = m.content || ''; |
| 7562 | + const lines = raw.split('\n'); |
| 7563 | + const lineCount = lines.length; |
| 7564 | + const preview = lines.slice(0, 2).join('\n'); |
| 7565 | + const block = document.createElement('div'); |
| 7566 | + block.className = 'tool-result-block'; |
| 7567 | + const toolLabel = m.tool_name ? escH(m.tool_name) + ' result' : 'tool result'; |
| 7568 | + block.innerHTML = `<div class="tr-header" onclick="this.classList.toggle('open');this.nextElementSibling.classList.toggle('open')"><span class="tr-arrow">▶</span><span style="font-size:11px;color:var(--muted)">${toolLabel}</span><span style="margin-left:4px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escH(preview.substring(0,80))}${lineCount > 2 ? '…' : ''}</span><span style="font-size:10px;color:var(--muted);flex-shrink:0;margin-left:6px">${lineCount} line${lineCount !== 1 ? 's' : ''}</span></div><pre class="tr-content">${escH(raw)}</pre>`; |
| 7569 | + const div = document.createElement('div'); |
| 7570 | + div.className = 'msg'; |
| 7571 | + div.appendChild(block); |
| 7572 | + w.appendChild(div); |
| 7573 | + if (prepend) { const f = msgsEl.querySelector('.mw, .agent-team-card'); if (f) msgsEl.insertBefore(w, f); else msgsEl.prepend(w); } |
| 7574 | + else msgsEl.appendChild(w); |
| 7575 | + return w; |
| 7576 | + } |
| 7577 | + |
7466 | 7578 | const w = document.createElement('div'); |
7467 | 7579 | w.className = `mw ${m.role}`; |
7468 | 7580 | if (m.id) w.dataset.msgId = String(m.id); |
@@ -7698,6 +7810,7 @@ <h3 id="rlModalTitle">Ліміт вичерпано</h3> |
7698 | 7810 | // Single mode: scan backward for consecutive tool rows before this text message |
7699 | 7811 | const tc = {}; |
7700 | 7812 | for (let j = i - 1; j >= 0; j--) { |
| 7813 | + if (raw[j].type === 'thinking' || raw[j].type === 'tool_result') continue; |
7701 | 7814 | if (raw[j].type !== 'tool' || raw[j].agent_id) break; |
7702 | 7815 | const tn = raw[j].tool_name || 'tool'; |
7703 | 7816 | tc[tn] = (tc[tn] || 0) + 1; |
@@ -9868,6 +9981,32 @@ <h3 id="rlModalTitle">Ліміт вичерпано</h3> |
9868 | 9981 | closeModalOverlay('sshHostModal'); |
9869 | 9982 | } |
9870 | 9983 |
|
| 9984 | +// ─── Thinking Modal ────────────────────────────────────────────────────────── |
| 9985 | +let _thinkingContent = ''; |
| 9986 | + |
| 9987 | +function openThinkingModal(content) { |
| 9988 | + _thinkingContent = content || ''; |
| 9989 | + const pre = $i('thinkingModalPre'); |
| 9990 | + const meta = $i('thinkingModalMeta'); |
| 9991 | + const feedback = $i('thinkingCopyFeedback'); |
| 9992 | + pre.textContent = _thinkingContent; |
| 9993 | + const charCount = _thinkingContent.length; |
| 9994 | + const wordCount = Math.round(charCount / 5); |
| 9995 | + meta.textContent = `${wordCount.toLocaleString()} words · ${charCount.toLocaleString()} chars`; |
| 9996 | + if (feedback) feedback.textContent = ''; |
| 9997 | + openModalOverlay('thinkingModal'); |
| 9998 | +} |
| 9999 | + |
| 10000 | +function closeThinkingModal() { closeModalOverlay('thinkingModal'); } |
| 10001 | + |
| 10002 | +async function copyThinkingContent() { |
| 10003 | + try { |
| 10004 | + await navigator.clipboard.writeText(_thinkingContent); |
| 10005 | + const fb = $i('thinkingCopyFeedback'); |
| 10006 | + if (fb) { fb.textContent = '✓ Скопійовано'; setTimeout(() => { if (fb) fb.textContent = ''; }, 2000); } |
| 10007 | + } catch { toast('Не вдалося скопіювати'); } |
| 10008 | +} |
| 10009 | + |
9871 | 10010 | // ─── CLI Import ────────────────────────────────────────────────────────────── |
9872 | 10011 | let _cliSessions = []; // cache of loaded sessions from server |
9873 | 10012 |
|
|
0 commit comments