All checks were successful
Dev Build / build-test (pull_request) Successful in 2m46s
153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
import { ChangeDetectionStrategy, Component, signal, computed, ViewChild } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { MatChipsModule } from '@angular/material/chips';
|
|
import { AgentCardComponent } from '../../command-hub/components/agent-card/agent-card.component';
|
|
import { AgentSessionDrawerComponent } from '../../components/agent-session-drawer/index';
|
|
import { AgentCardData } from '../../models/agent.model';
|
|
import { AgentStatus } from '../../models/agent.model';
|
|
|
|
/**
|
|
* Filter options for the hub page agent card grid.
|
|
* Per CUB-27: "Filter chip group (All, Active, Error, etc.) with horizontal scroll on mobile"
|
|
*/
|
|
export type AgentFilter = 'all' | AgentStatus;
|
|
|
|
@Component({
|
|
selector: 'app-hub-page',
|
|
standalone: true,
|
|
imports: [CommonModule, MatChipsModule, AgentCardComponent, AgentSessionDrawerComponent],
|
|
templateUrl: './hub-page.component.html',
|
|
styleUrl: './hub-page.component.scss',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class HubPageComponent {
|
|
@ViewChild(AgentSessionDrawerComponent) sessionDrawer!: AgentSessionDrawerComponent;
|
|
|
|
readonly isMobile = signal(false);
|
|
|
|
protected readonly filters: { label: string; value: AgentFilter }[] = [
|
|
{ label: 'All', value: 'all' },
|
|
{ label: 'Active', value: 'active' },
|
|
{ label: 'Idle', value: 'idle' },
|
|
{ label: 'Thinking', value: 'thinking' },
|
|
{ label: 'Error', value: 'error' },
|
|
{ label: 'Offline', value: 'offline' },
|
|
];
|
|
|
|
protected readonly activeFilter = signal<AgentFilter>('all');
|
|
|
|
/** Stub agent data (TODO: wire to AgentStatusService / SignalR). */
|
|
readonly agents = signal<AgentCardData[]>([
|
|
{
|
|
id: 'otto',
|
|
displayName: 'Otto',
|
|
role: 'Orchestrator Agent',
|
|
status: 'active',
|
|
currentTask: 'Reviewing PR #42',
|
|
taskProgress: 67,
|
|
taskElapsed: '04m 12s',
|
|
sessionKey: 'agent:otto:slack:CUB-42:abc123',
|
|
channel: 'slack',
|
|
lastActivity: new Date(),
|
|
},
|
|
{
|
|
id: 'rex',
|
|
displayName: 'Rex',
|
|
role: 'Frontend Agent',
|
|
status: 'thinking',
|
|
currentTask: 'Building responsive layout',
|
|
taskProgress: 40,
|
|
taskElapsed: '02m 30s',
|
|
sessionKey: 'agent:rex:telegram:CUB-27:def456',
|
|
channel: 'telegram',
|
|
lastActivity: new Date(Date.now() - 30000),
|
|
},
|
|
{
|
|
id: 'dex',
|
|
displayName: 'Dex',
|
|
role: 'Backend Agent',
|
|
status: 'idle',
|
|
currentTask: undefined,
|
|
taskProgress: undefined,
|
|
taskElapsed: undefined,
|
|
sessionKey: 'agent:dex:slack:CUB-53:ghi789',
|
|
channel: 'slack',
|
|
lastActivity: new Date(Date.now() - 300000),
|
|
},
|
|
{
|
|
id: 'hex',
|
|
displayName: 'Hex',
|
|
role: 'Database Agent',
|
|
status: 'error',
|
|
currentTask: 'Migration failed — rollback initiated',
|
|
taskProgress: 0,
|
|
taskElapsed: '00m 45s',
|
|
sessionKey: 'agent:hex:slack:CUB-56:jkl012',
|
|
channel: 'slack',
|
|
lastActivity: new Date(Date.now() - 60000),
|
|
errorMessage: 'Connection timeout to database server',
|
|
},
|
|
{
|
|
id: 'nano',
|
|
displayName: 'Nano',
|
|
role: 'ESP32 Agent',
|
|
status: 'offline',
|
|
currentTask: undefined,
|
|
taskProgress: undefined,
|
|
taskElapsed: undefined,
|
|
sessionKey: 'agent:nano:mqtt:CUB-48:mno345',
|
|
channel: 'mqtt',
|
|
lastActivity: new Date(Date.now() - 86400000),
|
|
},
|
|
]);
|
|
|
|
protected readonly filteredAgents = computed(() => {
|
|
const filter = this.activeFilter();
|
|
if (filter === 'all') return this.agents();
|
|
return this.agents().filter(a => a.status === filter);
|
|
});
|
|
|
|
constructor() {
|
|
// Detect mobile viewport
|
|
if (typeof window !== 'undefined') {
|
|
const mql = window.matchMedia('(max-width: 599px)');
|
|
this.isMobile.set(mql.matches);
|
|
mql.addEventListener('change', (e) => this.isMobile.set(e.matches));
|
|
}
|
|
}
|
|
|
|
protected selectFilter(filter: AgentFilter): void {
|
|
this.activeFilter.set(filter);
|
|
}
|
|
|
|
/** Card click → open session drawer with agent details. */
|
|
onCardClick(sessionKey: string): void {
|
|
const agent = this.agents().find((a) => a.sessionKey === sessionKey);
|
|
if (agent) {
|
|
this.sessionDrawer?.open(agent);
|
|
}
|
|
}
|
|
|
|
/** Long-press on card → bypass drawer, go directly to session log. */
|
|
onCardLongPress(sessionKey: string): void {
|
|
console.log('[Hub] Long press — navigate to session log:', sessionKey);
|
|
// TODO: Navigate directly to session log page when sessions route is implemented
|
|
}
|
|
|
|
/** Open full session from drawer action button. */
|
|
onOpenSession(sessionKey: string): void {
|
|
console.log('[Hub] Open full session:', sessionKey);
|
|
// TODO: Navigate to full session view
|
|
}
|
|
|
|
/** Pin agent to dashboard from drawer action button. */
|
|
onPinToDashboard(sessionKey: string): void {
|
|
console.log('[Hub] Pin to dashboard:', sessionKey);
|
|
// TODO: Implement pin-to-dashboard
|
|
}
|
|
|
|
/** Drawer closed. */
|
|
onDrawerClose(): void {
|
|
// No-op for now — drawer is self-managing
|
|
}
|
|
} |