Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions apps/web/app/api/dashboard/activity/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* API endpoint for recent agent activity
*
* Returns a timeline of recent agent events across all projects
*/

import { NextRequest, NextResponse } from 'next/server';
import { AgentEventService } from '@codervisor/devlog-core/server';

export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
Comment on lines +10 to +12
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function parameter request is only used to extract searchParams. Consider destructuring directly or using _request if the full request object might be needed for future middleware.

Suggested change
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
export async function GET({ nextUrl }: NextRequest) {
try {
const searchParams = nextUrl.searchParams;

Copilot uses AI. Check for mistakes.
const limit = parseInt(searchParams.get('limit') || '20');

// Get all projects (for now, using projectId 1 as default)
// TODO: Query across all user's projects
const projectId = 1;

const eventService = AgentEventService.getInstance(projectId);
await eventService.initialize();

// Get recent events
const events = await eventService.getEvents({
projectId,
limit,
});

return NextResponse.json({
success: true,
data: events,
});
} catch (error) {
console.error('Error fetching recent activity:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch recent activity',
},
{ status: 500 }
);
}
}
80 changes: 80 additions & 0 deletions apps/web/app/api/dashboard/stats/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* API endpoint for dashboard statistics
*
* Provides aggregated metrics for the main dashboard:
* - Active sessions count
* - Total events today
* - Average session duration
* - Events per minute rate
*/

import { NextRequest, NextResponse } from 'next/server';
import { AgentSessionService, AgentEventService } from '@codervisor/devlog-core/server';

export async function GET(request: NextRequest) {
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request parameter is declared but never used. Consider removing it if not needed, or use _request to indicate it's intentionally unused.

Suggested change
export async function GET(request: NextRequest) {
export async function GET(_request: NextRequest) {

Copilot uses AI. Check for mistakes.
try {
// Get all projects (for now, using projectId 1 as default)
// TODO: Query across all user's projects
const projectId = 1;

const sessionService = AgentSessionService.getInstance(projectId);
const eventService = AgentEventService.getInstance(projectId);

await Promise.all([
sessionService.initialize(),
eventService.initialize()
]);

// Get active sessions
const activeSessions = await sessionService.getActiveSessions();

// Get today's date range
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);

// Get events from today
const todayEvents = await eventService.getEvents({
projectId,
startTime: today,
endTime: tomorrow,
});

// Calculate events per minute (based on last hour)
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
const recentEvents = await eventService.getEvents({
projectId,
startTime: oneHourAgo,
});
const eventsPerMinute = recentEvents.length > 0
? (recentEvents.length / 60).toFixed(2)
: '0';

// Get session stats for average duration
const sessionStats = await sessionService.getSessionStats({
projectId,
startTimeFrom: today,
});

return NextResponse.json({
success: true,
data: {
activeSessions: activeSessions.length,
totalEventsToday: todayEvents.length,
averageDuration: sessionStats.averageDuration || 0,
eventsPerMinute: parseFloat(eventsPerMinute),
lastUpdated: new Date().toISOString(),
},
});
} catch (error) {
console.error('Error fetching dashboard stats:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch dashboard statistics',
},
{ status: 500 }
);
}
}
64 changes: 64 additions & 0 deletions apps/web/app/api/sessions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* API endpoint for global agent sessions
*
* Returns agent sessions across all projects with filtering and search
*/

import { NextRequest, NextResponse } from 'next/server';
import { AgentSessionService } from '@codervisor/devlog-core/server';

export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;

// Parse query parameters
const agentId = searchParams.get('agentId') || undefined;
const outcome = searchParams.get('outcome') || undefined;
const status = searchParams.get('status') || undefined; // 'active' or 'completed'
const startTimeFrom = searchParams.get('startTimeFrom') || undefined;
const startTimeTo = searchParams.get('startTimeTo') || undefined;
const limit = parseInt(searchParams.get('limit') || '50');
const offset = parseInt(searchParams.get('offset') || '0');

// Get all projects (for now, using projectId 1 as default)
// TODO: Query across all user's projects
const projectId = 1;

const sessionService = AgentSessionService.getInstance(projectId);
await sessionService.initialize();

// Build filter
const filter: any = { projectId, limit, offset };
if (agentId) filter.agentId = agentId;
if (outcome) filter.outcome = outcome;
if (startTimeFrom) filter.startTimeFrom = new Date(startTimeFrom);
if (startTimeTo) filter.startTimeTo = new Date(startTimeTo);

// Get sessions based on status
let sessions;
if (status === 'active') {
sessions = await sessionService.getActiveSessions();
} else {
sessions = await sessionService.listSessions(filter);
}

return NextResponse.json({
success: true,
data: sessions,
pagination: {
limit,
offset,
total: sessions.length,
},
});
} catch (error) {
console.error('Error fetching sessions:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch sessions',
},
{ status: 500 }
);
}
}
84 changes: 10 additions & 74 deletions apps/web/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
*/

import { Suspense } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { Activity, Zap, Clock, TrendingUp } from 'lucide-react';
import { DashboardStats, RecentActivity, ActiveSessions } from '@/components/agent-observability/dashboard';

export default function DashboardPage() {
return (
Expand All @@ -23,82 +22,19 @@ export default function DashboardPage() {
</div>

{/* Overview Stats */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Sessions</CardTitle>
<Activity className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">0</div>
<p className="text-xs text-muted-foreground">No active agent sessions</p>
</CardContent>
</Card>

<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Events Today</CardTitle>
<Zap className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">0</div>
<p className="text-xs text-muted-foreground">Agent events logged</p>
</CardContent>
</Card>

<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Avg Session Duration</CardTitle>
<Clock className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">-</div>
<p className="text-xs text-muted-foreground">No sessions yet</p>
</CardContent>
</Card>

<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Events Per Minute</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">0</div>
<p className="text-xs text-muted-foreground">Current rate</p>
</CardContent>
</Card>
</div>
<Suspense fallback={<Skeleton className="h-32 w-full" />}>
<DashboardStats />
</Suspense>

{/* Recent Activity */}
<Card>
<CardHeader>
<CardTitle>Recent Agent Activity</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="text-muted-foreground mb-4 text-4xl">πŸ€–</div>
<h3 className="text-lg font-semibold mb-2">No Agent Activity Yet</h3>
<p className="text-sm text-muted-foreground max-w-md">
Start monitoring your AI coding agents by configuring collectors and starting agent sessions.
Visit the Settings page to set up your first collector.
</p>
</div>
</CardContent>
</Card>
<Suspense fallback={<Skeleton className="h-64 w-full" />}>
<RecentActivity />
</Suspense>

{/* Active Sessions */}
<Card>
<CardHeader>
<CardTitle>Live Agent Sessions</CardTitle>
</CardHeader>
<CardContent>
<Suspense fallback={<Skeleton className="h-32 w-full" />}>
<div className="text-sm text-muted-foreground text-center py-8">
No active sessions
</div>
</Suspense>
</CardContent>
</Card>
<Suspense fallback={<Skeleton className="h-32 w-full" />}>
<ActiveSessions />
</Suspense>
</div>
);
}
40 changes: 7 additions & 33 deletions apps/web/app/sessions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/

import { Suspense } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { SessionsList } from '@/components/agent-observability/sessions';

export default function SessionsPage() {
return (
Expand All @@ -22,40 +22,14 @@ export default function SessionsPage() {
</div>

{/* Active Sessions */}
<Card>
<CardHeader>
<CardTitle>Active Sessions</CardTitle>
</CardHeader>
<CardContent>
<Suspense fallback={<Skeleton className="h-32 w-full" />}>
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="text-muted-foreground mb-4 text-4xl">⚑</div>
<h3 className="text-lg font-semibold mb-2">No Active Sessions</h3>
<p className="text-sm text-muted-foreground max-w-md">
No agents are currently running. Start a coding session with your AI agent to see it here.
</p>
</div>
</Suspense>
</CardContent>
</Card>
<Suspense fallback={<Skeleton className="h-32 w-full" />}>
<SessionsList status="active" title="Active Sessions" />
</Suspense>

{/* Recent Sessions */}
<Card>
<CardHeader>
<CardTitle>Recent Sessions</CardTitle>
</CardHeader>
<CardContent>
<Suspense fallback={<Skeleton className="h-96 w-full" />}>
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="text-muted-foreground mb-4 text-4xl">πŸ“Š</div>
<h3 className="text-lg font-semibold mb-2">No Session History</h3>
<p className="text-sm text-muted-foreground max-w-md">
Once you start using AI coding agents, their sessions will appear here for review and analysis.
</p>
</div>
</Suspense>
</CardContent>
</Card>
<Suspense fallback={<Skeleton className="h-96 w-full" />}>
<SessionsList title="Recent Sessions" />
</Suspense>
</div>
);
}
Loading