Skip to content

Commit 6230a4d

Browse files
Lexus2016claude
andcommitted
feat: extended thinking display, collapsible tool results, JSONL-first session serving
- Thinking blocks rendered as clickable 'Chain of thought' badges with word count estimate - Thinking modal with full reasoning content and copy button - Tool results displayed as collapsible blocks (tool name, preview, line count) - parseJsonlToMessages(): parses Claude CLI JSONL with thinking/tool_result/tool_use support - GET /api/sessions/:id now serves from JSONL first (preserves thinking blocks), falls back to SQLite - loadSess: skip thinking/tool_result rows when counting tool badges Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0b33872 commit 6230a4d

5 files changed

Lines changed: 210 additions & 9 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ Not a chatbot. "Refactor this function and add tests" → Claude opens files, ed
8787

8888
**Claude CLI session import** — import existing sessions from Claude Code CLI (`~/.claude/projects/`) directly into Studio. Click the ↓ button in the header, pick a project path, select sessions, import. Already-imported sessions are marked so you don't duplicate them. Works on Windows (`C:\...`), macOS, and Linux; supports `~` path expansion.
8989

90+
**Extended thinking** — when Claude uses extended thinking, each thinking block appears as a "Chain of thought" badge showing estimated word count. Click to open the full reasoning in a modal with a copy button. Tool results are shown as collapsible blocks with tool name, preview, and line count — no more noise, full detail on demand.
91+
9092
### 📋 Kanban Board
9193

9294
Create a card, describe what you want, move to "To Do" — Claude picks it up automatically.
@@ -247,7 +249,7 @@ npx github:Lexus2016/claude-code-studio # launch as usual
247249

248250
| Category | Features |
249251
|----------|----------|
250-
| **Chat** | Real-time streaming, screenshot paste, file attach (`@file`), conversation fork, auto-continue (3x), session compact, sidebar quick-filter, CLI session import |
252+
| **Chat** | Real-time streaming, screenshot paste, file attach (`@file`), conversation fork, auto-continue (3x), session compact, sidebar quick-filter, CLI session import, extended thinking display, collapsible tool results |
251253
| **Kanban** | Task queue, parallel + sequential, cross-tab sync, drag-and-drop tabs, dependency graphs |
252254
| **Scheduler** | One-time + recurring (hourly/daily/weekly/monthly), 5 parallel workers, Run Now, SQLite-persisted |
253255
| **Task Manager** | Autonomous child tasks, chains, context passing, result reporting, cancellation (MCP) |

README_RU.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ MIRROR=my-registry.company.com docker compose up -d --build
8787

8888
**Импорт сессий Claude CLI** — импортируйте существующие сессии Claude Code CLI из `~/.claude/projects/` прямо в Studio. Нажмите кнопку ↓ в шапке, укажите путь к проекту, выберите сессии, импортируйте. Уже импортированные сессии отмечаются, чтобы не было дублей. Работает на Windows (`C:\...`), macOS и Linux; поддерживается раскрытие `~`.
8989

90+
**Расширенное мышление** — когда Claude использует extended thinking, каждый блок размышлений отображается как бейдж «Chain of thought» с оценкой количества слов. Нажмите, чтобы открыть полную цепочку рассуждений в модальном окне с кнопкой копирования. Результаты инструментов отображаются как сворачиваемые блоки с названием инструмента, превью и количеством строк — минимум шума, полная детализация по запросу.
91+
9092
### 📋 Kanban-доска
9193

9294
Создайте карточку, опишите что нужно сделать, переместите в «К выполнению» — Claude подхватит её автоматически.
@@ -247,7 +249,7 @@ npx github:Lexus2016/claude-code-studio # запуск как обычно
247249

248250
| Категория | Возможности |
249251
|----------|----------|
250-
| **Чат** | Потоковая передача в реальном времени, вставка скриншотов, прикрепление файлов (`@file`), ветвление разговора, автопродолжение (3x), сжатие сессий, быстрый фильтр боковой панели, импорт сессий CLI |
252+
| **Чат** | Потоковая передача в реальном времени, вставка скриншотов, прикрепление файлов (`@file`), ветвление разговора, автопродолжение (3x), сжатие сессий, быстрый фильтр боковой панели, импорт сессий CLI, отображение extended thinking, сворачиваемые результаты инструментов |
251253
| **Kanban** | Очередь задач, параллельное + последовательное выполнение, синхронизация между вкладками, drag-and-drop вкладок, графы зависимостей |
252254
| **Планировщик** | Разовые + повторяющиеся (ежечасно/ежедневно/еженедельно/ежемесячно), 5 параллельных воркеров, Run Now, хранение в SQLite |
253255
| **Менеджер задач** | Автономные дочерние задачи, цепочки, передача контекста, отчётность о результатах, отмена (MCP) |

README_UA.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ MIRROR=my-registry.company.com docker compose up -d --build
8787

8888
**Імпорт сесій Claude CLI** — імпортуйте наявні сесії Claude Code CLI з `~/.claude/projects/` прямо в Studio. Натисніть кнопку ↓ у шапці, вкажіть шлях до проекту, виберіть сесії, імпортуйте. Вже імпортовані сесії позначаються, щоб уникнути дублів. Працює на Windows (`C:\...`), macOS та Linux; підтримується розкриття `~`.
8989

90+
**Extended thinking** — коли Claude використовує розширене мислення, кожен блок думок відображається як бейдж «Chain of thought» з оцінкою кількості слів. Натисніть, щоб відкрити повний ланцюжок міркувань у модальному вікні з кнопкою копіювання. Результати інструментів відображаються як згортувані блоки з назвою інструменту, прев'ю та кількістю рядків — мінімум шуму, повна деталізація на запит.
91+
9092
### 📋 Kanban-дошка
9193

9294
Створіть картку, опишіть що потрібно, перемістіть у "To Do" — Claude підхопить автоматично.
@@ -247,7 +249,7 @@ npx github:Lexus2016/claude-code-studio # запуск як зазвичай
247249

248250
| Категорія | Можливості |
249251
|----------|----------|
250-
| **Чат** | Потокова передача в реальному часі, вставка скріншотів, прикріплення файлів (`@file`), форк розмови, авто-продовження (3x), стиснення сесій, швидкий фільтр бічної панелі, імпорт сесій CLI |
252+
| **Чат** | Потокова передача в реальному часі, вставка скріншотів, прикріплення файлів (`@file`), форк розмови, авто-продовження (3x), стиснення сесій, швидкий фільтр бічної панелі, імпорт сесій CLI, відображення extended thinking, згортувані результати інструментів |
251253
| **Kanban** | Черга завдань, паралельно + послідовно, синхронізація між вкладками, drag-and-drop вкладки, графи залежностей |
252254
| **Scheduler** | Одноразово + повторювані (щогодини/щодня/щотижня/щомісяця), 5 паралельних воркерів, Run Now, збереження в SQLite |
253255
| **Task Manager** | Автономні дочірні завдання, ланцюжки, передача контексту, звітування про результати, скасування (MCP) |

public/index.html

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,48 @@
3131
* { margin: 0; padding: 0; box-sizing: border-box; }
3232
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; }
3333

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+
3476
/* ─── Focus visible (keyboard nav) ─── */
3577
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
3678
button:focus-visible, a:focus-visible { border-radius: 5px; }
@@ -2810,6 +2852,31 @@ <h2 id="dirModalTitle" data-i18n="dir.title">Новий проект</h2>
28102852
</div>
28112853
</div>
28122854

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+
28132880
<!-- CLI Import Modal -->
28142881
<div class="modal-overlay hidden" id="cliImportModal" aria-hidden="true" onclick="if(event.target===this)closeCliImport()">
28152882
<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>
74637530
return card;
74647531
}
74657532
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+
74667578
const w = document.createElement('div');
74677579
w.className = `mw ${m.role}`;
74687580
if (m.id) w.dataset.msgId = String(m.id);
@@ -7698,6 +7810,7 @@ <h3 id="rlModalTitle">Ліміт вичерпано</h3>
76987810
// Single mode: scan backward for consecutive tool rows before this text message
76997811
const tc = {};
77007812
for (let j = i - 1; j >= 0; j--) {
7813+
if (raw[j].type === 'thinking' || raw[j].type === 'tool_result') continue;
77017814
if (raw[j].type !== 'tool' || raw[j].agent_id) break;
77027815
const tn = raw[j].tool_name || 'tool';
77037816
tc[tn] = (tc[tn] || 0) + 1;
@@ -9868,6 +9981,32 @@ <h3 id="rlModalTitle">Ліміт вичерпано</h3>
98689981
closeModalOverlay('sshHostModal');
98699982
}
98709983

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+
987110010
// ─── CLI Import ──────────────────────────────────────────────────────────────
987210011
let _cliSessions = []; // cache of loaded sessions from server
987310012

0 commit comments

Comments
 (0)