From 5375d117922e0c40b6d0203418e2fd5f431fe268 Mon Sep 17 00:00:00 2001 From: rex-bot Date: Mon, 27 Apr 2026 14:17:04 +0000 Subject: [PATCH] CUB-48: Agent Status Badge component with pulse animations --- .../agent-status-badge.component.html | 8 + .../agent-status-badge.component.scss | 146 ++++++++++++++++++ .../agent-status-badge.component.ts | 54 +++++++ .../components/agent-status-badge/index.ts | 1 + frontend/src/app/components/index.ts | 1 + 5 files changed, 210 insertions(+) create mode 100644 frontend/src/app/components/agent-status-badge/agent-status-badge.component.html create mode 100644 frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss create mode 100644 frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts create mode 100644 frontend/src/app/components/agent-status-badge/index.ts create mode 100644 frontend/src/app/components/index.ts diff --git a/frontend/src/app/components/agent-status-badge/agent-status-badge.component.html b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.html new file mode 100644 index 0000000..e88ea86 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.html @@ -0,0 +1,8 @@ + + + {{ displayLabel }} + \ No newline at end of file diff --git a/frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss new file mode 100644 index 0000000..331d6f9 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.scss @@ -0,0 +1,146 @@ +// ============================================================================ +// Agent Status Badge — per spec Section 7.3 +// Colored pill with dot indicator and optional pulse animation. +// ============================================================================ + +$badge-height: 24px; +$dot-size: 8px; +$border-radius: 12px; +$font-size: 12px; +$font-weight: 500; +$padding-x: 8px; +$gap: 6px; + +@use 'sass:color'; + +// Status color palette +$color-active: #22c55e; // green-500 +$color-idle: #9ca3af; // gray-400 +$color-thinking: #3b82f6; // blue-500 +$color-error: #ef4444; // red-500 +$color-offline: #9ca3af; // gray-400 + +// Background tints (12% opacity for soft pill background) +$bg-active: rgba($color-active, 0.12); +$bg-idle: rgba($color-idle, 0.12); +$bg-thinking: rgba($color-thinking, 0.12); +$bg-error: rgba($color-error, 0.12); +$bg-offline: rgba($color-offline, 0.12); + +// --------------------------------------------------------------------------- +// Base pill +// --------------------------------------------------------------------------- +.badge { + display: inline-flex; + align-items: center; + height: $badge-height; + padding: 0 $padding-x; + border-radius: $border-radius; + gap: $gap; + font-size: $font-size; + font-weight: $font-weight; + line-height: 1; + white-space: nowrap; + user-select: none; +} + +// --------------------------------------------------------------------------- +// Dot indicator +// --------------------------------------------------------------------------- +.badge__dot { + width: $dot-size; + height: $dot-size; + border-radius: 50%; + flex-shrink: 0; +} + +// --------------------------------------------------------------------------- +// Label text +// --------------------------------------------------------------------------- +.badge__label { + line-height: 1; +} + +// --------------------------------------------------------------------------- +// Status color variants +// --------------------------------------------------------------------------- +.badge--active { + background: $bg-active; + color: color.adjust($color-active, $lightness: -10%); + + .badge__dot { + background: $color-active; + } +} + +.badge--idle { + background: $bg-idle; + color: color.adjust($color-idle, $lightness: -15%); + + .badge__dot { + background: $color-idle; + } +} + +.badge--thinking { + background: $bg-thinking; + color: color.adjust($color-thinking, $lightness: -10%); + + .badge__dot { + background: $color-thinking; + } +} + +.badge--error { + background: $bg-error; + color: color.adjust($color-error, $lightness: -10%); + + .badge__dot { + background: $color-error; + } +} + +.badge--offline { + background: $bg-offline; + color: color.adjust($color-offline, $lightness: -15%); + + .badge__dot { + background: $color-offline; + } +} + +// --------------------------------------------------------------------------- +// Pulse animation — applied when status is active, thinking, or error +// --------------------------------------------------------------------------- +.badge--pulse { + .badge__dot { + animation: pulse-dot 2s ease-in-out infinite; + } +} + +// Active: 2s pulse +.badge--active.badge--pulse .badge__dot { + animation-duration: 2s; +} + +// Thinking: 3s pulse +.badge--thinking.badge--pulse .badge__dot { + animation-duration: 3s; +} + +// Error: 0.8s pulse (fast, urgent) +.badge--error.badge--pulse .badge__dot { + animation-duration: 0.8s; +} + +@keyframes pulse-dot { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.4; + transform: scale(1.5); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts new file mode 100644 index 0000000..a246d63 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/agent-status-badge.component.ts @@ -0,0 +1,54 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { AgentStatus } from '../../models/agent.model'; + +/** + * Agent Status Badge component. + * Displays a colored pill with a pulse animation indicating the agent's current status. + * Per spec Section 7.3: Agent Card Component Interface — status indicator. + * + * Color mapping: + * - Active → green + * - Idle → gray + * - Thinking → blue + * - Error → red + * - Offline → gray (no pulse) + * + * Pulse animations: + * - Active → 2s + * - Error → 0.8s + * - Thinking → 3s + * - Idle / Offline → no pulse + */ +@Component({ + selector: 'app-agent-status-badge', + standalone: true, + imports: [], + templateUrl: './agent-status-badge.component.html', + styleUrl: './agent-status-badge.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AgentStatusBadgeComponent { + /** Current agent status — binds to the AgentStatus type from the model. */ + readonly status = input.required(); + + /** Label text shown inside the badge. Defaults to title-cased status. */ + readonly label = input(); + + get displayLabel(): string { + return this.label() ?? this.titleCase(this.status()); + } + + /** CSS class driven by the current status value. */ + get statusClass(): string { + return `badge--${this.status()}`; + } + + /** Whether the pulse animation should be active for the current status. */ + get hasPulse(): boolean { + return this.status() === 'active' || this.status() === 'thinking' || this.status() === 'error'; + } + + private titleCase(value: string): string { + return value.charAt(0).toUpperCase() + value.slice(1); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/agent-status-badge/index.ts b/frontend/src/app/components/agent-status-badge/index.ts new file mode 100644 index 0000000..e531d31 --- /dev/null +++ b/frontend/src/app/components/agent-status-badge/index.ts @@ -0,0 +1 @@ +export { AgentStatusBadgeComponent } from './agent-status-badge.component'; \ No newline at end of file diff --git a/frontend/src/app/components/index.ts b/frontend/src/app/components/index.ts new file mode 100644 index 0000000..09c8fb9 --- /dev/null +++ b/frontend/src/app/components/index.ts @@ -0,0 +1 @@ +export { AgentStatusBadgeComponent } from './agent-status-badge/agent-status-badge.component'; \ No newline at end of file