@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/components.json +16 -0
  3. package/package.json +47 -0
  4. package/src/components/fleet/agent-card.tsx +62 -0
  5. package/src/components/fleet/fleet-overview.tsx +89 -0
  6. package/src/components/fleet/provider-icon.tsx +61 -0
  7. package/src/components/fleet/stat-card.tsx +43 -0
  8. package/src/components/fleet/status-dot.tsx +32 -0
  9. package/src/components/layout/bottom-bar.tsx +50 -0
  10. package/src/components/layout/shell.tsx +57 -0
  11. package/src/components/layout/sidebar.tsx +81 -0
  12. package/src/components/layout/top-bar.tsx +55 -0
  13. package/src/components/pipeline/pipeline-card.tsx +42 -0
  14. package/src/components/pipeline/pipeline-column.tsx +40 -0
  15. package/src/components/pipeline/pipeline-view.tsx +75 -0
  16. package/src/components/sessions/session-detail.tsx +132 -0
  17. package/src/components/sessions/session-list.tsx +109 -0
  18. package/src/components/sessions/session-timeline.tsx +54 -0
  19. package/src/components/sessions/token-chart.tsx +51 -0
  20. package/src/components/settings/settings-view.tsx +155 -0
  21. package/src/components/shared/empty-state.tsx +29 -0
  22. package/src/components/shared/logo.tsx +37 -0
  23. package/src/components/ui/badge.tsx +33 -0
  24. package/src/components/ui/button.tsx +54 -0
  25. package/src/components/ui/card.tsx +50 -0
  26. package/src/components/ui/dropdown-menu.tsx +77 -0
  27. package/src/components/ui/scroll-area.tsx +45 -0
  28. package/src/components/ui/separator.tsx +25 -0
  29. package/src/components/ui/sheet.tsx +88 -0
  30. package/src/components/ui/skeleton.tsx +12 -0
  31. package/src/components/ui/tabs.tsx +54 -0
  32. package/src/components/ui/tooltip.tsx +29 -0
  33. package/src/hooks/use-sessions.ts +13 -0
  34. package/src/hooks/use-stats.ts +13 -0
  35. package/src/hooks/use-workers.ts +17 -0
  36. package/src/index.ts +82 -0
  37. package/src/lib/format.ts +36 -0
  38. package/src/lib/status-config.ts +58 -0
  39. package/src/lib/utils.ts +6 -0
  40. package/src/lib/work-type-config.ts +53 -0
  41. package/src/pages/dashboard-page.tsx +7 -0
  42. package/src/pages/pipeline-page.tsx +7 -0
  43. package/src/pages/session-page.tsx +29 -0
  44. package/src/pages/settings-page.tsx +7 -0
  45. package/src/styles/globals.css +38 -0
  46. package/src/types/api.ts +45 -0
  47. 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
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -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,7 @@
1
+ 'use client'
2
+
3
+ import { FleetOverview } from '../components/fleet/fleet-overview'
4
+
5
+ export function DashboardPage() {
6
+ return <FleetOverview />
7
+ }
@@ -0,0 +1,7 @@
1
+ 'use client'
2
+
3
+ import { PipelineView } from '../components/pipeline/pipeline-view'
4
+
5
+ export function PipelinePage() {
6
+ return <PipelineView />
7
+ }
@@ -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,7 @@
1
+ 'use client'
2
+
3
+ import { SettingsView } from '../components/settings/settings-view'
4
+
5
+ export function SettingsPage() {
6
+ return <SettingsView />
7
+ }
@@ -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
+ }
@@ -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