Compare commits
1 Commits
2a21cad431
...
agent/rex/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcaa526a69 |
@@ -1,82 +0,0 @@
|
|||||||
<!-- ========================================================================== -->
|
|
||||||
<!-- AgentCard — per spec Section 7.3 -->
|
|
||||||
<!-- Integrates: Status Badge · Task Progress Bar · Quick‑Jump Button -->
|
|
||||||
<!-- Left‑border accent matches status color. role="article" + aria‑labels. -->
|
|
||||||
<!-- ========================================================================== -->
|
|
||||||
<article
|
|
||||||
class="agent-card"
|
|
||||||
role="article"
|
|
||||||
[attr.aria-label]="displayName + ' — ' + statusLabel()"
|
|
||||||
[style.border-left-color]="statusBorderColor()"
|
|
||||||
>
|
|
||||||
|
|
||||||
<!-- ── Header: status badge + agent info ── -->
|
|
||||||
<div class="agent-card__header">
|
|
||||||
<div class="agent-card__badge" [attr.aria-label]="'Status: ' + statusLabel()">
|
|
||||||
<span
|
|
||||||
class="status-dot"
|
|
||||||
[ngClass]="[statusDotClass()]"
|
|
||||||
></span>
|
|
||||||
<span class="agent-card__status-label">{{ statusLabel() }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="agent-card__identity">
|
|
||||||
<span class="agent-card__name">{{ displayName || agentId }}</span>
|
|
||||||
<span class="agent-card__role">{{ role }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ── Body: current task ── -->
|
|
||||||
<div class="agent-card__body" *ngIf="task || status === 'error'">
|
|
||||||
<p
|
|
||||||
class="agent-card__task"
|
|
||||||
[attr.aria-label]="'Current task: ' + (status === 'error' ? errorMessage : task)"
|
|
||||||
>
|
|
||||||
{{ status === 'error' ? errorMessage || task : task }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ── Task Progress Bar ── -->
|
|
||||||
<div
|
|
||||||
class="agent-card__progress"
|
|
||||||
*ngIf="progress > 0 && status !== 'error'"
|
|
||||||
[attr.aria-label]="'Task progress: ' + progress + '%'"
|
|
||||||
>
|
|
||||||
<mat-progress-bar
|
|
||||||
mode="determinate"
|
|
||||||
[value]="progress"
|
|
||||||
[aria-label]="'Progress ' + progress + '% complete'"
|
|
||||||
></mat-progress-bar>
|
|
||||||
<span class="agent-card__progress-label text-mono">{{ progress }}%</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ── Footer: channel + last activity + quick‑jump ── -->
|
|
||||||
<div class="agent-card__footer">
|
|
||||||
<div class="agent-card__meta">
|
|
||||||
<span
|
|
||||||
class="agent-card__channel text-mono"
|
|
||||||
[attr.aria-label]="'Channel: ' + channel"
|
|
||||||
>
|
|
||||||
<mat-icon aria-hidden="true">{{ channelIcon() }}</mat-icon>
|
|
||||||
{{ channel }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="agent-card__last-activity text-mono"
|
|
||||||
[attr.aria-label]="'Last activity: ' + lastActivityLabel()"
|
|
||||||
>
|
|
||||||
{{ lastActivityLabel() }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick‑Jump Button -->
|
|
||||||
<a
|
|
||||||
class="agent-card__jump"
|
|
||||||
mat-button
|
|
||||||
[routerLink]="jumpRoute()"
|
|
||||||
[attr.aria-label]="'Jump to session for ' + (displayName || agentId)"
|
|
||||||
matTooltip="Jump to session"
|
|
||||||
>
|
|
||||||
<mat-icon aria-hidden="true">arrow_forward</mat-icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
// ============================================================================
|
|
||||||
// AgentCard — M3 tactical dark styling
|
|
||||||
// Per spec Section 7.3: left‑border accent, status‑aware coloring,
|
|
||||||
// responsive card layout with 320px min‑width.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
.agent-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: var(--cc-card-min-width);
|
|
||||||
padding: var(--cc-card-padding);
|
|
||||||
background-color: var(--cc-surface-container);
|
|
||||||
border-radius: var(--cc-card-border-radius);
|
|
||||||
border-left: 4px solid var(--status-offline); // default; overridden by [style]
|
|
||||||
border-top: 1px solid var(--cc-outline);
|
|
||||||
border-right: 1px solid var(--cc-outline);
|
|
||||||
border-bottom: 1px solid var(--cc-outline);
|
|
||||||
gap: 16px;
|
|
||||||
transition: border-left-color 0.3s ease, box-shadow 0.2s ease;
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
outline: 2px solid var(--status-active);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Header ──
|
|
||||||
.agent-card__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__badge {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background-color: var(--status-active-bg); // overridden per status below
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--cc-on-surface);
|
|
||||||
|
|
||||||
// Per‑status background tints
|
|
||||||
.status-dot--active + & {
|
|
||||||
background-color: var(--status-active-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__status-label {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: var(--cc-on-surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__identity {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__name {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--cc-on-surface);
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__role {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--cc-on-surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Body ──
|
|
||||||
.agent-card__body {
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__task {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--cc-on-surface);
|
|
||||||
line-height: 1.4;
|
|
||||||
|
|
||||||
// Error messages get distinct styling
|
|
||||||
.agent-card--error & {
|
|
||||||
color: var(--status-error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Progress Bar ──
|
|
||||||
.agent-card__progress {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__progress-label {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--cc-on-surface-variant);
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override mat-progress-bar to match tactical dark theme
|
|
||||||
.agent-card__progress ::ng-deep .mat-mdc-progress-bar {
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
|
|
||||||
.mdc-linear-progress__bar-inner {
|
|
||||||
background-color: var(--status-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdc-linear-progress__track {
|
|
||||||
background-color: var(--cc-outline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Footer ──
|
|
||||||
.agent-card__footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: auto; // push footer to bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__channel {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--cc-on-surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__channel-icon,
|
|
||||||
.agent-card__channel .mat-icon {
|
|
||||||
font-size: 14px;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__last-activity {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--cc-on-surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Quick‑Jump Button ──
|
|
||||||
.agent-card__jump {
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
// Match M3 text button sizing
|
|
||||||
.mat-mdc-button {
|
|
||||||
min-width: 36px;
|
|
||||||
padding: 0 8px;
|
|
||||||
color: var(--status-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-icon {
|
|
||||||
font-size: 18px;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Status‑specific background tints for badge ──
|
|
||||||
// We use the global status-dot classes from styles.scss and pair them
|
|
||||||
// with contextual background-color overrides here.
|
|
||||||
|
|
||||||
.agent-card[data-status="active"] .agent-card__badge,
|
|
||||||
.agent-card .status-dot--active ~ .agent-card__badge {
|
|
||||||
background-color: var(--status-active-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card[data-status="idle"] .agent-card__badge {
|
|
||||||
background-color: var(--status-idle-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card[data-status="thinking"] .agent-card__badge {
|
|
||||||
background-color: var(--status-thinking-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card[data-status="error"] .agent-card__badge {
|
|
||||||
background-color: var(--status-error-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Responsive ──
|
|
||||||
@media (max-width: 599px) {
|
|
||||||
.agent-card {
|
|
||||||
min-width: unset;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__header {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__footer {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-card__meta {
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Accessibility: reduced motion ──
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.agent-card {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
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}`);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './agent-card/agent-card.component';
|
|
||||||
1
frontend/src/app/components/index.ts
Normal file
1
frontend/src/app/components/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './quick-jump-button/quick-jump-button.component';
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
class="quick-jump-button"
|
||||||
|
[attr.aria-label]="'Jump to agent session'"
|
||||||
|
(click)="onJumpClick()"
|
||||||
|
>
|
||||||
|
<mat-icon>arrow_forward</mat-icon>
|
||||||
|
</button>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// Quick-Jump Button — M3 FilledTonalIconButton
|
||||||
|
// Per spec Section 7.3: Agent Card Quick-Jump action
|
||||||
|
// M3 spec: FilledTonalIconButton uses secondary container color
|
||||||
|
// with 8% state layer overlay for hover/focus.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
.quick-jump-button {
|
||||||
|
// M3 FilledTonalIconButton: secondary-container background
|
||||||
|
// Angular Material mat-icon-button sets up the base shape (40x40, round).
|
||||||
|
// We override the color tokens to match FilledTonal style.
|
||||||
|
--mdc-icon-button-icon-color: var(--mat-sys-on-secondary-container);
|
||||||
|
background-color: var(--mat-sys-secondary-container);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
// M3 State Layer: 8% overlay on hover
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--mat-sys-secondary-container);
|
||||||
|
// State layer overlay using a pseudo-element for precise 8% opacity
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--mat-sys-on-secondary-container);
|
||||||
|
opacity: 0.08;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// M3 State Layer: 12% overlay on focus-visible (slightly stronger for accessibility)
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--mat-sys-secondary-container);
|
||||||
|
outline: 3px solid var(--status-active);
|
||||||
|
outline-offset: 2px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--mat-sys-on-secondary-container);
|
||||||
|
opacity: 0.12;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// M3 State Layer: 12% overlay on active/pressed
|
||||||
|
&:active {
|
||||||
|
background-color: var(--mat-sys-secondary-container);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--mat-sys-on-secondary-container);
|
||||||
|
opacity: 0.12;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon color stays on-secondary-container across all states
|
||||||
|
.mat-icon {
|
||||||
|
color: var(--mat-sys-on-secondary-container);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { MatIconButton } from '@angular/material/button';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick-Jump Button — M3 FilledTonalIconButton
|
||||||
|
*
|
||||||
|
* An icon button that emits a navigation event for jumping to an agent session.
|
||||||
|
* Uses the Material Design 3 FilledTonalIconButton style with 8% state layer
|
||||||
|
* overlay on hover and focus.
|
||||||
|
*
|
||||||
|
* Per spec Section 7.3: Agent Card Component Interface
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'app-quick-jump-button',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatIconButton, MatIcon],
|
||||||
|
templateUrl: './quick-jump-button.component.html',
|
||||||
|
styleUrl: './quick-jump-button.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class QuickJumpButtonComponent {
|
||||||
|
/** Emitted when the button is clicked, carrying the session key for navigation. */
|
||||||
|
@Output() jumpClick = new EventEmitter<string>();
|
||||||
|
|
||||||
|
/** The session key to navigate to. Set by the parent agent card. */
|
||||||
|
sessionKey = '';
|
||||||
|
|
||||||
|
onJumpClick(): void {
|
||||||
|
this.jumpClick.emit(this.sessionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user