diff --git a/python/extensions/agent_init/_10_initial_message.py b/python/extensions/agent_init/_10_initial_message.py index f64a3fce44..65b5010fdb 100644 --- a/python/extensions/agent_init/_10_initial_message.py +++ b/python/extensions/agent_init/_10_initial_message.py @@ -35,7 +35,6 @@ async def execute(self, **kwargs): # Add to log (green bubble) for immediate UI display self.agent.context.log.log( type="response", - heading=f"{self.agent.agent_name}: Welcome", content=initial_message_text, finished=True, update_progress="none", diff --git a/python/extensions/before_main_llm_call/_10_log_for_stream.py b/python/extensions/before_main_llm_call/_10_log_for_stream.py index 49b04b7b7b..6618a0a47f 100644 --- a/python/extensions/before_main_llm_call/_10_log_for_stream.py +++ b/python/extensions/before_main_llm_call/_10_log_for_stream.py @@ -19,8 +19,10 @@ async def execute(self, loop_data: LoopData = LoopData(), text: str = "", **kwar ) ) -def build_heading(agent, text: str): - return f"icon://network_intelligence {agent.agent_name}: {text}" +def build_heading(agent, text: str, icon: str = "network_intelligence"): + # Include agent identifier for all agents (A0:, A1:, A2:, etc.) + agent_prefix = f"{agent.agent_name}: " + return f"icon://{icon} {agent_prefix}{text}" def build_default_heading(agent): return build_heading(agent, "Generating...") \ No newline at end of file diff --git a/python/extensions/response_stream/_10_log_from_stream.py b/python/extensions/response_stream/_10_log_from_stream.py index ace6baf547..375b96c032 100644 --- a/python/extensions/response_stream/_10_log_from_stream.py +++ b/python/extensions/response_stream/_10_log_from_stream.py @@ -22,12 +22,9 @@ async def execute( if "headline" in parsed: heading = build_heading(self.agent, parsed['headline']) elif "tool_name" in parsed: - heading = build_heading(self.agent, f"Using tool {parsed['tool_name']}") # if the llm skipped headline + heading = build_heading(self.agent, f"Using {parsed['tool_name']}") # if the llm skipped headline elif "thoughts" in parsed: - # thought length indicator - thoughts = "\n".join(parsed["thoughts"]) - pipes = "|" * math.ceil(math.sqrt(len(thoughts))) - heading = build_heading(self.agent, f"Thinking... {pipes}") + heading = build_default_heading(self.agent) # create log message and store it in loop data temporary params if "log_item_generating" not in loop_data.params_temporary: diff --git a/python/helpers/log.py b/python/helpers/log.py index a799666588..d231810d25 100644 --- a/python/helpers/log.py +++ b/python/helpers/log.py @@ -1,5 +1,6 @@ from dataclasses import dataclass, field import json +import time from typing import Any, Literal, Optional, Dict, TypeVar, TYPE_CHECKING T = TypeVar("T") @@ -131,9 +132,13 @@ class LogItem: kvps: Optional[OrderedDict] = None # Use OrderedDict for kvps id: Optional[str] = None # Add id field guid: str = "" + timestamp: float = 0.0 + duration_ms: Optional[int] = None + agent_number: int = 0 def __post_init__(self): self.guid = self.log.guid + self.timestamp = self.timestamp or time.time() def update( self, @@ -181,6 +186,9 @@ def output(self): "content": self.content, "temp": self.temp, "kvps": self.kvps, + "timestamp": self.timestamp, + "duration_ms": self.duration_ms, + "agent_number": self.agent_number, } @@ -206,11 +214,22 @@ def log( ) -> LogItem: # add a minimal item to the log + # Determine agent number from streaming agent + agent_number = 0 + if self.context and self.context.streaming_agent: + agent_number = self.context.streaming_agent.number + item = LogItem( log=self, no=len(self.logs), type=type, + agent_number=agent_number, ) + # Set duration on previous item and mark it as updated + if self.logs: + prev = self.logs[-1] + prev.duration_ms = int((item.timestamp - prev.timestamp) * 1000) + self.updates += [prev.no] self.logs.append(item) # and update it (to have just one implementation) diff --git a/python/helpers/persist_chat.py b/python/helpers/persist_chat.py index 55867e6fe5..9e4c20b6c7 100644 --- a/python/helpers/persist_chat.py +++ b/python/helpers/persist_chat.py @@ -262,17 +262,19 @@ def _deserialize_log(data: dict[str, Any]) -> "Log": # Deserialize the list of LogItem objects i = 0 for item_data in data.get("logs", []): - log.logs.append( - LogItem( - log=log, # restore the log reference - no=i, # item_data["no"], - type=item_data["type"], - heading=item_data.get("heading", ""), - content=item_data.get("content", ""), - kvps=OrderedDict(item_data["kvps"]) if item_data["kvps"] else None, - temp=item_data.get("temp", False), - ) - ) + log.logs.append(LogItem( + log=log, # restore the log reference + no=i, # item_data["no"], + type=item_data["type"], + heading=item_data.get("heading", ""), + content=item_data.get("content", ""), + kvps=OrderedDict(item_data["kvps"]) if item_data["kvps"] else None, + temp=item_data.get("temp", False), + # Pass metrics directly to constructor + timestamp=item_data.get("timestamp", 0.0), + duration_ms=item_data.get("duration_ms"), + agent_number=item_data.get("agent_number", 0), + )) log.updates.append(i) i += 1 diff --git a/webui/components/messages/process-group/process-group-store.js b/webui/components/messages/process-group/process-group-store.js new file mode 100644 index 0000000000..89a2f77ecc --- /dev/null +++ b/webui/components/messages/process-group/process-group-store.js @@ -0,0 +1,181 @@ +import { createStore } from "/js/AlpineStore.js"; + +// Process Group Store - manages collapsible process groups in chat + +// Unified mapping for both Tool Names and Step Types +// Specific tool names (keys) take precedence over generic types +const DISPLAY_CODES = { + // --- Specific Tools --- + 'call_subordinate': 'SUB', + 'search_engine': 'WEB', + 'a2a_chat': 'A2A', + 'behaviour_adjustment': 'ADJ', + 'document_query': 'DOC', + 'vision_load': 'EYE', + 'notify_user': 'NTF', + 'scheduler': 'SCH', + 'unknown': 'UNK', + // Memory operations group + 'memory_save': 'MEM', + 'memory_load': 'MEM', + 'memory_forget': 'MEM', + 'memory_delete': 'MEM', + + // --- Step Types --- + 'agent': 'GEN', + 'response': 'END', + 'tool': 'USE', // Generic fallback for tools + 'code_exe': 'EXE', + 'browser': 'BRW', + 'progress': 'HLD', + 'subagent': 'SUB', // Type fallback if tool name missing + 'mcp': 'MCP', + 'info': 'INF', + 'hint': 'HNT', + 'warning': 'WRN', + 'error': 'ERR', + 'util': 'UTL', + 'done': 'END' +}; + +const model = { + // Track which process groups are expanded (by group ID) + expandedGroups: {}, + + // Track which individual steps are expanded within a group + expandedSteps: {}, + + // Default collapsed state for new process groups + defaultCollapsed: true, + + init() { + try { + // Load persisted state + const stored = localStorage.getItem("processGroupState"); + if (stored) { + const parsed = JSON.parse(stored); + this.expandedGroups = parsed.expandedGroups || {}; + this.expandedSteps = parsed.expandedSteps || {}; + this.defaultCollapsed = parsed.defaultCollapsed ?? true; + } + } catch (e) { + console.error("Failed to load process group state", e); + } + }, + + _persist() { + try { + localStorage.setItem("processGroupState", JSON.stringify({ + expandedGroups: this.expandedGroups, + expandedSteps: this.expandedSteps, + defaultCollapsed: this.defaultCollapsed + })); + } catch (e) { + console.error("Failed to persist process group state", e); + } + }, + + // Check if a process group is expanded + isGroupExpanded(groupId) { + if (groupId in this.expandedGroups) { + return this.expandedGroups[groupId]; + } + return !this.defaultCollapsed; + }, + + // Toggle process group expansion + toggleGroup(groupId) { + const current = this.isGroupExpanded(groupId); + this.expandedGroups[groupId] = !current; + this._persist(); + }, + + // Expand a specific group + expandGroup(groupId) { + this.expandedGroups[groupId] = true; + this._persist(); + }, + + // Collapse a specific group + collapseGroup(groupId) { + this.expandedGroups[groupId] = false; + this._persist(); + }, + + // Check if a step within a group is expanded + isStepExpanded(groupId, stepId) { + const key = `${groupId}:${stepId}`; + return this.expandedSteps[key] || false; + }, + + // Toggle step expansion + toggleStep(groupId, stepId) { + const key = `${groupId}:${stepId}`; + const currentState = this.expandedSteps[key] || false; + this.expandedSteps[key] = !currentState; + this._persist(); + }, + + // Status code (3-4 letter) for backend log types + // Looks up tool name first (specific), then falls back to type (generic) + getStepCode(type, toolName = null) { + // Specific tool codes only apply to generic 'tool' steps + if (type === 'tool' && toolName && DISPLAY_CODES[toolName]) { + return DISPLAY_CODES[toolName]; + } + + return DISPLAY_CODES[type] || + type?.toUpperCase()?.slice(0, 4) || + 'GEN'; + }, + + // CSS color class for backend log types + // Looks up tool name first (specific), then falls back to type (generic) + getStatusColorClass(type, toolName = null) { + // Specific tool name mappings for 'tool' steps + if (type === 'tool' && toolName) { + // call_subordinate gets teal (SUB color) + if (toolName === 'call_subordinate') { + return 'status-sub'; + } + // Add other specific tool mappings here if needed in the future + } + + const colors = { + 'agent': 'status-gen', + 'response': 'status-end', + 'tool': 'status-tool', + 'mcp': 'status-mcp', + 'subagent': 'status-sub', + 'code_exe': 'status-exe', + 'browser': 'status-brw', + 'progress': 'status-wait', + 'info': 'status-inf', + 'hint': 'status-hnt', + 'warning': 'status-wrn', + 'error': 'status-err', + 'util': 'status-utl', + 'done': 'status-end' + }; + return colors[type] || 'status-gen'; + }, + + // Clear state for a specific context (when chat is reset) + clearContext(contextPrefix) { + // Clear groups matching the context + for (const key of Object.keys(this.expandedGroups)) { + if (key.startsWith(contextPrefix)) { + delete this.expandedGroups[key]; + } + } + // Clear steps matching the context + for (const key of Object.keys(this.expandedSteps)) { + if (key.startsWith(contextPrefix)) { + delete this.expandedSteps[key]; + } + } + this._persist(); + } +}; + +export const store = createStore("processGroup", model); diff --git a/webui/components/messages/process-group/process-group.css b/webui/components/messages/process-group/process-group.css new file mode 100644 index 0000000000..ba3641998b --- /dev/null +++ b/webui/components/messages/process-group/process-group.css @@ -0,0 +1,894 @@ +.process-group { + display: inline-flex; + flex-direction: column; + position: relative; + z-index: 1; + margin: var(--spacing-xs) 0; + padding: var(--spacing-xs) 0; + min-width: 200px; + max-width: 100%; + box-sizing: border-box; + flex-shrink: 0; +} + +/* Embedded Process Group inside Response */ +.process-group.embedded { + display: flex; + flex-direction: column; + width: 100%; + margin: 0; + border-radius: var(--border-radius) var(--border-radius) 0 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: transparent; +} + +/* Message container with embedded process group */ +.message-container.has-process-group { + display: inline-flex; + flex-direction: column; + padding: 0; + overflow: hidden; + min-width: 200px; + max-width: 100%; +} + +.message-container.has-process-group > .message { + border: none; + background: transparent; + margin: 0; +} + +/* Process Group Header */ +.process-group-header { + display: flex; + align-items: center; + padding: 0; + cursor: pointer; + user-select: none; + transition: opacity 0.15s ease; + min-height: 22px; + gap: 6px; + white-space: nowrap; +} + +.process-group-header:hover { + opacity: 0.85; +} + +/* Expand/collapse triangle - CSS-only (no Material Icons) */ +.process-group-header .expand-icon { + display: inline-block; + width: 0; + height: 0; + border-style: solid; + border-width: 4px 0 4px 6px; + border-color: transparent transparent transparent var(--color-text); + opacity: 0.5; + transition: transform 0.2s ease, opacity 0.15s ease; + flex-shrink: 0; + font-size: 0; /* Hide any text content */ + margin-right: 2px; +} + +.process-group-header:hover .expand-icon { + opacity: 0.8; +} + +.process-group.expanded .process-group-header .expand-icon { + transform: rotate(90deg); +} + +/* Group icon removed - using status badges */ +.process-group-header .group-icon { + display: none; +} + +/* Group title - prominent display */ +.process-group-header .group-title { + flex: 0 1 auto; + font-size: var(--font-size-smaller); + font-weight: 500; + color: var(--color-text); + opacity: 0.9; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 350px; +} + +/* Step count badge */ +.process-group-header .step-count { + font-size: 0.65rem; + color: var(--color-text); + opacity: 0.5; + flex-shrink: 0; + padding: 1px 6px; + font-family: var(--font-family-code); +} + +/* =========================================== + 3-Letter Status Code Badges + =========================================== */ + +/* Base status badge */ +.status-badge { + display: inline-flex; + align-items: center; + gap: 3px; + padding: 2px 6px; + border-radius: 3px; + font-size: 0.65rem; + font-weight: 600; + font-family: var(--font-family-code); + text-transform: uppercase; + letter-spacing: 0.5px; + flex-shrink: 0; + line-height: 1; +} + +/* Status badge with icon */ +.status-badge .material-symbols-outlined { + font-size: 0.8rem; + line-height: 1; +} + +/* Badge icon styling */ +.status-badge .badge-icon { + font-size: 0.7rem; + margin-right: 2px; + opacity: 0.9; +} + +/* Status colors - mapped from backend types via store */ +/* Each status defines --step-accent for cascading to internal icons */ + +/* GEN - agent type (blue/cyan) */ +.status-gen { + --step-accent: #38bdf8; + color: var(--step-accent); +} + +/* END - response/done type (green) */ +.status-end { + --step-accent: #22c55e; + color: var(--step-accent); +} + +/* USE - tool usage (amber/yellow) */ +.status-tool { + --step-accent: #fbbf24; + color: var(--step-accent); +} + +/* MCP - mcp type (amber/yellow) */ +.status-mcp { + --step-accent: #fbbf24; + color: var(--step-accent); +} + +/* SUB - subagent type (teal) */ +.status-sub { + --step-accent: #14b8a6; + color: var(--step-accent); +} + +/* EXE - code_exe type (magenta/purple) */ +.status-exe { + --step-accent: #ba68c8; + color: var(--step-accent); +} + +/* BRW - browser type (indigo) */ +.status-brw { + --step-accent: #818cf8; + color: var(--step-accent); +} + +/* WAIT - progress type (slate) */ +.status-wait { + --step-accent: #94a3b8; + color: var(--step-accent); +} + +/* INF - info type (gray) */ +.status-inf { + --step-accent: #94a3b8; + color: var(--step-accent); +} + +/* HNT - hint type (yellow-green) */ +.status-hnt { + --step-accent: #a3e635; + color: var(--step-accent); +} + +/* WRN - warning type (orange) */ +.status-wrn { + --step-accent: #f97316; + color: var(--step-accent); +} + +/* ERR - error type (red) */ +.status-err { + --step-accent: #ef4444; + color: var(--step-accent); +} + +/* UTL - util type (gray-blue) */ +.status-utl { + --step-accent: #64748b; + color: var(--step-accent); +} + +/* USR - user type (sky) */ +.status-usr { + --step-accent: #38bdf8; + color: var(--step-accent); +} + +/* Animated spinner for active status (CSS-only, no Material Icons) */ +.status-badge.status-active::after { + content: ""; + display: inline-block; + width: 8px; + height: 8px; + border: 1.5px solid currentColor; + border-top-color: transparent; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin-left: 4px; + flex-shrink: 0; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* Don't show spinner pseudo-element on END/ERR (they have their own indicators) */ +.status-badge.status-end::after, +.status-badge.status-err::after { + display: none; +} + +/* END status with checkmark icon (icon added via JS, no ::before needed) */ + +/* Completed process group styling */ +.process-group-completed { + opacity: 0.95; +} + +.process-group-completed .group-status { + font-weight: 700; +} + +/* Subtle completion indicator on the expand icon */ +.process-group-completed .expand-icon { + color: #22c55e; +} + +/* Group status badge in header */ +.process-group-header .group-status { + margin-left: var(--spacing-xs); +} + +/* Metrics row */ +.process-group-header .group-metrics { + display: flex; + align-items: center; + gap: var(--spacing-sm); + margin-left: auto; + font-size: 0.65rem; + font-family: var(--font-family-code); + color: var(--color-text); + opacity: 0.8; +} + +.process-group-header .group-metrics .material-symbols-outlined { + font-size: 0.75rem; + opacity: 0.7; +} + +.process-group-header .group-metrics span[class^="metric-"] { + display: inline-flex; + align-items: center; + gap: 2px; +} + +/* Legacy timestamp/duration (for backwards compatibility) */ +.process-group-header .group-timestamp { + font-size: 0.65rem; + color: rgba(255, 255, 255, 0.65); + flex-shrink: 0; + font-family: var(--font-family-code); + display: none; /* Hidden, replaced by metrics */ +} + +.process-group-header .group-duration { + font-size: 0.7rem; + color: #81c784; + flex-shrink: 0; + font-family: var(--font-family-code); + min-width: 40px; + text-align: right; + display: none; /* Hidden, replaced by metrics */ +} + +/* Process Group Content - Animated expand/collapse */ +.process-group-content { + display: grid; + grid-template-rows: 0fr; + opacity: 0; + margin-top: 0; + padding-top: 0; + border-top: 1px solid transparent; + transition: grid-template-rows 0.25s ease-out, + opacity 0.2s ease-out, + margin-top 0.25s ease-out, + padding-top 0.25s ease-out, + border-color 0.2s ease-out; + overflow: hidden; +} + +.process-group-content > .process-steps { + min-height: 0; +} + +.process-group.expanded .process-group-content { + grid-template-rows: 1fr; + opacity: 1; + margin-top: var(--spacing-xs); + padding-top: var(--spacing-xs); + border-top-color: rgba(255, 255, 255, 0.06); +} + +/* Process Steps List */ +.process-steps { + padding: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +/* Individual Process Step */ +.process-step { + display: flex; + flex-direction: column; + padding: 4px 0; + transition: background-color 0.15s ease; +} + +.step-expanded { + padding: var(--spacing-xs) 0; +} + +/* Utility/Info/Hint steps have subtle background tint */ +.process-step[data-type="util"], +.process-step[data-type="info"], +.process-step[data-type="hint"] { + background-color: rgba(120, 115, 100, 0); +} + +.process-step[data-type="util"]:hover, +.process-step[data-type="info"]:hover, +.process-step[data-type="hint"]:hover { + background-color: rgba(255, 255, 255, 0.03); +} + + +.light-mode .process-step[data-type="util"]:hover, +.light-mode .process-step[data-type="info"]:hover, +.light-mode .process-step[data-type="hint"]:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +/* Step Header (clickable) */ +.process-step-header { + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + gap: 4px; + min-height: 18px; +} + +/* Step icon removed - using status badges instead */ +.process-step-header .step-icon { + display: none; +} + +/* Step type label removed - using status badges instead */ +.process-step-header .step-type { + display: none; +} + +/* Step title */ +.process-step-header .step-title { + flex: 1; + font-size: 0.8rem; + color: var(--color-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-left: var(--spacing-xs); +} + +/* Step expand icon - CSS triangle (no Material Icons) */ +.process-step-header .step-expand-icon { + display: inline-block; + width: 0; + height: 0; + border-style: solid; + border-width: 3px 0 3px 5px; + border-color: transparent transparent transparent var(--color-text); + opacity: 0.4; + transition: transform 0.2s ease, opacity 0.15s ease; + flex-shrink: 0; + font-size: 0; /* Hide any text content */ + margin-right: 2px; +} + +.process-step-header:hover .step-expand-icon { + opacity: 0.7; +} + +/* Use direct child selector to prevent affecting nested steps */ +.process-step.step-expanded > .process-step-header > .step-expand-icon { + transform: rotate(90deg); +} + +/* Step Detail Content - Animated expand/collapse */ +.process-step-detail { + display: grid; + grid-template-rows: 0fr; + opacity: 0; + transition: grid-template-rows 0.2s ease-out, opacity 0.15s ease-out; + overflow: hidden; + -webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */ + scroll-behavior: smooth; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ + overscroll-behavior: contain; /* avoid scroll chaining */ +} + /* Hide scrollbar for Chrome/Safari/Webkit */ +.process-step-detail::-webkit-scrollbar { + display: none; + width: 0; + height: 0; +} + +.process-step-detail-content::-webkit-scrollbar { + display: none; + width: 0; + height: 0; +} + +.process-step-detail > .process-step-detail-content { + min-height: 0; +} + +/* Use direct child selector (>) to prevent cascading to nested steps */ +.process-step.step-expanded > .process-step-detail { + grid-template-rows: 1fr; + opacity: 1; + overflow: visible; + margin-top: var(--spacing-xs); +} + +.process-step-detail-content { + padding: var(--spacing-xs) 0; + margin-top: var(--spacing-xxs); + margin-left: 28px; /* Align with icon */ + background-color: transparent; + font-size: 0.7rem; + line-height: 1.5; + max-height: 300px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */ + scroll-behavior: smooth; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ + overscroll-behavior: contain; /* avoid scroll chaining */ +} + +.process-step-detail-content pre { + margin: 0; + white-space: pre-wrap; + word-break: break-word; + font-family: var(--font-family-code); + font-size: 0.7rem; + color: var(--color-text); + opacity: 0.8; +} + +/* KVPs in step detail - CSS Grid for aligned columns */ +.process-step-detail-content .step-kvps { + display: grid; + grid-template-columns: auto 1fr; + gap: var(--spacing-xs) var(--spacing-sm); + align-items: start; +} + +.process-step-detail-content .step-kvp { + display: contents; /* Children participate in parent grid */ +} + +.process-step-detail-content .step-kvp-key { + color: var(--step-accent, var(--color-primary)); + font-weight: 500; + opacity: 0.8; + display: flex; + align-items: center; + justify-content: end; +} + +.process-step-detail-content .step-kvp-key .material-symbols-outlined { + font-size: 0.9rem; + color: var(--step-accent, var(--color-primary)) !important; +} + +.process-step-detail-content .step-kvp-value { + color: var(--color-text); + opacity: 0.85; + word-break: break-word; + white-space: pre-wrap; + font-size: 0.75rem; + line-height: 1.5; +} + +/* Thoughts styling - single icon with plain text */ +.process-step-detail-content .step-thoughts { + display: flex; + align-items: flex-start; + gap: var(--spacing-sm); + margin: var(--spacing-xs) 0; +} + +.process-step-detail-content .thought-icon { + font-size: 0.85rem; + color: var(--step-accent, #38bdf8); + flex-shrink: 0; + margin-top: 2px; +} + +.process-step-detail-content .thought-text { + font-size: 0.7rem; + font-weight: 300; + color: var(--color-text); + opacity: 0.65; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; +} + +/* Light mode thoughts - uses step accent if available */ +.light-mode .process-step-detail-content .thought-icon { + color: var(--step-accent, #b45309); +} + +/* Tool arguments - CSS Grid for aligned columns */ +.process-step-detail-content .step-tool-args { + display: grid; + grid-template-columns: auto 1fr; + gap: 4px 8px; + margin: var(--spacing-xs) 0; + align-items: baseline; +} + +.process-step-detail-content .tool-arg-row { + display: contents; /* Children participate in parent grid */ +} + +.process-step-detail-content .tool-arg-label { + font-size: 0.68rem; + font-weight: 600; + color: var(--step-accent, var(--color-primary)); + display: flex; + align-items: center; + justify-content: end; +} + +.process-step-detail-content .tool-arg-label .material-symbols-outlined { + font-size: 0.9rem; + opacity: 0.85; + color: var(--step-accent, var(--color-primary)); +} + +.process-step-detail-content .tool-arg-value { + font-size: 0.7rem; + color: var(--color-text); + opacity: 0.85; + word-break: break-word; + font-family: var(--font-family-code); +} + +/* Light mode tool args - transparent like dark mode */ +.light-mode .process-step-detail-content .step-tool-args { + background: transparent; + border-left-color: transparent; +} + +.process-step-detail-content .step-tool-header .tool-icon { + font-size: 0.85rem; + color: var(--step-accent, var(--color-warning)); + opacity: 0.85; +} + +.process-step-detail-content .step-tool-header .tool-label { + font-size: 0.68rem; + font-weight: 600; + color: var(--step-accent, var(--color-warning)); + opacity: 0.85; +} + +.process-step-detail-content .step-tool-header .tool-name { + font-size: 0.72rem; + color: var(--color-text); + opacity: 0.85; +} + +/* Terminal-style output for code_exe */ +.process-step-detail-content .step-terminal { + margin: var(--spacing-xs) 0; + font-family: var(--font-family-code); + font-size: 0.72rem; +} + +.process-step-detail-content .terminal-output { + margin: var(--spacing-xs) 0 0 0; + padding: var(--spacing-xs); + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + color: #c9d1d9; + white-space: pre-wrap; + word-break: break-word; + max-height: 200px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */ + scroll-behavior: smooth; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ + overscroll-behavior: contain; /* avoid scroll chaining */ +} + +.process-step-detail-content .terminal-output::-webkit-scrollbar { + display: none; + width: 0; + height: 0; +} + +/* Light mode terminal */ +.light-mode .process-step-detail-content .terminal-output { + background: rgba(0, 0, 0, 0.05); + color: #24292f; +} + +/* Light mode adjustments */ +.light-mode .process-group.expanded .process-group-content { + border-top-color: rgba(0, 0, 0, 0.06); +} + +.light-mode .process-step-detail-content { + background-color: transparent; +} + +/* Light mode text colors for process group */ +.light-mode .process-group-header .group-title, +.light-mode .process-step-header .step-title { + color: var(--color-text); +} + +/* Light mode status badge adjustments for better contrast */ +/* Override --step-accent for cascading to internal icons */ +.light-mode .status-gen { + --step-accent: #0284c7; + color: var(--step-accent); +} + +.light-mode .status-end { + --step-accent: #15803d; + color: var(--step-accent); +} + +.light-mode .status-tool { + --step-accent: #b45309; + color: var(--step-accent); +} + +.light-mode .status-mcp { + --step-accent: #b45309; + color: var(--step-accent); +} + +.light-mode .status-sub { + --step-accent: #0f766e; + color: var(--step-accent); +} + +.light-mode .status-exe { + --step-accent: #7c3aed; + color: var(--step-accent); +} + +.light-mode .status-brw { + --step-accent: #4f46e5; + color: var(--step-accent); +} + +.light-mode .status-wait { + --step-accent: #475569; + color: var(--step-accent); +} + +.light-mode .status-inf { + --step-accent: #475569; + color: var(--step-accent); +} + +.light-mode .status-hnt { + --step-accent: #65a30d; + color: var(--step-accent); +} + +.light-mode .status-wrn { + --step-accent: #c2410c; + color: var(--step-accent); +} + +.light-mode .status-err { + --step-accent: #b91c1c; + color: var(--step-accent); +} + +.light-mode .status-utl { + --step-accent: #334155; + color: var(--step-accent); +} + +.light-mode .status-usr { + --step-accent: #0284c7; + color: var(--step-accent); +} + +/* Animation for loading state */ +@keyframes pulse-step { + 0%, 100% { opacity: 0.5; } + 50% { opacity: 0.8; } +} + +.process-step.loading .step-icon { + animation: pulse-step 1.2s ease-in-out infinite; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .process-group { + padding: var(--spacing-xs) var(--spacing-sm); + } + + .process-step-header .step-type { + display: none; + } + + .process-step-header .step-title { + font-size: 0.7rem; + } + + .process-step-detail-content { + margin-left: 16px; + } +} + +/* =========================================== + Preferences Visibility Controls + These rules work with preferences-store toggles + =========================================== */ + +/* Utility steps - default hidden (controlled by showUtils) */ +.process-step.message-util { + display: none; +} + +.process-step.message-util.show-util { + display: flex; +} + +/* Thoughts KVP row */ +.step-kvp.msg-thoughts { + display: none; +} + +/* JSON pre content - default hidden */ +.process-step-detail-content pre.msg-json { + display: none; +} + +/* Nested Process Steps (Subordinate Agents) */ + +.process-nested-container { + display: grid; + grid-template-rows: 0fr; + opacity: 0; + overflow: hidden; + transition: grid-template-rows 0.25s ease-out, opacity 0.2s ease-out; + gap: 2px; +} + +/* Only show nested container when parent step is expanded */ +.process-step.step-expanded > .process-nested-container { + grid-template-rows: 1fr; + opacity: 1; + margin-top: 2px; + /* Keep overflow hidden to allow nested steps to control their own expansion */ +} + +/* Inner wrapper to handle grid transition content */ +.process-nested-inner { + min-height: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +/* All nested containers have same indentation (no cascading) */ +.nested-step .process-nested-container { + margin-left: 0; +} + +/* Response content in process steps */ +.process-step-detail-content .step-response-content { + font-size: 0.75rem; + line-height: 1.6; + color: var(--color-text); + opacity: 0.9; + margin: var(--spacing-xs) 0; +} + +.process-step-detail-content .step-response-content p { + margin: 0.5em 0; +} + +.process-step-detail-content .step-response-content ul, +.process-step-detail-content .step-response-content ol { + margin: 0.5em 0; + padding-left: 1.5em; +} + +/* Warning/error content in process steps */ +.process-step-detail-content .step-warning-content { + font-size: 0.72rem; + line-height: 1.5; + color: var(--color-warning); + opacity: 0.9; + margin: var(--spacing-xs) 0; + padding: var(--spacing-xs); + background: rgba(255, 165, 0, 0.08); + border-left: 2px solid var(--color-warning); + border-radius: 3px; +} + +/* Browser screenshot content in process steps */ +.process-step-detail-content .screenshot-img { + max-width: 100%; + max-height: 400px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease, box-shadow 0.2s ease; + object-fit: contain; +} + +.process-step-detail-content .screenshot-img:hover { + transform: scale(1.02); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +/* Light mode screenshot border */ +.light-mode .process-step-detail-content .screenshot-img { + border: 1px solid rgba(0, 0, 0, 0.15); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.light-mode .process-step-detail-content .screenshot-img:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} diff --git a/webui/components/sidebar/bottom/preferences/preferences-panel.html b/webui/components/sidebar/bottom/preferences/preferences-panel.html index 516585f76d..a03bcf9832 100644 --- a/webui/components/sidebar/bottom/preferences/preferences-panel.html +++ b/webui/components/sidebar/bottom/preferences/preferences-panel.html @@ -41,20 +41,6 @@ -
  • - Show thoughts - -
  • -
  • - Show JSON - -
  • Show utility messages