Updates to get Docker working and adding Claude OAUTH token variable, and finish of the style guide mockup.

This commit is contained in:
sean-eskerium
2025-10-25 16:29:53 -04:00
parent 95791456cd
commit 4025f88ee9
8 changed files with 550 additions and 98 deletions

View File

@@ -31,6 +31,9 @@ LOG_LEVEL=INFO
# Get your API key from: https://console.anthropic.com/
# Required for the agent work orders service to execute Claude CLI commands
ANTHROPIC_API_KEY=
# Generate an OAUTH token in terminal and it will use your Claude OAUTH token from your subscription.
CLAUDE_CODE_OAUTH_TOKEN=
# GitHub Personal Access Token (Required for Agent Work Orders PR creation)
# Get your token from: https://github.com/settings/tokens
@@ -55,7 +58,7 @@ ARCHON_DOCS_PORT=3838
# Default: false (feature disabled)
# Set to "true" to enable: ENABLE_AGENT_WORK_ORDERS=true
# When enabled, requires Claude API key and GitHub PAT (see above)
ENABLE_AGENT_WORK_ORDERS=false
ENABLE_AGENT_WORK_ORDERS=true
# Agent Work Orders Service Configuration (Optional)
# Only needed if ENABLE_AGENT_WORK_ORDERS=true

View File

@@ -5,6 +5,7 @@ import { Button } from "@/features/ui/primitives/button";
import { Card } from "@/features/ui/primitives/card";
import { cn } from "@/features/ui/primitives/styles";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/features/ui/primitives/tooltip";
import { RealTimeStatsExample } from "./components/RealTimeStatsExample";
import { StepHistoryCard } from "./components/StepHistoryCard";
import { WorkflowStepButton } from "./components/WorkflowStepButton";
@@ -116,6 +117,9 @@ export const AgentWorkOrderExample = () => {
collapsible history, and integrated document editing for human-in-the-loop approval.
</p>
{/* Real-Time Execution Stats */}
<RealTimeStatsExample status="plan" stepNumber={2} />
{/* Workflow Progress Bar */}
<Card blur="md" transparency="light" edgePosition="top" edgeColor="cyan" size="lg" className="overflow-visible">
<div className="flex items-center justify-between mb-6">

View File

@@ -1,6 +1,8 @@
import {
Activity,
CheckCircle2,
ChevronDown,
ChevronUp,
Clock,
Copy,
Eye,
@@ -10,6 +12,7 @@ import {
Pin,
Play,
Plus,
Search,
Trash2,
} from "lucide-react";
import { useState } from "react";
@@ -24,6 +27,7 @@ import { SelectableCard } from "@/features/ui/primitives/selectable-card";
import { cn } from "@/features/ui/primitives/styles";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/features/ui/primitives/tooltip";
import { AgentWorkOrderExample } from "./AgentWorkOrderExample";
import { RealTimeStatsExample } from "./components/RealTimeStatsExample";
const MOCK_REPOSITORIES = [
{
@@ -69,7 +73,7 @@ interface WorkOrder {
const MOCK_WORK_ORDERS: WorkOrder[] = [
{
id: "wo-1",
id: "wo-1dc27d9e",
repositoryId: "1",
repositoryName: "archon-frontend",
request: "Add dark mode toggle to settings page",
@@ -78,7 +82,7 @@ const MOCK_WORK_ORDERS: WorkOrder[] = [
createdAt: "2024-01-15T10:30:00Z",
},
{
id: "wo-2",
id: "wo-2af8b3c1",
repositoryId: "1",
repositoryName: "archon-frontend",
request: "Refactor navigation component to use new design system",
@@ -87,7 +91,7 @@ const MOCK_WORK_ORDERS: WorkOrder[] = [
createdAt: "2024-01-15T09:15:00Z",
},
{
id: "wo-3",
id: "wo-4e372af3",
repositoryId: "2",
repositoryName: "archon-backend",
request: "Implement caching layer for API responses",
@@ -96,7 +100,7 @@ const MOCK_WORK_ORDERS: WorkOrder[] = [
createdAt: "2024-01-14T16:45:00Z",
},
{
id: "wo-4",
id: "wo-8b91f2d6",
repositoryId: "2",
repositoryName: "archon-backend",
request: "Add rate limiting to authentication endpoints",
@@ -105,7 +109,7 @@ const MOCK_WORK_ORDERS: WorkOrder[] = [
createdAt: "2024-01-14T14:20:00Z",
},
{
id: "wo-5",
id: "wo-5c7d4a89",
repositoryId: "1",
repositoryName: "archon-frontend",
request: "Fix responsive layout issues on mobile devices",
@@ -114,7 +118,7 @@ const MOCK_WORK_ORDERS: WorkOrder[] = [
createdAt: "2024-01-13T11:00:00Z",
},
{
id: "wo-6",
id: "wo-9f3e1b5a",
repositoryId: "3",
repositoryName: "archon-docs",
request: "Update API documentation with new endpoints",
@@ -126,7 +130,7 @@ const MOCK_WORK_ORDERS: WorkOrder[] = [
export const AgentWorkOrderLayoutExample = () => {
const [selectedRepositoryId, setSelectedRepositoryId] = useState("1");
const [layoutMode, setLayoutMode] = useState<"horizontal" | "sidebar">("horizontal");
const [layoutMode, setLayoutMode] = useState<"horizontal" | "sidebar">("sidebar");
const [sidebarExpanded, setSidebarExpanded] = useState(true);
const [showAddRepoModal, setShowAddRepoModal] = useState(false);
const [showNewWorkOrderModal, setShowNewWorkOrderModal] = useState(false);
@@ -134,6 +138,7 @@ export const AgentWorkOrderLayoutExample = () => {
const [activeTab, setActiveTab] = useState<string>("all");
const [showDetailView, setShowDetailView] = useState(false);
const [selectedWorkOrderId, setSelectedWorkOrderId] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const selectedRepository = MOCK_REPOSITORIES.find((r) => r.id === selectedRepositoryId);
const selectedWorkOrder = workOrders.find((wo) => wo.id === selectedWorkOrderId);
@@ -183,9 +188,42 @@ export const AgentWorkOrderLayoutExample = () => {
return (
<div className="space-y-6">
{/* Layout Mode Toggle */}
<div className="flex justify-end">
<div className="flex gap-1 p-1 bg-black/30 rounded-lg border border-white/10">
{/* Header Section */}
<div className="flex items-center justify-between gap-4">
{/* Title */}
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Agent Work Orders</h1>
{/* Search Bar */}
<div className="relative flex-1 max-w-md">
<Search
className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500"
aria-hidden="true"
/>
<Input
type="text"
placeholder="Search repositories..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
aria-label="Search repositories"
/>
</div>
{/* View Toggle - Sidebar is default/primary */}
<div className="flex gap-1 p-1 bg-black/30 dark:bg-white/10 rounded-lg border border-white/10 dark:border-gray-700">
<Button
variant="ghost"
size="sm"
onClick={() => setLayoutMode("sidebar")}
className={cn(
"px-3",
layoutMode === "sidebar" && "bg-purple-500/20 dark:bg-purple-500/30 text-purple-400 dark:text-purple-300",
)}
aria-label="Switch to sidebar layout"
aria-pressed={layoutMode === "sidebar"}
>
<List className="w-4 h-4" aria-hidden="true" />
</Button>
<Button
variant="ghost"
size="sm"
@@ -200,22 +238,18 @@ export const AgentWorkOrderLayoutExample = () => {
>
<LayoutGrid className="w-4 h-4" aria-hidden="true" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setLayoutMode("sidebar")}
className={cn(
"px-3",
layoutMode === "sidebar" && "bg-purple-500/20 dark:bg-purple-500/30 text-purple-400 dark:text-purple-300",
)}
aria-label="Switch to sidebar layout"
aria-pressed={layoutMode === "sidebar"}
>
<List className="w-4 h-4" aria-hidden="true" />
</Button>
</div>
{/* New Repo Button */}
<Button variant="cyan" onClick={() => setShowAddRepoModal(true)} aria-label="Add new repository">
<Plus className="w-4 h-4 mr-2" aria-hidden="true" />
New Repo
</Button>
</div>
{/* Add Repository Modal */}
<AddRepositoryModal open={showAddRepoModal} onOpenChange={setShowAddRepoModal} />
{layoutMode === "horizontal" ? (
<>
{/* Horizontal Repository Cards - ONLY cards scroll, not whole page */}
@@ -233,8 +267,6 @@ export const AgentWorkOrderLayoutExample = () => {
}}
/>
))}
{/* Add Repository Button */}
<AddRepositoryModal open={showAddRepoModal} onOpenChange={setShowAddRepoModal} />
</div>
</div>
</div>
@@ -756,8 +788,9 @@ const WorkOrdersTableView = ({
<thead>
<tr className="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 border-b-2 border-gray-200 dark:border-gray-700">
<th className="w-12" aria-label="Status indicator" />
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
Work Order ID
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">WO ID</th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300 w-40">
Repository
</th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
Request Summary
@@ -783,7 +816,7 @@ const WorkOrdersTableView = ({
);
};
// Work Order Row with status-based styling
// Work Order Row with status-based styling and expandable real-time stats
const WorkOrderRow = ({
workOrder,
index,
@@ -795,103 +828,165 @@ const WorkOrderRow = ({
onStart: () => void;
onViewDetails: () => void;
}) => {
const [isExpanded, setIsExpanded] = useState(false);
// Status colors - STATIC lookup with all properties
const statusColors: Record<
WorkOrderStatus,
{ color: "pink" | "cyan" | "blue" | "orange" | "purple" | "green"; edge: string; glow: string; label: string }
{
color: "pink" | "cyan" | "blue" | "orange" | "purple" | "green";
edge: string;
glow: string;
label: string;
stepNumber: number;
}
> = {
pending: {
color: "pink",
edge: "bg-pink-500",
glow: "rgba(236,72,153,0.5)",
label: "Pending",
stepNumber: 0,
},
create_branch: {
color: "cyan",
edge: "bg-cyan-500",
glow: "rgba(34,211,238,0.5)",
label: "+ Branch",
stepNumber: 1,
},
plan: {
color: "blue",
edge: "bg-blue-500",
glow: "rgba(59,130,246,0.5)",
label: "Planning",
stepNumber: 2,
},
execute: {
color: "orange",
edge: "bg-orange-500",
glow: "rgba(249,115,22,0.5)",
label: "Executing",
stepNumber: 3,
},
commit: {
color: "purple",
edge: "bg-purple-500",
glow: "rgba(168,85,247,0.5)",
label: "Commit",
stepNumber: 4,
},
create_pr: {
color: "green",
edge: "bg-green-500",
glow: "rgba(34,197,94,0.5)",
label: "Create PR",
stepNumber: 5,
},
};
const colors = statusColors[workOrder.status];
const canExpand = workOrder.status !== "pending";
const handleStart = () => {
setIsExpanded(true); // Auto-expand when started
onStart();
};
return (
<tr
className={cn(
"group transition-all duration-200",
index % 2 === 0 ? "bg-white/50 dark:bg-black/50" : "bg-gray-50/80 dark:bg-gray-900/30",
"hover:bg-gradient-to-r hover:from-cyan-50/70 hover:to-purple-50/70 dark:hover:from-cyan-900/20 dark:hover:to-purple-900/20",
"border-b border-gray-200 dark:border-gray-800",
)}
>
{/* Status indicator - glowing circle */}
<td className="px-3 py-2 w-12">
<div className="flex items-center justify-center">
<div className={cn("w-3 h-3 rounded-full", colors.edge)} style={{ boxShadow: `0 0 8px ${colors.glow}` }} />
</div>
</td>
{/* Work Order ID */}
<td className="px-4 py-2">
<span className="font-mono text-sm text-gray-700 dark:text-gray-300">{workOrder.id}</span>
</td>
{/* Request Summary */}
<td className="px-4 py-2">
<p className="text-sm text-gray-900 dark:text-white line-clamp-2">{workOrder.request}</p>
</td>
{/* Status Badge - using StatPill */}
<td className="px-4 py-2 w-32">
<StatPill color={colors.color} value={colors.label} size="sm" />
</td>
{/* Actions */}
<td className="px-4 py-2 w-32">
{workOrder.status === "pending" ? (
<Button onClick={onStart} size="xs" variant="green" className="w-full text-xs" aria-label="Start work order">
<Play className="w-3 h-3 mr-1" aria-hidden="true" />
Start
</Button>
) : (
<Button
onClick={onViewDetails}
size="xs"
variant="blue"
className="w-full text-xs"
aria-label="Observe work order details"
>
<Eye className="w-3 h-3 mr-1" aria-hidden="true" />
Observe
</Button>
<>
<tr
className={cn(
"group transition-all duration-200",
index % 2 === 0 ? "bg-white/50 dark:bg-black/50" : "bg-gray-50/80 dark:bg-gray-900/30",
"hover:bg-gradient-to-r hover:from-cyan-50/70 hover:to-purple-50/70 dark:hover:from-cyan-900/20 dark:hover:to-purple-900/20",
"border-b border-gray-200 dark:border-gray-800",
)}
</td>
</tr>
>
{/* Status indicator - glowing circle with optional collapse button */}
<td className="px-3 py-2 w-12">
<div className="flex items-center justify-center gap-1">
{canExpand && (
<button
type="button"
onClick={() => setIsExpanded(!isExpanded)}
className="p-0.5 hover:bg-gray-200 dark:hover:bg-gray-700 rounded transition-colors"
aria-label={isExpanded ? "Collapse details" : "Expand details"}
aria-expanded={isExpanded}
>
{isExpanded ? (
<ChevronUp className="w-3 h-3 text-gray-600 dark:text-gray-400" aria-hidden="true" />
) : (
<ChevronDown className="w-3 h-3 text-gray-600 dark:text-gray-400" aria-hidden="true" />
)}
</button>
)}
<div className={cn("w-3 h-3 rounded-full", colors.edge)} style={{ boxShadow: `0 0 8px ${colors.glow}` }} />
</div>
</td>
{/* Work Order ID */}
<td className="px-4 py-2">
<span className="font-mono text-sm text-gray-700 dark:text-gray-300">{workOrder.id}</span>
</td>
{/* Repository */}
<td className="px-4 py-2 w-40">
<span className="text-sm text-gray-900 dark:text-white">{workOrder.repositoryName}</span>
</td>
{/* Request Summary */}
<td className="px-4 py-2">
<p className="text-sm text-gray-900 dark:text-white line-clamp-2">{workOrder.request}</p>
</td>
{/* Status Badge - using StatPill */}
<td className="px-4 py-2 w-32">
<StatPill color={colors.color} value={colors.label} size="sm" />
</td>
{/* Actions */}
<td className="px-4 py-2 w-32">
{workOrder.status === "pending" ? (
<Button
onClick={handleStart}
size="xs"
variant="green"
className="w-full text-xs"
aria-label="Start work order"
>
<Play className="w-3 h-3 mr-1" aria-hidden="true" />
Start
</Button>
) : (
<Button
onClick={onViewDetails}
size="xs"
variant="blue"
className="w-full text-xs"
aria-label="View work order details"
>
<Eye className="w-3 h-3 mr-1" aria-hidden="true" />
Details
</Button>
)}
</td>
</tr>
{/* Expanded row with real-time stats */}
{isExpanded && canExpand && (
<tr
className={cn(
index % 2 === 0 ? "bg-white/50 dark:bg-black/50" : "bg-gray-50/80 dark:bg-gray-900/30",
"border-b border-gray-200 dark:border-gray-800",
)}
>
<td colSpan={6} className="px-4 py-4">
<RealTimeStatsExample status={workOrder.status} stepNumber={colors.stepNumber} />
</td>
</tr>
)}
</>
);
};
@@ -926,23 +1021,6 @@ const AddRepositoryModal = ({ open, onOpenChange }: { open: boolean; onOpenChang
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTrigger asChild>
<button
type="button"
className={cn(
"w-72 min-h-[180px] flex flex-col items-center justify-center shrink-0",
"rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-700",
"hover:border-cyan-400 dark:hover:border-cyan-500",
"transition-colors duration-200",
"bg-white/30 dark:bg-black/20",
"backdrop-blur-sm",
)}
aria-label="Add repository"
>
<Plus className="w-8 h-8 text-gray-400 dark:text-gray-500 mb-2" aria-hidden="true" />
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">Add Repository</span>
</button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Repository</DialogTitle>

View File

@@ -0,0 +1,212 @@
import { Trash2 } from "lucide-react";
import { useState } from "react";
import { Button } from "@/features/ui/primitives/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/features/ui/primitives/select";
import { cn } from "@/features/ui/primitives/styles";
import { Switch } from "@/features/ui/primitives/switch";
interface ExecutionLogsExampleProps {
/** Work order status to generate appropriate mock logs */
status: string;
}
interface MockLog {
timestamp: string;
level: "info" | "warning" | "error" | "debug";
event: string;
step?: string;
progress?: string;
}
/**
* Get color class for log level badge - STATIC lookup
*/
const logLevelColors: Record<string, string> = {
info: "bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-400/30",
warning: "bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-400/30",
error: "bg-red-500/20 text-red-600 dark:text-red-400 border-red-400/30",
debug: "bg-gray-500/20 text-gray-600 dark:text-gray-400 border-gray-400/30",
};
/**
* Format timestamp to relative time
*/
function formatRelativeTime(timestamp: string): string {
const now = Date.now();
const logTime = new Date(timestamp).getTime();
const diffSeconds = Math.floor((now - logTime) / 1000);
if (diffSeconds < 60) return `${diffSeconds}s ago`;
if (diffSeconds < 3600) return `${Math.floor(diffSeconds / 60)}m ago`;
return `${Math.floor(diffSeconds / 3600)}h ago`;
}
/**
* Individual log entry component
*/
function LogEntryRow({ log }: { log: MockLog }) {
const colorClass = logLevelColors[log.level] || logLevelColors.debug;
return (
<div className="flex items-start gap-2 py-1 px-2 hover:bg-white/5 dark:hover:bg-black/20 rounded font-mono text-sm">
<span className="text-gray-500 dark:text-gray-400 text-xs whitespace-nowrap">
{formatRelativeTime(log.timestamp)}
</span>
<span className={cn("px-1.5 py-0.5 rounded text-xs border uppercase whitespace-nowrap", colorClass)}>
{log.level}
</span>
{log.step && <span className="text-cyan-600 dark:text-cyan-400 text-xs whitespace-nowrap">[{log.step}]</span>}
<span className="text-gray-900 dark:text-gray-300 flex-1">{log.event}</span>
{log.progress && (
<span className="text-gray-500 dark:text-gray-400 text-xs whitespace-nowrap">{log.progress}</span>
)}
</div>
);
}
export function ExecutionLogsExample({ status }: ExecutionLogsExampleProps) {
const [autoScroll, setAutoScroll] = useState(true);
const [levelFilter, setLevelFilter] = useState<string>("all");
// Generate mock logs based on status
const generateMockLogs = (): MockLog[] => {
const now = Date.now();
const baseTime = now - 300000; // 5 minutes ago
const logs: MockLog[] = [
{ timestamp: new Date(baseTime).toISOString(), level: "info", event: "workflow_started" },
{ timestamp: new Date(baseTime + 1000).toISOString(), level: "info", event: "sandbox_setup_started" },
{
timestamp: new Date(baseTime + 3000).toISOString(),
level: "info",
event: "repository_cloned",
step: "setup",
},
{ timestamp: new Date(baseTime + 5000).toISOString(), level: "info", event: "sandbox_setup_completed" },
];
if (status !== "pending") {
logs.push(
{
timestamp: new Date(baseTime + 10000).toISOString(),
level: "info",
event: "step_started",
step: "create-branch",
progress: "1/5",
},
{
timestamp: new Date(baseTime + 12000).toISOString(),
level: "info",
event: "agent_command_started",
step: "create-branch",
},
{
timestamp: new Date(baseTime + 45000).toISOString(),
level: "info",
event: "branch_created",
step: "create-branch",
},
);
}
if (status === "plan" || status === "execute" || status === "commit" || status === "create_pr") {
logs.push(
{
timestamp: new Date(baseTime + 60000).toISOString(),
level: "info",
event: "step_started",
step: "planning",
progress: "2/5",
},
{
timestamp: new Date(baseTime + 120000).toISOString(),
level: "debug",
event: "analyzing_codebase",
step: "planning",
},
);
}
return logs;
};
const mockLogs = generateMockLogs();
const filteredLogs = levelFilter === "all" ? mockLogs : mockLogs.filter((log) => log.level === levelFilter);
return (
<div className="border border-white/10 dark:border-gray-700/30 rounded-lg overflow-hidden bg-black/20 dark:bg-white/5 backdrop-blur">
{/* Header with controls */}
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10 dark:border-gray-700/30 bg-gray-900/50 dark:bg-gray-800/30">
<div className="flex items-center gap-3">
<span className="font-semibold text-gray-900 dark:text-gray-300">Execution Logs</span>
{/* Live indicator */}
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-xs text-green-600 dark:text-green-400">Live</span>
</div>
<span className="text-xs text-gray-500 dark:text-gray-400">({filteredLogs.length} entries)</span>
</div>
{/* Controls */}
<div className="flex items-center gap-3">
{/* Level filter using proper Select primitive */}
<Select value={levelFilter} onValueChange={setLevelFilter}>
<SelectTrigger className="w-32 h-8 text-xs" aria-label="Filter log level">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Levels</SelectItem>
<SelectItem value="info">Info</SelectItem>
<SelectItem value="warning">Warning</SelectItem>
<SelectItem value="error">Error</SelectItem>
<SelectItem value="debug">Debug</SelectItem>
</SelectContent>
</Select>
{/* Auto-scroll toggle using Switch primitive */}
<div className="flex items-center gap-2">
<label htmlFor="auto-scroll-toggle" className="text-xs text-gray-700 dark:text-gray-300">
Auto-scroll:
</label>
<Switch
id="auto-scroll-toggle"
checked={autoScroll}
onCheckedChange={setAutoScroll}
aria-label="Toggle auto-scroll"
/>
<span
className={cn(
"text-xs font-medium",
autoScroll ? "text-cyan-600 dark:text-cyan-400" : "text-gray-500 dark:text-gray-400",
)}
>
{autoScroll ? "ON" : "OFF"}
</span>
</div>
{/* Clear logs button */}
<Button variant="ghost" size="xs" aria-label="Clear logs">
<Trash2 className="w-3 h-3" aria-hidden="true" />
</Button>
</div>
</div>
{/* Log content - scrollable area */}
<div className="max-h-96 overflow-y-auto bg-black/40 dark:bg-black/20">
{filteredLogs.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-gray-500 dark:text-gray-400">
<p>No logs match the current filter</p>
</div>
) : (
<div className="p-2">
{filteredLogs.map((log, index) => (
<LogEntryRow key={`${log.timestamp}-${index}`} log={log} />
))}
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,151 @@
import { Activity, ChevronDown, ChevronUp, Clock, TrendingUp } from "lucide-react";
import { useState } from "react";
import { Button } from "@/features/ui/primitives/button";
import { ExecutionLogsExample } from "./ExecutionLogsExample";
interface RealTimeStatsExampleProps {
/** Work order status for determining progress */
status: string;
/** Step number (1-5) */
stepNumber: number;
}
/**
* Format elapsed seconds to human-readable duration
*/
function formatDuration(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
if (hours > 0) {
return `${hours}h ${minutes}m ${secs}s`;
}
if (minutes > 0) {
return `${minutes}m ${secs}s`;
}
return `${secs}s`;
}
export function RealTimeStatsExample({ status, stepNumber }: RealTimeStatsExampleProps) {
const [showLogs, setShowLogs] = useState(false);
// Mock data based on status
const stepNames: Record<string, string> = {
create_branch: "create-branch",
plan: "planning",
execute: "execute",
commit: "commit",
create_pr: "create-pr",
};
const currentStep = stepNames[status] || "initializing";
const progressPct = (stepNumber / 5) * 100;
const mockElapsedSeconds = stepNumber * 120; // 2 minutes per step
const activities: Record<string, string> = {
create_branch: "Creating new branch for work order...",
plan: "Analyzing codebase and generating implementation plan...",
execute: "Writing code and applying changes...",
commit: "Committing changes to branch...",
create_pr: "Creating pull request on GitHub...",
};
const currentActivity = activities[status] || "Initializing workflow...";
return (
<div className="space-y-3">
<div className="border border-white/10 dark:border-gray-700/30 rounded-lg p-4 bg-black/20 dark:bg-white/5 backdrop-blur">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-300 mb-3 flex items-center gap-2">
<Activity className="w-4 h-4" aria-hidden="true" />
Real-Time Execution
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Current Step */}
<div className="space-y-1">
<div className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Current Step</div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-200">
{currentStep}
<span className="text-gray-500 dark:text-gray-400 ml-2">({stepNumber}/5)</span>
</div>
</div>
{/* Progress */}
<div className="space-y-1">
<div className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide flex items-center gap-1">
<TrendingUp className="w-3 h-3" aria-hidden="true" />
Progress
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<div className="flex-1 h-2 bg-gray-700 dark:bg-gray-200/20 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-cyan-500 to-blue-500 transition-all duration-500 ease-out"
style={{ width: `${progressPct}%` }}
/>
</div>
<span className="text-sm font-medium text-cyan-600 dark:text-cyan-400">{progressPct}%</span>
</div>
</div>
</div>
{/* Elapsed Time */}
<div className="space-y-1">
<div className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide flex items-center gap-1">
<Clock className="w-3 h-3" aria-hidden="true" />
Elapsed Time
</div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-200">
{formatDuration(mockElapsedSeconds)}
</div>
</div>
</div>
{/* Latest Activity with Status Indicator - at top */}
<div className="mt-4 pt-3 border-t border-white/10 dark:border-gray-700/30">
<div className="flex items-center justify-between gap-4">
<div className="flex items-start gap-2 flex-1 min-w-0">
<div className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide whitespace-nowrap">
Latest Activity:
</div>
<div className="text-sm text-gray-900 dark:text-gray-300 flex-1 truncate">{currentActivity}</div>
</div>
{/* Status Indicator - right side of Latest Activity */}
<div className="flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 flex-shrink-0">
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
<span>Running</span>
</div>
</div>
</div>
{/* Show Execution Logs button - at bottom */}
<div className="mt-3 pt-3 border-t border-white/10 dark:border-gray-700/30">
<Button
variant="ghost"
size="sm"
onClick={() => setShowLogs(!showLogs)}
className="w-full justify-center text-cyan-600 dark:text-cyan-400 hover:bg-cyan-500/10"
aria-label={showLogs ? "Hide execution logs" : "Show execution logs"}
aria-expanded={showLogs}
>
{showLogs ? (
<>
<ChevronUp className="w-4 h-4 mr-1" aria-hidden="true" />
Hide Execution Logs
</>
) : (
<>
<ChevronDown className="w-4 h-4 mr-1" aria-hidden="true" />
Show Execution Logs
</>
)}
</Button>
</div>
</div>
{/* Collapsible Execution Logs */}
{showLogs && <ExecutionLogsExample status={status} />}
</div>
);
}

View File

@@ -1,6 +1,5 @@
import { Briefcase, Database, FileText, FolderKanban, Navigation, Settings } from "lucide-react";
import { useState } from "react";
import { AgentWorkOrderExample } from "../layouts/AgentWorkOrderExample";
import { AgentWorkOrderLayoutExample } from "../layouts/AgentWorkOrderLayoutExample";
import { DocumentBrowserExample } from "../layouts/DocumentBrowserExample";
import { KnowledgeLayoutExample } from "../layouts/KnowledgeLayoutExample";

View File

@@ -172,6 +172,7 @@ services:
- SUPABASE_SERVICE_KEY=${SUPABASE_SERVICE_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
- CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-}
- LOGFIRE_TOKEN=${LOGFIRE_TOKEN:-}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- AGENT_WORK_ORDERS_PORT=${AGENT_WORK_ORDERS_PORT:-8053}

View File

@@ -59,6 +59,10 @@ RUN mkdir -p /repos /tmp/agent-work-orders && \
USER agentuser
RUN curl -fsSL https://claude.ai/install.sh | bash
# Configure git to use gh CLI for GitHub authentication
# This allows git clone to authenticate using GH_TOKEN environment variable
RUN git config --global credential.helper '!gh auth git-credential'
# Set environment variables
ENV PYTHONPATH="/app:$PYTHONPATH"
ENV PYTHONUNBUFFERED=1