feat(#52): implement Active Projects & Agent Chains widget
Add HUD widget for tracking active projects and running agent sessions. Backend: - Add getActiveProjectsData() and getAgentChainsData() to WidgetDataService - Create POST /api/widgets/data/active-projects endpoint - Create POST /api/widgets/data/agent-chains endpoint - Add WidgetProjectItem and WidgetAgentSessionItem response types Frontend: - Create ActiveProjectsWidget component with dual panels - Active Projects panel: name, color, task/event counts, last activity - Agent Chains panel: status, runtime, message count, expandable details - Real-time updates (projects: 30s, agents: 10s) - PDA-friendly status indicators (Running vs URGENT) Testing: - 7 comprehensive tests covering loading, rendering, empty states, expandability - All tests passing (7/7) Refs #52 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,31 @@ export interface WidgetCalendarItem {
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface WidgetProjectItem {
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
lastActivity: string;
|
||||
taskCount: number;
|
||||
eventCount: number;
|
||||
color: string | null;
|
||||
}
|
||||
|
||||
export interface WidgetAgentSessionItem {
|
||||
id: string;
|
||||
sessionKey: string;
|
||||
label: string | null;
|
||||
channel: string | null;
|
||||
agentName: string | null;
|
||||
agentStatus: string | null;
|
||||
status: "active" | "ended";
|
||||
startedAt: string;
|
||||
lastMessageAt: string | null;
|
||||
runtimeMs: number;
|
||||
messageCount: number;
|
||||
contextSummary: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for fetching widget data from various sources
|
||||
*/
|
||||
@@ -595,4 +620,76 @@ export class WidgetDataService {
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active projects data
|
||||
*/
|
||||
async getActiveProjectsData(workspaceId: string): Promise<WidgetProjectItem[]> {
|
||||
const projects = await this.prisma.project.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
status: ProjectStatus.ACTIVE,
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: { tasks: true, events: true },
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: "desc",
|
||||
},
|
||||
take: 20,
|
||||
});
|
||||
|
||||
return projects.map((project) => ({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
status: project.status,
|
||||
lastActivity: project.updatedAt.toISOString(),
|
||||
taskCount: project._count.tasks,
|
||||
eventCount: project._count.events,
|
||||
color: project.color,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent chains data (active agent sessions)
|
||||
*/
|
||||
async getAgentChainsData(workspaceId: string): Promise<WidgetAgentSessionItem[]> {
|
||||
const sessions = await this.prisma.agentSession.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
isActive: true,
|
||||
},
|
||||
include: {
|
||||
agent: {
|
||||
select: {
|
||||
name: true,
|
||||
status: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
startedAt: "desc",
|
||||
},
|
||||
take: 20,
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
|
||||
return sessions.map((session) => ({
|
||||
id: session.id,
|
||||
sessionKey: session.sessionKey,
|
||||
label: session.label,
|
||||
channel: session.channel,
|
||||
agentName: session.agent?.name ?? null,
|
||||
agentStatus: session.agent?.status ?? null,
|
||||
status: session.isActive ? ("active" as const) : ("ended" as const),
|
||||
startedAt: session.startedAt.toISOString(),
|
||||
lastMessageAt: session.lastMessageAt ? session.lastMessageAt.toISOString() : null,
|
||||
runtimeMs: now.getTime() - session.startedAt.getTime(),
|
||||
messageCount: session.messageCount,
|
||||
contextSummary: session.contextSummary,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +100,30 @@ export class WidgetsController {
|
||||
}
|
||||
return this.widgetDataService.getCalendarPreviewData(workspaceId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/widgets/data/active-projects
|
||||
* Get active projects widget data
|
||||
*/
|
||||
@Post("data/active-projects")
|
||||
async getActiveProjectsData(@Request() req: AuthenticatedRequest) {
|
||||
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
|
||||
if (!workspaceId) {
|
||||
throw new UnauthorizedException("Workspace ID required");
|
||||
}
|
||||
return this.widgetDataService.getActiveProjectsData(workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/widgets/data/agent-chains
|
||||
* Get agent chains widget data (active agent sessions)
|
||||
*/
|
||||
@Post("data/agent-chains")
|
||||
async getAgentChainsData(@Request() req: AuthenticatedRequest) {
|
||||
const workspaceId = req.user?.currentWorkspaceId ?? req.user?.workspaceId;
|
||||
if (!workspaceId) {
|
||||
throw new UnauthorizedException("Workspace ID required");
|
||||
}
|
||||
return this.widgetDataService.getAgentChainsData(workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user