mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
feat(web): inline tool activity cards with auto-collapsing (#376)
* feat(web): inline tool activity cards with auto-collapsing Add Claude/Codex-style inline tool activity cards to the web UI that show tool execution progress directly in the chat conversation. While processing: - Animated thinking dots with message text (e.g. "Calling LLM...") - Individual tool cards with live spinner and elapsed timer - Cards show tool name, duration, and expandable output preview After response arrives: - Activity group auto-collapses to "Used N tools (Xs)" - Click summary to expand and see individual tool cards - Click card header to see tool output in monospace Also includes: - "Calling LLM..." thinking status from dispatcher (all channels) - 5-minute max timer guard to prevent leaks on dropped SSE - Handles parallel tools, same tool twice, failures, thread switching Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(web): use frozen duration for completed tools in activity summary The collapsed activity summary was showing inflated total duration because finalizeActivityGroup() recalculated elapsed time from Date.now() for already-completed tools. Now each tool card stores its final duration at completion time and the summary uses that frozen value instead. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
2477923af2
commit
443b120272
@@ -483,6 +483,7 @@ body {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
|
||||
.scroll-load-spinner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -502,6 +503,225 @@ body {
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
/* === Tool Activity Cards === */
|
||||
|
||||
.activity-group {
|
||||
align-self: flex-start;
|
||||
max-width: 80%;
|
||||
padding: 4px 0 4px 12px;
|
||||
border-left: 2px solid var(--border);
|
||||
margin: 4px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.activity-group.collapsed {
|
||||
border-left-color: transparent;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* Thinking indicator */
|
||||
|
||||
.activity-thinking {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.activity-thinking-dots {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.activity-thinking-dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-secondary);
|
||||
animation: thinkingPulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.activity-thinking-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.activity-thinking-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes thinkingPulse {
|
||||
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||
40% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
.activity-thinking-text {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Tool card */
|
||||
|
||||
.activity-tool-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.activity-tool-card[data-status="running"] {
|
||||
border-color: rgba(52, 211, 153, 0.3);
|
||||
}
|
||||
|
||||
.activity-tool-card[data-status="fail"] {
|
||||
border-color: rgba(230, 76, 76, 0.3);
|
||||
}
|
||||
|
||||
.activity-tool-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.activity-tool-header:hover {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.activity-tool-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.activity-tool-icon .spinner {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 2px solid var(--border);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
.activity-icon-success {
|
||||
color: var(--success);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.activity-icon-fail {
|
||||
color: var(--danger);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.activity-tool-name {
|
||||
font-size: 13px;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.activity-tool-duration {
|
||||
font-size: 11px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-secondary);
|
||||
min-width: 36px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.activity-tool-chevron {
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
transition: transform 0.15s ease;
|
||||
width: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.activity-tool-chevron.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.activity-tool-body {
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.activity-tool-output {
|
||||
margin: 0;
|
||||
padding: 8px 10px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--text-secondary);
|
||||
background: var(--code-bg);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Collapsed summary */
|
||||
|
||||
.activity-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--radius);
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.activity-summary:hover {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.activity-summary-chevron {
|
||||
font-size: 10px;
|
||||
transition: transform 0.15s ease;
|
||||
width: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.activity-summary-chevron.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.activity-summary-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.activity-summary-duration {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.activity-cards-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding-left: 12px;
|
||||
border-left: 2px solid var(--border);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.activity-group {
|
||||
max-width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Approval card (inline in chat) */
|
||||
.approval-card {
|
||||
align-self: flex-start;
|
||||
|
||||
Reference in New Issue
Block a user