Skip to content

Commit b55cd13

Browse files
committed
Enh(viewer): Have better expand/minimize for messages
1 parent d036aea commit b55cd13

3 files changed

Lines changed: 185 additions & 78 deletions

File tree

codeclash/viewer/static/css/style.css

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ body {
262262
.messages-container {
263263
display: flex;
264264
flex-direction: column;
265-
gap: 1rem;
265+
gap: 0.75rem;
266266
}
267267

268268
.message-block {
@@ -285,41 +285,67 @@ body {
285285
}
286286

287287
.message-header {
288-
background-color: var(--bg-tertiary);
289288
padding: 0.75rem 1rem;
290-
display: flex;
291-
justify-content: space-between;
292-
align-items: center;
293-
font-weight: 500;
289+
background-color: var(--bg-secondary);
294290
}
295291

296-
.message-role {
297-
text-transform: capitalize;
292+
.message-role-inline {
298293
font-weight: 600;
294+
font-size: 0.875rem;
295+
color: var(--text-primary);
296+
text-transform: capitalize;
297+
display: inline-block;
298+
margin-bottom: 0.5rem;
299299
}
300300

301-
.message-index {
302-
font-size: 0.875rem;
303-
color: var(--text-secondary);
301+
.message-preview {
302+
margin-top: 0.5rem;
304303
}
305304

306-
.message-content-foldout {
307-
border: none;
308-
margin: 0;
309-
background: transparent;
305+
.message-preview-short,
306+
.message-content-full,
307+
.message-content-expanded {
308+
background-color: var(--bg-primary);
309+
border-radius: 0.375rem;
310+
padding: 0.75rem;
310311
}
311312

312-
.message-content-foldout summary {
313-
padding: 0.75rem 1rem;
314-
background-color: var(--bg-secondary);
315-
border: none;
316-
font-weight: normal;
317-
font-size: 0.875rem;
313+
.message-preview-short {
314+
border: 1px solid var(--border-color);
315+
}
316+
317+
.clickable-message {
318+
cursor: pointer;
319+
transition: all 0.2s ease;
320+
position: relative;
321+
}
322+
323+
.clickable-message:hover {
324+
background-color: var(--bg-tertiary);
325+
transform: translateY(-1px);
326+
box-shadow: var(--shadow);
327+
}
328+
329+
.clickable-message:active {
330+
transform: translateY(0);
331+
}
332+
333+
.expand-indicator,
334+
.collapse-indicator {
335+
font-size: 0.75rem;
318336
color: var(--text-secondary);
337+
text-align: center;
338+
padding: 0.5rem;
339+
margin-top: 0.5rem;
340+
border-top: 1px solid var(--border-color);
341+
background-color: var(--bg-secondary);
342+
font-weight: 500;
319343
}
320344

321-
.message-content {
322-
padding: 1rem;
345+
.clickable-message:hover .expand-indicator,
346+
.clickable-message:hover .collapse-indicator {
347+
color: var(--accent-color);
348+
background-color: var(--bg-tertiary);
323349
}
324350

325351
.message-text pre,

codeclash/viewer/static/js/app.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ function initializeKeyboardShortcuts() {
106106
details.removeAttribute('open');
107107
});
108108
}
109+
110+
109111
});
110112
}
111113

@@ -271,6 +273,44 @@ function initializePerformanceMonitoring() {
271273
});
272274
}
273275

276+
// Message expand/collapse functionality
277+
function toggleMessageExpand(clickedElement) {
278+
const messageHeader = clickedElement.closest('.message-header');
279+
const previewShort = messageHeader.querySelector('.message-preview-short');
280+
const contentFull = messageHeader.querySelector('.message-content-full');
281+
const contentExpanded = messageHeader.querySelector('.message-content-expanded');
282+
283+
if (clickedElement.classList.contains('message-preview-short')) {
284+
// Expanding - hide preview, show full content
285+
if (previewShort) previewShort.style.display = 'none';
286+
if (contentFull) contentFull.style.display = 'block';
287+
if (contentExpanded) contentExpanded.style.display = 'block';
288+
289+
// Smooth scroll to keep the content in view
290+
setTimeout(() => {
291+
messageHeader.scrollIntoView({
292+
behavior: 'smooth',
293+
block: 'nearest'
294+
});
295+
}, 100);
296+
} else if (clickedElement.classList.contains('message-content-full') || clickedElement.classList.contains('message-content-expanded')) {
297+
// Collapsing - show preview, hide full content
298+
if (contentFull) contentFull.style.display = 'none';
299+
if (contentExpanded) contentExpanded.style.display = 'none';
300+
if (previewShort) previewShort.style.display = 'block';
301+
302+
// Smooth scroll to keep the header in view
303+
setTimeout(() => {
304+
messageHeader.scrollIntoView({
305+
behavior: 'smooth',
306+
block: 'nearest'
307+
});
308+
}, 100);
309+
}
310+
}
311+
312+
313+
274314
// Initialize everything when DOM is loaded
275315
document.addEventListener('DOMContentLoaded', function() {
276316
initializeTheme();

codeclash/viewer/templates/trajectory.html

Lines changed: 96 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -22,75 +22,116 @@
2222
{% for message in trajectory.messages %}
2323
<div class="message-block {{ message.role }}">
2424
<div class="message-header">
25-
<span class="message-role">{{ message.role.title() }}</span>
26-
<span class="message-index">#{{ loop.index }}</span>
27-
</div>
28-
29-
<details class="message-content-foldout" {% if loop.index <= 2 %}open{% endif %}>
30-
<summary class="message-summary">
31-
{% if message.role == 'system' %}
32-
System instructions...
33-
{% elif message.role == 'user' %}
34-
{% if message.content is string %}
35-
{{ message.content[:100] }}{% if message.content|length > 100 %}...{% endif %}
36-
{% else %}
37-
User input with {{ message.content|length }} parts
38-
{% endif %}
39-
{% elif message.role == 'assistant' %}
40-
{% if 'THOUGHT:' in message.content %}
41-
{{ message.content.split('THOUGHT:')[1].split('```')[0][:100] }}...
42-
{% else %}
43-
{{ message.content[:100] }}{% if message.content|length > 100 %}...{% endif %}
44-
{% endif %}
45-
{% else %}
46-
{{ message.content[:100] }}{% if message.content|length > 100 %}...{% endif %}
47-
{% endif %}
48-
</summary>
49-
50-
<div class="message-content">
25+
<span class="message-role-inline">{{ message.role.title() }} #{{ loop.index }}</span>
26+
<div class="message-preview">
5127
{% if message.content is string %}
52-
{% if message.role == 'assistant' and '```' in message.content %}
53-
<!-- Special handling for assistant messages with code blocks -->
54-
{% set parts = message.content.split('```') %}
55-
{% for part in parts %}
56-
{% if loop.index is odd %}
57-
<!-- Text part -->
58-
<div class="message-text">{{ part | nl2br | safe }}</div>
28+
{% set content_lines = message.content.split('\n') %}
29+
{% if content_lines|length <= 5 %}
30+
<!-- Show full content if 5 lines or less -->
31+
<div class="message-content-full">
32+
{% if message.role == 'assistant' and '```' in message.content %}
33+
<!-- Special handling for assistant messages with code blocks -->
34+
{% set parts = message.content.split('```') %}
35+
{% for part in parts %}
36+
{% if loop.index is odd %}
37+
<!-- Text part -->
38+
<div class="message-text"><pre>{{ part }}</pre></div>
39+
{% else %}
40+
<!-- Code part -->
41+
<div class="code-block">
42+
<pre><code>{{ part }}</code></pre>
43+
</div>
44+
{% endif %}
45+
{% endfor %}
5946
{% else %}
60-
<!-- Code part -->
61-
<div class="code-block">
62-
<pre><code>{{ part }}</code></pre>
47+
<!-- Regular text content -->
48+
<div class="message-text">
49+
<pre>{{ message.content }}</pre>
6350
</div>
6451
{% endif %}
65-
{% endfor %}
52+
</div>
6653
{% else %}
67-
<!-- Regular text content -->
68-
<div class="message-text">
69-
<pre>{{ message.content }}</pre>
54+
<!-- Show preview with expand button -->
55+
<div class="message-preview-short clickable-message" onclick="toggleMessageExpand(this)" title="Click to expand ({{ content_lines|length - 5 }} more lines)">
56+
<div class="message-text">
57+
<pre>{{ content_lines[:5] | join('\n') }}</pre>
58+
</div>
59+
<div class="expand-indicator">
60+
▼ {{ content_lines|length - 5 }} more lines
61+
</div>
62+
</div>
63+
<div class="message-content-full clickable-message" style="display: none;" onclick="toggleMessageExpand(this)" title="Click to collapse">
64+
{% if message.role == 'assistant' and '```' in message.content %}
65+
<!-- Special handling for assistant messages with code blocks -->
66+
{% set parts = message.content.split('```') %}
67+
{% for part in parts %}
68+
{% if loop.index is odd %}
69+
<!-- Text part -->
70+
<div class="message-text"><pre>{{ part }}</pre></div>
71+
{% else %}
72+
<!-- Code part -->
73+
<div class="code-block">
74+
<pre><code>{{ part }}</code></pre>
75+
</div>
76+
{% endif %}
77+
{% endfor %}
78+
{% else %}
79+
<!-- Regular text content -->
80+
<div class="message-text">
81+
<pre>{{ message.content }}</pre>
82+
</div>
83+
{% endif %}
84+
<div class="collapse-indicator">
85+
▲ Click to collapse
86+
</div>
7087
</div>
7188
{% endif %}
7289
{% elif message.content is iterable %}
7390
<!-- Handle complex content (e.g., user messages with multiple parts) -->
74-
{% for content_part in message.content %}
75-
{% if content_part.type == 'text' %}
76-
<div class="message-text">
77-
<pre>{{ content_part.text }}</pre>
78-
</div>
79-
{% else %}
80-
<div class="message-part">
81-
<strong>{{ content_part.type.title() }}:</strong>
82-
<pre>{{ content_part | tojson(indent=2) }}</pre>
83-
</div>
84-
{% endif %}
85-
{% endfor %}
91+
<div class="message-content-full">
92+
{% for content_part in message.content %}
93+
{% if content_part.type == 'text' %}
94+
{% set content_lines = content_part.text.split('\n') %}
95+
{% if content_lines|length <= 5 %}
96+
<div class="message-text">
97+
<pre>{{ content_part.text }}</pre>
98+
</div>
99+
{% else %}
100+
<div class="message-preview-short clickable-message" onclick="toggleMessageExpand(this)" title="Click to expand ({{ content_lines|length - 5 }} more lines)">
101+
<div class="message-text">
102+
<pre>{{ content_lines[:5] | join('\n') }}</pre>
103+
</div>
104+
<div class="expand-indicator">
105+
▼ {{ content_lines|length - 5 }} more lines
106+
</div>
107+
</div>
108+
<div class="message-content-expanded clickable-message" style="display: none;" onclick="toggleMessageExpand(this)" title="Click to collapse">
109+
<div class="message-text">
110+
<pre>{{ content_part.text }}</pre>
111+
</div>
112+
<div class="collapse-indicator">
113+
▲ Click to collapse
114+
</div>
115+
</div>
116+
{% endif %}
117+
{% else %}
118+
<div class="message-part">
119+
<strong>{{ content_part.type.title() }}:</strong>
120+
<pre>{{ content_part | tojson(indent=2) }}</pre>
121+
</div>
122+
{% endif %}
123+
{% endfor %}
124+
</div>
86125
{% else %}
87126
<!-- Fallback for other content types -->
88-
<div class="message-text">
89-
<pre>{{ message.content | tojson(indent=2) }}</pre>
127+
<div class="message-content-full">
128+
<div class="message-text">
129+
<pre>{{ message.content | tojson(indent=2) }}</pre>
130+
</div>
90131
</div>
91132
{% endif %}
92133
</div>
93-
</details>
134+
</div>
94135
</div>
95136
{% endfor %}
96137
</div>

0 commit comments

Comments
 (0)