127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
import {
|
||
ChangeDetectionStrategy,
|
||
Component,
|
||
Input,
|
||
computed,
|
||
} from '@angular/core';
|
||
import { CommonModule } from '@angular/common';
|
||
import { RouterModule } from '@angular/router';
|
||
import { MatIconModule } from '@angular/material/icon';
|
||
import { MatButtonModule } from '@angular/material/button';
|
||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||
import { AgentStatus } from '../../../models/agent.model';
|
||
|
||
// ============================================================================
|
||
// AgentCard Component
|
||
// Per spec Section 7.3: Composes Agent Status Badge, Task Progress Bar,
|
||
// and Quick‑Jump Button into a card with left‑border status accent.
|
||
// ============================================================================
|
||
|
||
@Component({
|
||
selector: 'app-agent-card',
|
||
standalone: true,
|
||
imports: [
|
||
CommonModule,
|
||
RouterModule,
|
||
MatIconModule,
|
||
MatButtonModule,
|
||
MatProgressBarModule,
|
||
MatTooltipModule,
|
||
],
|
||
templateUrl: './agent-card.component.html',
|
||
styleUrl: './agent-card.component.scss',
|
||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||
})
|
||
export class AgentCardComponent {
|
||
// --- Six required inputs per spec ---
|
||
|
||
/** Agent status — drives badge color and left‑border accent */
|
||
@Input({ required: true }) status!: AgentStatus;
|
||
|
||
/** Current task description, e.g. "Reviewing PR #42" */
|
||
@Input() task = '';
|
||
|
||
/** Task progress percentage 0–100 */
|
||
@Input() progress = 0;
|
||
|
||
/** Full session key for quick‑jump navigation */
|
||
@Input({ required: true }) sessionKey = '';
|
||
|
||
/** Communication channel, e.g. "telegram" */
|
||
@Input({ required: true }) channel = '';
|
||
|
||
/** Timestamp of last agent activity */
|
||
@Input({ required: true }) lastActivity!: Date;
|
||
|
||
// --- Additional display inputs ---
|
||
|
||
/** Short agent ID, e.g. "otto" */
|
||
@Input() agentId = '';
|
||
|
||
/** Display name, e.g. "Otto" */
|
||
@Input() displayName = '';
|
||
|
||
/** Role description, e.g. "Orchestrator Agent" */
|
||
@Input() role = '';
|
||
|
||
/** Error message (shown only when status is 'error') */
|
||
@Input() errorMessage = '';
|
||
|
||
// --- Computed values ---
|
||
|
||
/** Map status → CSS custom property for the left‑border accent */
|
||
readonly statusBorderColor = computed(() => {
|
||
const map: Record<AgentStatus, string> = {
|
||
active: 'var(--status-active)',
|
||
idle: 'var(--status-idle)',
|
||
thinking: 'var(--status-thinking)',
|
||
error: 'var(--status-error)',
|
||
offline: 'var(--status-offline)',
|
||
};
|
||
return map[this.status] ?? 'var(--status-offline)';
|
||
});
|
||
|
||
/** Human‑readable status label */
|
||
readonly statusLabel = computed(() => {
|
||
const labels: Record<AgentStatus, string> = {
|
||
active: 'Active',
|
||
idle: 'Idle',
|
||
thinking: 'Thinking…',
|
||
error: 'Error',
|
||
offline: 'Offline',
|
||
};
|
||
return labels[this.status] ?? this.status;
|
||
});
|
||
|
||
/** CSS class suffix for the status badge dot */
|
||
readonly statusDotClass = computed(() => `status-dot--${this.status}`);
|
||
|
||
/** Material icon name for the channel */
|
||
readonly channelIcon = computed(() => {
|
||
const icons: Record<string, string> = {
|
||
telegram: 'telegram', // falls back to font icon if no SVG registered
|
||
slack: 'chat',
|
||
discord: 'forum',
|
||
whatsapp: 'chat',
|
||
webchat: 'language',
|
||
email: 'email',
|
||
};
|
||
return icons[this.channel] ?? 'chat';
|
||
});
|
||
|
||
/** Relative time string for lastActivity */
|
||
readonly lastActivityLabel = computed(() => {
|
||
if (!this.lastActivity) return '';
|
||
const now = Date.now();
|
||
const then = this.lastActivity.getTime();
|
||
const diffSec = Math.max(0, Math.floor((now - then) / 1000));
|
||
if (diffSec < 60) return 'just now';
|
||
if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m ago`;
|
||
if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h ago`;
|
||
return `${Math.floor(diffSec / 86400)}d ago`;
|
||
});
|
||
|
||
/** Quick‑jump route derived from sessionKey */
|
||
readonly jumpRoute = computed(() => `/sessions/${this.sessionKey}`);
|
||
} |