@supaku/agentfactory-dashboard 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/components.json +16 -0
- package/package.json +47 -0
- package/src/components/fleet/agent-card.tsx +62 -0
- package/src/components/fleet/fleet-overview.tsx +89 -0
- package/src/components/fleet/provider-icon.tsx +61 -0
- package/src/components/fleet/stat-card.tsx +43 -0
- package/src/components/fleet/status-dot.tsx +32 -0
- package/src/components/layout/bottom-bar.tsx +50 -0
- package/src/components/layout/shell.tsx +57 -0
- package/src/components/layout/sidebar.tsx +81 -0
- package/src/components/layout/top-bar.tsx +55 -0
- package/src/components/pipeline/pipeline-card.tsx +42 -0
- package/src/components/pipeline/pipeline-column.tsx +40 -0
- package/src/components/pipeline/pipeline-view.tsx +75 -0
- package/src/components/sessions/session-detail.tsx +132 -0
- package/src/components/sessions/session-list.tsx +109 -0
- package/src/components/sessions/session-timeline.tsx +54 -0
- package/src/components/sessions/token-chart.tsx +51 -0
- package/src/components/settings/settings-view.tsx +155 -0
- package/src/components/shared/empty-state.tsx +29 -0
- package/src/components/shared/logo.tsx +37 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/button.tsx +54 -0
- package/src/components/ui/card.tsx +50 -0
- package/src/components/ui/dropdown-menu.tsx +77 -0
- package/src/components/ui/scroll-area.tsx +45 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/sheet.tsx +88 -0
- package/src/components/ui/skeleton.tsx +12 -0
- package/src/components/ui/tabs.tsx +54 -0
- package/src/components/ui/tooltip.tsx +29 -0
- package/src/hooks/use-sessions.ts +13 -0
- package/src/hooks/use-stats.ts +13 -0
- package/src/hooks/use-workers.ts +17 -0
- package/src/index.ts +82 -0
- package/src/lib/format.ts +36 -0
- package/src/lib/status-config.ts +58 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/work-type-config.ts +53 -0
- package/src/pages/dashboard-page.tsx +7 -0
- package/src/pages/pipeline-page.tsx +7 -0
- package/src/pages/session-page.tsx +29 -0
- package/src/pages/settings-page.tsx +7 -0
- package/src/styles/globals.css +38 -0
- package/src/types/api.ts +45 -0
- package/tailwind.config.ts +88 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function formatDuration(seconds: number): string {
|
|
2
|
+
if (seconds < 60) return `${seconds}s`
|
|
3
|
+
if (seconds < 3600) {
|
|
4
|
+
const m = Math.floor(seconds / 60)
|
|
5
|
+
const s = seconds % 60
|
|
6
|
+
return s > 0 ? `${m}m ${s}s` : `${m}m`
|
|
7
|
+
}
|
|
8
|
+
const h = Math.floor(seconds / 3600)
|
|
9
|
+
const m = Math.floor((seconds % 3600) / 60)
|
|
10
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatCost(usd: number | undefined | null): string {
|
|
14
|
+
if (usd == null || usd === 0) return '$0.00'
|
|
15
|
+
if (usd < 0.01) return `$${usd.toFixed(4)}`
|
|
16
|
+
return `$${usd.toFixed(2)}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatTokens(count: number | undefined | null): string {
|
|
20
|
+
if (count == null || count === 0) return '0'
|
|
21
|
+
if (count < 1_000) return count.toString()
|
|
22
|
+
if (count < 1_000_000) return `${(count / 1_000).toFixed(1)}k`
|
|
23
|
+
return `${(count / 1_000_000).toFixed(2)}M`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatRelativeTime(isoString: string): string {
|
|
27
|
+
const diff = Date.now() - new Date(isoString).getTime()
|
|
28
|
+
const seconds = Math.floor(diff / 1000)
|
|
29
|
+
if (seconds < 60) return 'just now'
|
|
30
|
+
const minutes = Math.floor(seconds / 60)
|
|
31
|
+
if (minutes < 60) return `${minutes}m ago`
|
|
32
|
+
const hours = Math.floor(minutes / 60)
|
|
33
|
+
if (hours < 24) return `${hours}h ago`
|
|
34
|
+
const days = Math.floor(hours / 24)
|
|
35
|
+
return `${days}d ago`
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type SessionStatus = 'queued' | 'parked' | 'working' | 'completed' | 'failed' | 'stopped'
|
|
2
|
+
|
|
3
|
+
export interface StatusConfig {
|
|
4
|
+
label: string
|
|
5
|
+
dotColor: string
|
|
6
|
+
textColor: string
|
|
7
|
+
bgColor: string
|
|
8
|
+
animate: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const statuses: Record<SessionStatus, StatusConfig> = {
|
|
12
|
+
working: {
|
|
13
|
+
label: 'Working',
|
|
14
|
+
dotColor: 'bg-af-status-success',
|
|
15
|
+
textColor: 'text-af-status-success',
|
|
16
|
+
bgColor: 'bg-af-status-success/10',
|
|
17
|
+
animate: true,
|
|
18
|
+
},
|
|
19
|
+
queued: {
|
|
20
|
+
label: 'Queued',
|
|
21
|
+
dotColor: 'bg-af-status-warning',
|
|
22
|
+
textColor: 'text-af-status-warning',
|
|
23
|
+
bgColor: 'bg-af-status-warning/10',
|
|
24
|
+
animate: true,
|
|
25
|
+
},
|
|
26
|
+
parked: {
|
|
27
|
+
label: 'Parked',
|
|
28
|
+
dotColor: 'bg-af-text-secondary',
|
|
29
|
+
textColor: 'text-af-text-secondary',
|
|
30
|
+
bgColor: 'bg-af-text-secondary/10',
|
|
31
|
+
animate: false,
|
|
32
|
+
},
|
|
33
|
+
completed: {
|
|
34
|
+
label: 'Completed',
|
|
35
|
+
dotColor: 'bg-af-status-success',
|
|
36
|
+
textColor: 'text-af-status-success',
|
|
37
|
+
bgColor: 'bg-af-status-success/10',
|
|
38
|
+
animate: false,
|
|
39
|
+
},
|
|
40
|
+
failed: {
|
|
41
|
+
label: 'Failed',
|
|
42
|
+
dotColor: 'bg-af-status-error',
|
|
43
|
+
textColor: 'text-af-status-error',
|
|
44
|
+
bgColor: 'bg-af-status-error/10',
|
|
45
|
+
animate: false,
|
|
46
|
+
},
|
|
47
|
+
stopped: {
|
|
48
|
+
label: 'Stopped',
|
|
49
|
+
dotColor: 'bg-af-text-secondary',
|
|
50
|
+
textColor: 'text-af-text-secondary',
|
|
51
|
+
bgColor: 'bg-af-text-secondary/10',
|
|
52
|
+
animate: false,
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getStatusConfig(status: SessionStatus): StatusConfig {
|
|
57
|
+
return statuses[status] ?? statuses.queued
|
|
58
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface WorkTypeConfig {
|
|
2
|
+
label: string
|
|
3
|
+
color: string
|
|
4
|
+
bgColor: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const workTypes: Record<string, WorkTypeConfig> = {
|
|
8
|
+
development: {
|
|
9
|
+
label: 'Development',
|
|
10
|
+
color: 'text-blue-400',
|
|
11
|
+
bgColor: 'bg-blue-400/10',
|
|
12
|
+
},
|
|
13
|
+
bugfix: {
|
|
14
|
+
label: 'Bug Fix',
|
|
15
|
+
color: 'text-red-400',
|
|
16
|
+
bgColor: 'bg-red-400/10',
|
|
17
|
+
},
|
|
18
|
+
feature: {
|
|
19
|
+
label: 'Feature',
|
|
20
|
+
color: 'text-emerald-400',
|
|
21
|
+
bgColor: 'bg-emerald-400/10',
|
|
22
|
+
},
|
|
23
|
+
qa: {
|
|
24
|
+
label: 'QA',
|
|
25
|
+
color: 'text-purple-400',
|
|
26
|
+
bgColor: 'bg-purple-400/10',
|
|
27
|
+
},
|
|
28
|
+
refactor: {
|
|
29
|
+
label: 'Refactor',
|
|
30
|
+
color: 'text-amber-400',
|
|
31
|
+
bgColor: 'bg-amber-400/10',
|
|
32
|
+
},
|
|
33
|
+
review: {
|
|
34
|
+
label: 'Review',
|
|
35
|
+
color: 'text-cyan-400',
|
|
36
|
+
bgColor: 'bg-cyan-400/10',
|
|
37
|
+
},
|
|
38
|
+
docs: {
|
|
39
|
+
label: 'Docs',
|
|
40
|
+
color: 'text-indigo-400',
|
|
41
|
+
bgColor: 'bg-indigo-400/10',
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const defaultWorkType: WorkTypeConfig = {
|
|
46
|
+
label: 'Development',
|
|
47
|
+
color: 'text-af-text-secondary',
|
|
48
|
+
bgColor: 'bg-af-text-secondary/10',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getWorkTypeConfig(workType: string): WorkTypeConfig {
|
|
52
|
+
return workTypes[workType.toLowerCase()] ?? defaultWorkType
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useSessions } from '../hooks/use-sessions'
|
|
5
|
+
import { SessionList } from '../components/sessions/session-list'
|
|
6
|
+
import { SessionDetail } from '../components/sessions/session-detail'
|
|
7
|
+
|
|
8
|
+
interface SessionPageProps {
|
|
9
|
+
sessionId?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SessionPage({ sessionId: initialId }: SessionPageProps) {
|
|
13
|
+
const [selectedId, setSelectedId] = useState<string | undefined>(initialId)
|
|
14
|
+
const { data } = useSessions()
|
|
15
|
+
const sessions = data?.sessions ?? []
|
|
16
|
+
|
|
17
|
+
const selected = selectedId ? sessions.find((s) => s.id === selectedId) : undefined
|
|
18
|
+
|
|
19
|
+
if (selected) {
|
|
20
|
+
return (
|
|
21
|
+
<SessionDetail
|
|
22
|
+
session={selected}
|
|
23
|
+
onBack={() => setSelectedId(undefined)}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return <SessionList onSelect={setSelectedId} />
|
|
29
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--background: 222 47% 7%;
|
|
8
|
+
--foreground: 210 40% 98%;
|
|
9
|
+
--card: 222 30% 14%;
|
|
10
|
+
--card-foreground: 210 40% 98%;
|
|
11
|
+
--popover: 222 30% 14%;
|
|
12
|
+
--popover-foreground: 210 40% 98%;
|
|
13
|
+
--primary: 19 100% 60%;
|
|
14
|
+
--primary-foreground: 210 40% 98%;
|
|
15
|
+
--secondary: 222 30% 18%;
|
|
16
|
+
--secondary-foreground: 210 40% 98%;
|
|
17
|
+
--muted: 222 20% 22%;
|
|
18
|
+
--muted-foreground: 215 16% 62%;
|
|
19
|
+
--accent: 222 30% 18%;
|
|
20
|
+
--accent-foreground: 210 40% 98%;
|
|
21
|
+
--destructive: 0 84% 60%;
|
|
22
|
+
--destructive-foreground: 210 40% 98%;
|
|
23
|
+
--border: 222 20% 22%;
|
|
24
|
+
--input: 222 20% 22%;
|
|
25
|
+
--ring: 19 100% 60%;
|
|
26
|
+
--radius: 0.5rem;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@layer base {
|
|
31
|
+
* {
|
|
32
|
+
@apply border-border;
|
|
33
|
+
}
|
|
34
|
+
body {
|
|
35
|
+
@apply bg-background text-foreground;
|
|
36
|
+
font-feature-settings: "rlig" 1, "calt" 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/types/api.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface PublicStatsResponse {
|
|
2
|
+
workersOnline: number
|
|
3
|
+
agentsWorking: number
|
|
4
|
+
queueDepth: number
|
|
5
|
+
completedToday: number
|
|
6
|
+
availableCapacity: number
|
|
7
|
+
totalCostToday?: number
|
|
8
|
+
timestamp: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type SessionStatus = 'queued' | 'parked' | 'working' | 'completed' | 'failed' | 'stopped'
|
|
12
|
+
|
|
13
|
+
export interface PublicSessionResponse {
|
|
14
|
+
id: string
|
|
15
|
+
identifier: string
|
|
16
|
+
status: SessionStatus
|
|
17
|
+
workType: string
|
|
18
|
+
startedAt: string
|
|
19
|
+
duration: number
|
|
20
|
+
costUsd?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PublicSessionsListResponse {
|
|
24
|
+
sessions: PublicSessionResponse[]
|
|
25
|
+
count: number
|
|
26
|
+
timestamp: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkerResponse {
|
|
30
|
+
id: string
|
|
31
|
+
status: 'active' | 'draining' | 'offline'
|
|
32
|
+
capacity: number
|
|
33
|
+
activeSessions: number
|
|
34
|
+
lastHeartbeat: string
|
|
35
|
+
provider?: string
|
|
36
|
+
hostname?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WorkersListResponse {
|
|
40
|
+
workers: WorkerResponse[]
|
|
41
|
+
count: number
|
|
42
|
+
timestamp: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type PipelineStatus = 'backlog' | 'started' | 'finished' | 'delivered' | 'accepted'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Config } from 'tailwindcss'
|
|
2
|
+
|
|
3
|
+
const dashboardPreset: Config = {
|
|
4
|
+
content: [],
|
|
5
|
+
theme: {
|
|
6
|
+
extend: {
|
|
7
|
+
colors: {
|
|
8
|
+
'af-bg-primary': '#0A0E1A',
|
|
9
|
+
'af-bg-secondary': '#111827',
|
|
10
|
+
'af-surface': '#1A1F2E',
|
|
11
|
+
'af-surface-border': '#2A3040',
|
|
12
|
+
'af-accent': '#FF6B35',
|
|
13
|
+
'af-status-success': '#22C55E',
|
|
14
|
+
'af-status-warning': '#F59E0B',
|
|
15
|
+
'af-status-error': '#EF4444',
|
|
16
|
+
'af-text-primary': '#F9FAFB',
|
|
17
|
+
'af-text-secondary': '#9CA3AF',
|
|
18
|
+
'af-code': '#A5B4FC',
|
|
19
|
+
border: 'hsl(var(--border))',
|
|
20
|
+
input: 'hsl(var(--input))',
|
|
21
|
+
ring: 'hsl(var(--ring))',
|
|
22
|
+
background: 'hsl(var(--background))',
|
|
23
|
+
foreground: 'hsl(var(--foreground))',
|
|
24
|
+
primary: {
|
|
25
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
26
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
27
|
+
},
|
|
28
|
+
secondary: {
|
|
29
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
30
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
31
|
+
},
|
|
32
|
+
destructive: {
|
|
33
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
34
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
35
|
+
},
|
|
36
|
+
muted: {
|
|
37
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
38
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
39
|
+
},
|
|
40
|
+
accent: {
|
|
41
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
42
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
43
|
+
},
|
|
44
|
+
popover: {
|
|
45
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
46
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
47
|
+
},
|
|
48
|
+
card: {
|
|
49
|
+
DEFAULT: 'hsl(var(--card))',
|
|
50
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
fontFamily: {
|
|
54
|
+
sans: ['Inter', 'Geist Sans', 'system-ui', 'sans-serif'],
|
|
55
|
+
mono: ['JetBrains Mono', 'Geist Mono', 'ui-monospace', 'monospace'],
|
|
56
|
+
},
|
|
57
|
+
fontSize: {
|
|
58
|
+
xs: ['0.6875rem', { lineHeight: '1rem' }],
|
|
59
|
+
sm: ['0.8125rem', { lineHeight: '1.25rem' }],
|
|
60
|
+
base: ['0.875rem', { lineHeight: '1.5rem' }],
|
|
61
|
+
lg: ['1rem', { lineHeight: '1.75rem' }],
|
|
62
|
+
},
|
|
63
|
+
borderRadius: {
|
|
64
|
+
lg: 'var(--radius)',
|
|
65
|
+
md: 'calc(var(--radius) - 2px)',
|
|
66
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
67
|
+
},
|
|
68
|
+
keyframes: {
|
|
69
|
+
'pulse-dot': {
|
|
70
|
+
'0%, 100%': { opacity: '1' },
|
|
71
|
+
'50%': { opacity: '0.4' },
|
|
72
|
+
},
|
|
73
|
+
heartbeat: {
|
|
74
|
+
'0%': { transform: 'scale(1)', opacity: '0.6' },
|
|
75
|
+
'50%': { transform: 'scale(1.8)', opacity: '0' },
|
|
76
|
+
'100%': { transform: 'scale(1)', opacity: '0' },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
animation: {
|
|
80
|
+
'pulse-dot': 'pulse-dot 2s ease-in-out infinite',
|
|
81
|
+
heartbeat: 'heartbeat 2s ease-out infinite',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
plugins: [],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default dashboardPreset
|