refactor(frontend): clean up code by consolidating multi-line JSX into single lines where feasible
- Refactored JSX elements to improve readability by collapsing multi-line props and attributes into single lines if their length permits. - Improved consistency in component imports by grouping and consolidating them. - No functional changes, purely restructuring for clarity and maintainability.
This commit is contained in:
@@ -248,12 +248,47 @@ const EVENT_TYPE_CONFIG: Record<
|
||||
};
|
||||
|
||||
const FILTER_CATEGORIES = [
|
||||
{ id: 'agent', label: 'Agent Actions', types: [EventType.AGENT_SPAWNED, EventType.AGENT_MESSAGE, EventType.AGENT_STATUS_CHANGED, EventType.AGENT_TERMINATED] },
|
||||
{ id: 'issue', label: 'Issues', types: [EventType.ISSUE_CREATED, EventType.ISSUE_UPDATED, EventType.ISSUE_ASSIGNED, EventType.ISSUE_CLOSED] },
|
||||
{
|
||||
id: 'agent',
|
||||
label: 'Agent Actions',
|
||||
types: [
|
||||
EventType.AGENT_SPAWNED,
|
||||
EventType.AGENT_MESSAGE,
|
||||
EventType.AGENT_STATUS_CHANGED,
|
||||
EventType.AGENT_TERMINATED,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'issue',
|
||||
label: 'Issues',
|
||||
types: [
|
||||
EventType.ISSUE_CREATED,
|
||||
EventType.ISSUE_UPDATED,
|
||||
EventType.ISSUE_ASSIGNED,
|
||||
EventType.ISSUE_CLOSED,
|
||||
],
|
||||
},
|
||||
{ id: 'sprint', label: 'Sprints', types: [EventType.SPRINT_STARTED, EventType.SPRINT_COMPLETED] },
|
||||
{ id: 'approval', label: 'Approvals', types: [EventType.APPROVAL_REQUESTED, EventType.APPROVAL_GRANTED, EventType.APPROVAL_DENIED] },
|
||||
{ id: 'workflow', label: 'Workflows', types: [EventType.WORKFLOW_STARTED, EventType.WORKFLOW_STEP_COMPLETED, EventType.WORKFLOW_COMPLETED, EventType.WORKFLOW_FAILED] },
|
||||
{ id: 'project', label: 'Projects', types: [EventType.PROJECT_CREATED, EventType.PROJECT_UPDATED, EventType.PROJECT_ARCHIVED] },
|
||||
{
|
||||
id: 'approval',
|
||||
label: 'Approvals',
|
||||
types: [EventType.APPROVAL_REQUESTED, EventType.APPROVAL_GRANTED, EventType.APPROVAL_DENIED],
|
||||
},
|
||||
{
|
||||
id: 'workflow',
|
||||
label: 'Workflows',
|
||||
types: [
|
||||
EventType.WORKFLOW_STARTED,
|
||||
EventType.WORKFLOW_STEP_COMPLETED,
|
||||
EventType.WORKFLOW_COMPLETED,
|
||||
EventType.WORKFLOW_FAILED,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'project',
|
||||
label: 'Projects',
|
||||
types: [EventType.PROJECT_CREATED, EventType.PROJECT_UPDATED, EventType.PROJECT_ARCHIVED],
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
@@ -266,25 +301,60 @@ function getEventConfig(event: ProjectEvent) {
|
||||
|
||||
// Fallback based on event category
|
||||
if (isAgentEvent(event)) {
|
||||
return { icon: Bot, label: event.type, color: 'text-blue-500', bgColor: 'bg-blue-100 dark:bg-blue-900' };
|
||||
return {
|
||||
icon: Bot,
|
||||
label: event.type,
|
||||
color: 'text-blue-500',
|
||||
bgColor: 'bg-blue-100 dark:bg-blue-900',
|
||||
};
|
||||
}
|
||||
if (isIssueEvent(event)) {
|
||||
return { icon: FileText, label: event.type, color: 'text-green-500', bgColor: 'bg-green-100 dark:bg-green-900' };
|
||||
return {
|
||||
icon: FileText,
|
||||
label: event.type,
|
||||
color: 'text-green-500',
|
||||
bgColor: 'bg-green-100 dark:bg-green-900',
|
||||
};
|
||||
}
|
||||
if (isSprintEvent(event)) {
|
||||
return { icon: PlayCircle, label: event.type, color: 'text-indigo-500', bgColor: 'bg-indigo-100 dark:bg-indigo-900' };
|
||||
return {
|
||||
icon: PlayCircle,
|
||||
label: event.type,
|
||||
color: 'text-indigo-500',
|
||||
bgColor: 'bg-indigo-100 dark:bg-indigo-900',
|
||||
};
|
||||
}
|
||||
if (isApprovalEvent(event)) {
|
||||
return { icon: AlertTriangle, label: event.type, color: 'text-orange-500', bgColor: 'bg-orange-100 dark:bg-orange-900' };
|
||||
return {
|
||||
icon: AlertTriangle,
|
||||
label: event.type,
|
||||
color: 'text-orange-500',
|
||||
bgColor: 'bg-orange-100 dark:bg-orange-900',
|
||||
};
|
||||
}
|
||||
if (isWorkflowEvent(event)) {
|
||||
return { icon: Workflow, label: event.type, color: 'text-cyan-500', bgColor: 'bg-cyan-100 dark:bg-cyan-900' };
|
||||
return {
|
||||
icon: Workflow,
|
||||
label: event.type,
|
||||
color: 'text-cyan-500',
|
||||
bgColor: 'bg-cyan-100 dark:bg-cyan-900',
|
||||
};
|
||||
}
|
||||
if (isProjectEvent(event)) {
|
||||
return { icon: Folder, label: event.type, color: 'text-teal-500', bgColor: 'bg-teal-100 dark:bg-teal-900' };
|
||||
return {
|
||||
icon: Folder,
|
||||
label: event.type,
|
||||
color: 'text-teal-500',
|
||||
bgColor: 'bg-teal-100 dark:bg-teal-900',
|
||||
};
|
||||
}
|
||||
|
||||
return { icon: Activity, label: event.type, color: 'text-gray-500', bgColor: 'bg-gray-100 dark:bg-gray-800' };
|
||||
return {
|
||||
icon: Activity,
|
||||
label: event.type,
|
||||
color: 'text-gray-500',
|
||||
bgColor: 'bg-gray-100 dark:bg-gray-800',
|
||||
};
|
||||
}
|
||||
|
||||
function getEventSummary(event: ProjectEvent): string {
|
||||
@@ -304,7 +374,9 @@ function getEventSummary(event: ProjectEvent): string {
|
||||
case EventType.ISSUE_UPDATED:
|
||||
return `Issue ${payload.issue_id || ''} updated`;
|
||||
case EventType.ISSUE_ASSIGNED:
|
||||
return payload.assignee_name ? `Assigned to ${payload.assignee_name}` : 'Issue assignment changed';
|
||||
return payload.assignee_name
|
||||
? `Assigned to ${payload.assignee_name}`
|
||||
: 'Issue assignment changed';
|
||||
case EventType.ISSUE_CLOSED:
|
||||
return payload.resolution ? `Closed: ${payload.resolution}` : 'Issue closed';
|
||||
case EventType.SPRINT_STARTED:
|
||||
@@ -318,11 +390,15 @@ function getEventSummary(event: ProjectEvent): string {
|
||||
case EventType.APPROVAL_DENIED:
|
||||
return payload.reason ? `Denied: ${payload.reason}` : 'Approval denied';
|
||||
case EventType.WORKFLOW_STARTED:
|
||||
return payload.workflow_type ? `${payload.workflow_type} workflow started` : 'Workflow started';
|
||||
return payload.workflow_type
|
||||
? `${payload.workflow_type} workflow started`
|
||||
: 'Workflow started';
|
||||
case EventType.WORKFLOW_STEP_COMPLETED:
|
||||
return `Step ${payload.step_number}/${payload.total_steps}: ${payload.step_name || 'completed'}`;
|
||||
case EventType.WORKFLOW_COMPLETED:
|
||||
return payload.duration_seconds ? `Completed in ${payload.duration_seconds}s` : 'Workflow completed';
|
||||
return payload.duration_seconds
|
||||
? `Completed in ${payload.duration_seconds}s`
|
||||
: 'Workflow completed';
|
||||
case EventType.WORKFLOW_FAILED:
|
||||
return payload.error_message ? String(payload.error_message) : 'Workflow failed';
|
||||
default:
|
||||
@@ -391,11 +467,7 @@ function ConnectionIndicator({ state, onReconnect, className }: ConnectionIndica
|
||||
return (
|
||||
<div className={cn('flex items-center gap-2', className)} data-testid="connection-indicator">
|
||||
<span
|
||||
className={cn(
|
||||
'h-2 w-2 rounded-full',
|
||||
config.color,
|
||||
config.pulse && 'animate-pulse'
|
||||
)}
|
||||
className={cn('h-2 w-2 rounded-full', config.color, config.pulse && 'animate-pulse')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">{config.label}</span>
|
||||
@@ -475,7 +547,10 @@ function FilterPanel({
|
||||
checked={showPendingOnly}
|
||||
onCheckedChange={(checked) => onShowPendingOnlyChange(checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="filter-pending" className="flex items-center gap-1 text-sm font-normal cursor-pointer">
|
||||
<Label
|
||||
htmlFor="filter-pending"
|
||||
className="flex items-center gap-1 text-sm font-normal cursor-pointer"
|
||||
>
|
||||
Show only pending approvals
|
||||
{pendingCount > 0 && (
|
||||
<Badge variant="destructive" className="text-xs">
|
||||
@@ -598,77 +673,85 @@ function EventItem({
|
||||
}}
|
||||
aria-label={expanded ? 'Collapse details' : 'Expand details'}
|
||||
>
|
||||
{expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expanded Details */}
|
||||
{expanded && (() => {
|
||||
const issueId = payload.issue_id as string | undefined;
|
||||
const pullRequest = payload.pullRequest as string | number | undefined;
|
||||
const documentUrl = payload.documentUrl as string | undefined;
|
||||
const progress = payload.progress as number | undefined;
|
||||
{expanded &&
|
||||
(() => {
|
||||
const issueId = payload.issue_id as string | undefined;
|
||||
const pullRequest = payload.pullRequest as string | number | undefined;
|
||||
const documentUrl = payload.documentUrl as string | undefined;
|
||||
const progress = payload.progress as number | undefined;
|
||||
|
||||
return (
|
||||
<div className="mt-3 rounded-md bg-muted/50 p-3 space-y-3" data-testid="event-details">
|
||||
{/* Issue/PR Links */}
|
||||
{issueId && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<CircleDot className="h-4 w-4" aria-hidden="true" />
|
||||
<span>Issue #{issueId}</span>
|
||||
</div>
|
||||
)}
|
||||
{pullRequest && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<GitPullRequest className="h-4 w-4" aria-hidden="true" />
|
||||
<span>PR #{String(pullRequest)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Document Links */}
|
||||
{documentUrl && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<ExternalLink className="h-4 w-4" aria-hidden="true" />
|
||||
<a href={documentUrl} className="text-primary hover:underline">
|
||||
{documentUrl}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress */}
|
||||
{progress !== undefined && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>Progress</span>
|
||||
<span>{progress}%</span>
|
||||
return (
|
||||
<div
|
||||
className="mt-3 rounded-md bg-muted/50 p-3 space-y-3"
|
||||
data-testid="event-details"
|
||||
>
|
||||
{/* Issue/PR Links */}
|
||||
{issueId && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<CircleDot className="h-4 w-4" aria-hidden="true" />
|
||||
<span>Issue #{issueId}</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-muted">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
)}
|
||||
{pullRequest && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<GitPullRequest className="h-4 w-4" aria-hidden="true" />
|
||||
<span>PR #{String(pullRequest)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Timestamp */}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{new Date(event.timestamp).toLocaleString()}
|
||||
</p>
|
||||
{/* Document Links */}
|
||||
{documentUrl && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<ExternalLink className="h-4 w-4" aria-hidden="true" />
|
||||
<a href={documentUrl} className="text-primary hover:underline">
|
||||
{documentUrl}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Raw Payload (for debugging) */}
|
||||
<details className="text-xs">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
View raw payload
|
||||
</summary>
|
||||
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2">
|
||||
{JSON.stringify(event.payload, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{/* Progress */}
|
||||
{progress !== undefined && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>Progress</span>
|
||||
<span>{progress}%</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-muted">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Timestamp */}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{new Date(event.timestamp).toLocaleString()}
|
||||
</p>
|
||||
|
||||
{/* Raw Payload (for debugging) */}
|
||||
<details className="text-xs">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
View raw payload
|
||||
</summary>
|
||||
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2">
|
||||
{JSON.stringify(event.payload, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Approval Actions */}
|
||||
{isPendingApproval && (onApprove || onReject) && (
|
||||
@@ -680,7 +763,12 @@ function EventItem({
|
||||
</Button>
|
||||
)}
|
||||
{onReject && (
|
||||
<Button variant="outline" size="sm" onClick={handleReject} data-testid="reject-button">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleReject}
|
||||
data-testid="reject-button"
|
||||
>
|
||||
<XCircle className="mr-2 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
@@ -712,7 +800,10 @@ function LoadingSkeleton() {
|
||||
|
||||
function EmptyState({ hasFilters }: { hasFilters: boolean }) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground" data-testid="empty-state">
|
||||
<div
|
||||
className="flex flex-col items-center justify-center py-12 text-muted-foreground"
|
||||
data-testid="empty-state"
|
||||
>
|
||||
<Activity className="h-12 w-12 mb-4" aria-hidden="true" />
|
||||
<h3 className="font-semibold">No activity found</h3>
|
||||
<p className="text-sm">
|
||||
@@ -894,7 +985,10 @@ export function ActivityFeed({
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{groupedEvents.map((group) => (
|
||||
<div key={group.label} data-testid={`event-group-${group.label.toLowerCase().replace(' ', '-')}`}>
|
||||
<div
|
||||
key={group.label}
|
||||
data-testid={`event-group-${group.label.toLowerCase().replace(' ', '-')}`}
|
||||
>
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">{group.label}</h3>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
|
||||
@@ -57,13 +57,19 @@ interface AgentTypeDetailProps {
|
||||
function AgentTypeStatusBadge({ isActive }: { isActive: boolean }) {
|
||||
if (isActive) {
|
||||
return (
|
||||
<Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" variant="outline">
|
||||
<Badge
|
||||
className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
||||
variant="outline"
|
||||
>
|
||||
Active
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Badge className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200" variant="outline">
|
||||
<Badge
|
||||
className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
||||
variant="outline"
|
||||
>
|
||||
Inactive
|
||||
</Badge>
|
||||
);
|
||||
@@ -139,9 +145,7 @@ export function AgentTypeDetail({
|
||||
<div className="py-12 text-center">
|
||||
<AlertTriangle className="mx-auto h-12 w-12 text-muted-foreground" />
|
||||
<h3 className="mt-4 font-semibold">Agent type not found</h3>
|
||||
<p className="text-muted-foreground">
|
||||
The requested agent type could not be found
|
||||
</p>
|
||||
<p className="text-muted-foreground">The requested agent type could not be found</p>
|
||||
<Button onClick={onBack} variant="outline" className="mt-4">
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Go Back
|
||||
@@ -265,9 +269,7 @@ export function AgentTypeDetail({
|
||||
<div
|
||||
key={server.id}
|
||||
className={`flex items-center justify-between rounded-lg border p-3 ${
|
||||
isEnabled
|
||||
? 'border-primary/20 bg-primary/5'
|
||||
: 'border-muted bg-muted/50'
|
||||
isEnabled ? 'border-primary/20 bg-primary/5' : 'border-muted bg-muted/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -284,9 +286,7 @@ export function AgentTypeDetail({
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{server.name}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{server.description}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">{server.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant={isEnabled ? 'default' : 'secondary'}>
|
||||
@@ -313,9 +313,7 @@ export function AgentTypeDetail({
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Primary Model</p>
|
||||
<p className="font-medium">
|
||||
{getModelDisplayName(agentType.primary_model)}
|
||||
</p>
|
||||
<p className="font-medium">{getModelDisplayName(agentType.primary_model)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Failover Model</p>
|
||||
@@ -355,9 +353,7 @@ export function AgentTypeDetail({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-center">
|
||||
<p className="text-4xl font-bold text-primary">
|
||||
{agentType.instance_count}
|
||||
</p>
|
||||
<p className="text-4xl font-bold text-primary">{agentType.instance_count}</p>
|
||||
<p className="text-sm text-muted-foreground">Active instances</p>
|
||||
</div>
|
||||
<Button variant="outline" className="mt-4 w-full" size="sm" disabled>
|
||||
|
||||
@@ -26,16 +26,7 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import {
|
||||
FileText,
|
||||
Cpu,
|
||||
Shield,
|
||||
MessageSquare,
|
||||
Sliders,
|
||||
Save,
|
||||
ArrowLeft,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { FileText, Cpu, Shield, MessageSquare, Sliders, Save, ArrowLeft, X } from 'lucide-react';
|
||||
import {
|
||||
agentTypeCreateSchema,
|
||||
type AgentTypeCreateFormValues,
|
||||
@@ -151,9 +142,7 @@ export function AgentTypeForm({
|
||||
{isEditing ? 'Edit Agent Type' : 'Create Agent Type'}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{isEditing
|
||||
? 'Modify agent type configuration'
|
||||
: 'Define a new agent type template'}
|
||||
{isEditing ? 'Modify agent type configuration' : 'Define a new agent type template'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -281,9 +270,7 @@ export function AgentTypeForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Expertise Areas</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Add skills and areas of expertise
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Add skills and areas of expertise</p>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder="e.g., System Design"
|
||||
@@ -325,9 +312,7 @@ export function AgentTypeForm({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Model Selection</CardTitle>
|
||||
<CardDescription>
|
||||
Choose the AI models that power this agent type
|
||||
</CardDescription>
|
||||
<CardDescription>Choose the AI models that power this agent type</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
@@ -358,9 +343,7 @@ export function AgentTypeForm({
|
||||
{errors.primary_model.message}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Main model used for this agent
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">Main model used for this agent</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fallback_model">Fallover Model</Label>
|
||||
@@ -420,9 +403,7 @@ export function AgentTypeForm({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
0 = deterministic, 2 = creative
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">0 = deterministic, 2 = creative</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="max_tokens">Max Tokens</Label>
|
||||
@@ -472,9 +453,7 @@ export function AgentTypeForm({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>MCP Server Permissions</CardTitle>
|
||||
<CardDescription>
|
||||
Configure which MCP servers this agent can access
|
||||
</CardDescription>
|
||||
<CardDescription>Configure which MCP servers this agent can access</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{AVAILABLE_MCP_SERVERS.map((server) => (
|
||||
@@ -508,8 +487,8 @@ export function AgentTypeForm({
|
||||
<CardHeader>
|
||||
<CardTitle>Personality Prompt</CardTitle>
|
||||
<CardDescription>
|
||||
Define the agent's personality, behavior, and communication style. This
|
||||
prompt shapes how the agent approaches tasks and interacts.
|
||||
Define the agent's personality, behavior, and communication style. This prompt
|
||||
shapes how the agent approaches tasks and interacts.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -535,9 +514,7 @@ export function AgentTypeForm({
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<span>
|
||||
Character count: {watch('personality_prompt')?.length || 0}
|
||||
</span>
|
||||
<span>Character count: {watch('personality_prompt')?.length || 0}</span>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<span className="text-xs">
|
||||
Tip: Be specific about expertise, communication style, and decision-making
|
||||
|
||||
@@ -41,13 +41,19 @@ interface AgentTypeListProps {
|
||||
function AgentTypeStatusBadge({ isActive }: { isActive: boolean }) {
|
||||
if (isActive) {
|
||||
return (
|
||||
<Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" variant="outline">
|
||||
<Badge
|
||||
className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
||||
variant="outline"
|
||||
>
|
||||
Active
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Badge className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200" variant="outline">
|
||||
<Badge
|
||||
className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
||||
variant="outline"
|
||||
>
|
||||
Inactive
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -12,13 +12,7 @@
|
||||
import { Component, type ReactNode } from 'react';
|
||||
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
@@ -59,25 +53,17 @@ function DefaultFallback({ error, onReset, showReset }: DefaultFallbackProps) {
|
||||
Something went wrong
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
An unexpected error occurred. Please try again or contact support if
|
||||
the problem persists.
|
||||
An unexpected error occurred. Please try again or contact support if the problem persists.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{error && (
|
||||
<div className="mb-4 rounded-md bg-muted p-3">
|
||||
<p className="font-mono text-sm text-muted-foreground">
|
||||
{error.message}
|
||||
</p>
|
||||
<p className="font-mono text-sm text-muted-foreground">{error.message}</p>
|
||||
</div>
|
||||
)}
|
||||
{showReset && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onReset}
|
||||
className="gap-2"
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={onReset} className="gap-2">
|
||||
<RefreshCw className="h-4 w-4" aria-hidden="true" />
|
||||
Try again
|
||||
</Button>
|
||||
@@ -108,10 +94,7 @@ function DefaultFallback({ error, onReset, showReset }: DefaultFallbackProps) {
|
||||
* </ErrorBoundary>
|
||||
* ```
|
||||
*/
|
||||
export class ErrorBoundary extends Component<
|
||||
ErrorBoundaryProps,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
@@ -142,13 +125,7 @@ export class ErrorBoundary extends Component<
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaultFallback
|
||||
error={error}
|
||||
onReset={this.handleReset}
|
||||
showReset={showReset}
|
||||
/>
|
||||
);
|
||||
return <DefaultFallback error={error} onReset={this.handleReset} showReset={showReset} />;
|
||||
}
|
||||
|
||||
return children;
|
||||
|
||||
@@ -153,7 +153,8 @@ export function ConnectionStatus({
|
||||
className={cn(
|
||||
'flex flex-col gap-3 rounded-lg border p-4',
|
||||
state === 'error' && 'border-destructive bg-destructive/5',
|
||||
state === 'connected' && 'border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950',
|
||||
state === 'connected' &&
|
||||
'border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950',
|
||||
className
|
||||
)}
|
||||
role="status"
|
||||
@@ -199,11 +200,7 @@ export function ConnectionStatus({
|
||||
{showErrorDetails && error && (
|
||||
<div className="rounded-md bg-destructive/10 p-3 text-sm">
|
||||
<p className="font-medium text-destructive">Error: {error.message}</p>
|
||||
{error.code && (
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
Code: {error.code}
|
||||
</p>
|
||||
)}
|
||||
{error.code && <p className="mt-1 text-muted-foreground">Code: {error.code}</p>}
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{new Date(error.timestamp).toLocaleTimeString()}
|
||||
</p>
|
||||
|
||||
@@ -250,17 +250,11 @@ function getEventSummary(event: ProjectEvent): string {
|
||||
? `Assigned to ${payload.assignee_name}`
|
||||
: 'Issue assignment changed';
|
||||
case EventType.ISSUE_CLOSED:
|
||||
return payload.resolution
|
||||
? `Closed: ${payload.resolution}`
|
||||
: 'Issue closed';
|
||||
return payload.resolution ? `Closed: ${payload.resolution}` : 'Issue closed';
|
||||
case EventType.SPRINT_STARTED:
|
||||
return payload.sprint_name
|
||||
? `Sprint "${payload.sprint_name}" started`
|
||||
: 'Sprint started';
|
||||
return payload.sprint_name ? `Sprint "${payload.sprint_name}" started` : 'Sprint started';
|
||||
case EventType.SPRINT_COMPLETED:
|
||||
return payload.sprint_name
|
||||
? `Sprint "${payload.sprint_name}" completed`
|
||||
: 'Sprint completed';
|
||||
return payload.sprint_name ? `Sprint "${payload.sprint_name}" completed` : 'Sprint completed';
|
||||
case EventType.APPROVAL_REQUESTED:
|
||||
return String(payload.description || 'Approval requested');
|
||||
case EventType.APPROVAL_GRANTED:
|
||||
@@ -278,9 +272,7 @@ function getEventSummary(event: ProjectEvent): string {
|
||||
? `Completed in ${payload.duration_seconds}s`
|
||||
: 'Workflow completed';
|
||||
case EventType.WORKFLOW_FAILED:
|
||||
return payload.error_message
|
||||
? String(payload.error_message)
|
||||
: 'Workflow failed';
|
||||
return payload.error_message ? String(payload.error_message) : 'Workflow failed';
|
||||
default:
|
||||
return event.type;
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ export function HeroSection({ onOpenDemoModal }: HeroSectionProps) {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
Opinionated, secure, and production-ready. Syndarix gives you the solid foundation
|
||||
you need to stop configuring and start shipping.{' '}
|
||||
Opinionated, secure, and production-ready. Syndarix gives you the solid foundation you
|
||||
need to stop configuring and start shipping.{' '}
|
||||
<span className="text-foreground font-medium">Start building features on day one.</span>
|
||||
</motion.p>
|
||||
|
||||
|
||||
@@ -74,11 +74,7 @@ function generateBreadcrumbs(pathname: string): BreadcrumbItem[] {
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
export function AppBreadcrumbs({
|
||||
items,
|
||||
showHome = true,
|
||||
className,
|
||||
}: AppBreadcrumbsProps) {
|
||||
export function AppBreadcrumbs({ items, showHome = true, className }: AppBreadcrumbsProps) {
|
||||
const pathname = usePathname();
|
||||
|
||||
// Use provided items or generate from pathname
|
||||
|
||||
@@ -49,11 +49,7 @@ export function AppHeader({
|
||||
{/* Left side - Logo and Project Switcher */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Logo - visible on mobile, hidden on desktop when sidebar is visible */}
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 lg:hidden"
|
||||
aria-label="Syndarix home"
|
||||
>
|
||||
<Link href="/" className="flex items-center gap-2 lg:hidden" aria-label="Syndarix home">
|
||||
<Image
|
||||
src="/logo-icon.svg"
|
||||
alt=""
|
||||
|
||||
@@ -73,11 +73,7 @@ export function AppLayout({
|
||||
{!hideBreadcrumbs && <AppBreadcrumbs items={breadcrumbs} />}
|
||||
|
||||
{/* Main content */}
|
||||
<main
|
||||
className={cn('flex-1', className)}
|
||||
id="main-content"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<main className={cn('flex-1', className)} id="main-content" tabIndex={-1}>
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
@@ -110,11 +106,7 @@ const maxWidthClasses: Record<string, string> = {
|
||||
full: 'max-w-full',
|
||||
};
|
||||
|
||||
export function PageContainer({
|
||||
children,
|
||||
maxWidth = '6xl',
|
||||
className,
|
||||
}: PageContainerProps) {
|
||||
export function PageContainer({ children, maxWidth = '6xl', className }: PageContainerProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -144,12 +136,7 @@ interface PageHeaderProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function PageHeader({
|
||||
title,
|
||||
description,
|
||||
actions,
|
||||
className,
|
||||
}: PageHeaderProps) {
|
||||
export function PageHeader({ title, description, actions, className }: PageHeaderProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -160,9 +147,7 @@ export function PageHeader({
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">{title}</h1>
|
||||
{description && (
|
||||
<p className="text-muted-foreground">{description}</p>
|
||||
)}
|
||||
{description && <p className="text-muted-foreground">{description}</p>}
|
||||
</div>
|
||||
{actions && <div className="flex items-center gap-2">{actions}</div>}
|
||||
</div>
|
||||
|
||||
@@ -98,9 +98,7 @@ export function ProjectSwitcher({
|
||||
className={cn('gap-2 min-w-[160px] justify-between', className)}
|
||||
data-testid="project-switcher-trigger"
|
||||
aria-label={
|
||||
currentProject
|
||||
? `Switch project, current: ${currentProject.name}`
|
||||
: 'Select project'
|
||||
currentProject ? `Switch project, current: ${currentProject.name}` : 'Select project'
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -112,11 +110,7 @@ export function ProjectSwitcher({
|
||||
<ChevronDown className="h-4 w-4 opacity-50" aria-hidden="true" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
className="w-[200px]"
|
||||
data-testid="project-switcher-menu"
|
||||
>
|
||||
<DropdownMenuContent align="start" className="w-[200px]" data-testid="project-switcher-menu">
|
||||
<DropdownMenuLabel>Projects</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{projects.map((project) => (
|
||||
|
||||
@@ -11,13 +11,7 @@ import { Link } from '@/lib/i18n/routing';
|
||||
import { usePathname } from '@/lib/i18n/routing';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
||||
import {
|
||||
FolderKanban,
|
||||
Bot,
|
||||
@@ -113,9 +107,7 @@ function NavLink({ item, collapsed, basePath = '' }: NavLinkProps) {
|
||||
const pathname = usePathname();
|
||||
const href = basePath ? `${basePath}${item.href}` : item.href;
|
||||
|
||||
const isActive = item.exact
|
||||
? pathname === href
|
||||
: pathname.startsWith(href);
|
||||
const isActive = item.exact ? pathname === href : pathname.startsWith(href);
|
||||
|
||||
const Icon = item.icon;
|
||||
|
||||
@@ -155,9 +147,7 @@ function SidebarContent({
|
||||
<div className="flex h-full flex-col">
|
||||
{/* Sidebar Header */}
|
||||
<div className="flex h-14 items-center justify-between border-b px-4">
|
||||
{!collapsed && (
|
||||
<span className="text-lg font-semibold text-foreground">Navigation</span>
|
||||
)}
|
||||
{!collapsed && <span className="text-lg font-semibold text-foreground">Navigation</span>}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -308,11 +298,7 @@ export function Sidebar({ projectSlug, className }: SidebarProps) {
|
||||
data-testid="sidebar"
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
<SidebarContent
|
||||
collapsed={collapsed}
|
||||
projectSlug={projectSlug}
|
||||
onToggle={handleToggle}
|
||||
/>
|
||||
<SidebarContent collapsed={collapsed} projectSlug={projectSlug} onToggle={handleToggle} />
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -20,14 +20,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import {
|
||||
User,
|
||||
LogOut,
|
||||
Shield,
|
||||
Lock,
|
||||
Monitor,
|
||||
UserCog,
|
||||
} from 'lucide-react';
|
||||
import { User, LogOut, Shield, Lock, Monitor, UserCog } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface UserMenuProps {
|
||||
@@ -76,20 +69,14 @@ export function UserMenu({ className }: UserMenuProps) {
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-56"
|
||||
align="end"
|
||||
data-testid="user-menu-content"
|
||||
>
|
||||
<DropdownMenuContent className="w-56" align="end" data-testid="user-menu-content">
|
||||
{/* User info header */}
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
{user.first_name} {user.last_name}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{user.email}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">{user.email}</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
@@ -143,11 +130,7 @@ export function UserMenu({ className }: UserMenuProps) {
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
href="/admin"
|
||||
className="cursor-pointer"
|
||||
data-testid="user-menu-admin"
|
||||
>
|
||||
<Link href="/admin" className="cursor-pointer" data-testid="user-menu-admin">
|
||||
<Shield className="mr-2 h-4 w-4" aria-hidden="true" />
|
||||
{t('adminPanel')}
|
||||
</Link>
|
||||
|
||||
@@ -9,13 +9,7 @@
|
||||
import { Bot, MoreVertical } from 'lucide-react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -228,11 +222,7 @@ export function AgentPanel({
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{agents.map((agent) => (
|
||||
<AgentListItem
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
onAction={onAgentAction}
|
||||
/>
|
||||
<AgentListItem key={agent.id} agent={agent} onAction={onAgentAction} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -60,16 +60,10 @@ export function AgentStatusIndicator({
|
||||
aria-label={`Status: ${config.label}`}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'inline-block rounded-full',
|
||||
sizeClasses[size],
|
||||
config.color
|
||||
)}
|
||||
className={cn('inline-block rounded-full', sizeClasses[size], config.color)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{showLabel && (
|
||||
<span className="text-xs text-muted-foreground">{config.label}</span>
|
||||
)}
|
||||
{showLabel && <span className="text-xs text-muted-foreground">{config.label}</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,15 +109,7 @@ export function BurndownChart({
|
||||
{data.map((d, i) => {
|
||||
const x = padding.left + (i / (data.length - 1)) * innerWidth;
|
||||
const y = padding.top + innerHeight - (d.remaining / maxPoints) * innerHeight;
|
||||
return (
|
||||
<circle
|
||||
key={i}
|
||||
cx={x}
|
||||
cy={y}
|
||||
r="2"
|
||||
className="fill-primary"
|
||||
/>
|
||||
);
|
||||
return <circle key={i} cx={x} cy={y} r="2" className="fill-primary" />;
|
||||
})}
|
||||
</svg>
|
||||
|
||||
|
||||
@@ -6,22 +6,10 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import {
|
||||
GitBranch,
|
||||
CircleDot,
|
||||
PlayCircle,
|
||||
Clock,
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
} from 'lucide-react';
|
||||
import { GitBranch, CircleDot, PlayCircle, Clock, AlertCircle, CheckCircle2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import type { IssueCountSummary } from './types';
|
||||
@@ -141,12 +129,7 @@ export function IssueSummary({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3" role="list" aria-label="Issue counts by status">
|
||||
<StatusRow
|
||||
icon={CircleDot}
|
||||
iconColor="text-blue-500"
|
||||
label="Open"
|
||||
count={summary.open}
|
||||
/>
|
||||
<StatusRow icon={CircleDot} iconColor="text-blue-500" label="Open" count={summary.open} />
|
||||
<StatusRow
|
||||
icon={PlayCircle}
|
||||
iconColor="text-yellow-500"
|
||||
@@ -177,12 +160,7 @@ export function IssueSummary({
|
||||
|
||||
{onViewAllIssues && (
|
||||
<div className="pt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
size="sm"
|
||||
onClick={onViewAllIssues}
|
||||
>
|
||||
<Button variant="outline" className="w-full" size="sm" onClick={onViewAllIssues}>
|
||||
View All Issues ({summary.total})
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -85,14 +85,12 @@ export function ProjectHeader({
|
||||
}
|
||||
|
||||
const showPauseButton = canPause && project.status === 'active';
|
||||
const showStartButton = canStart && project.status !== 'completed' && project.status !== 'archived';
|
||||
const showStartButton =
|
||||
canStart && project.status !== 'completed' && project.status !== 'archived';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col gap-4 md:flex-row md:items-start md:justify-between',
|
||||
className
|
||||
)}
|
||||
className={cn('flex flex-col gap-4 md:flex-row md:items-start md:justify-between', className)}
|
||||
data-testid="project-header"
|
||||
>
|
||||
{/* Project Info */}
|
||||
@@ -102,20 +100,13 @@ export function ProjectHeader({
|
||||
<ProjectStatusBadge status={project.status} />
|
||||
<AutonomyBadge level={project.autonomy_level} />
|
||||
</div>
|
||||
{project.description && (
|
||||
<p className="text-muted-foreground">{project.description}</p>
|
||||
)}
|
||||
{project.description && <p className="text-muted-foreground">{project.description}</p>}
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{onSettings && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onSettings}
|
||||
aria-label="Project settings"
|
||||
>
|
||||
<Button variant="ghost" size="icon" onClick={onSettings} aria-label="Project settings">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -19,12 +19,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import type { ActivityItem } from './types';
|
||||
|
||||
@@ -104,9 +99,7 @@ function ActivityItemRow({ activity, onActionClick }: ActivityItemRowProps) {
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm">
|
||||
{activity.agent && (
|
||||
<span className="font-medium">{activity.agent}</span>
|
||||
)}{' '}
|
||||
{activity.agent && <span className="font-medium">{activity.agent}</span>}{' '}
|
||||
<span className="text-muted-foreground">{activity.message}</span>
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">{timestamp}</p>
|
||||
|
||||
@@ -9,13 +9,7 @@
|
||||
import { TrendingUp, Calendar } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -188,10 +182,7 @@ export function SprintProgress({
|
||||
</div>
|
||||
|
||||
{availableSprints.length > 1 && onSprintChange && (
|
||||
<Select
|
||||
value={selectedSprintId || sprint.id}
|
||||
onValueChange={onSprintChange}
|
||||
>
|
||||
<Select value={selectedSprintId || sprint.id} onValueChange={onSprintChange}>
|
||||
<SelectTrigger className="w-32" aria-label="Select sprint">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@@ -231,16 +222,8 @@ export function SprintProgress({
|
||||
label="In Progress"
|
||||
colorClass="text-blue-600"
|
||||
/>
|
||||
<StatCard
|
||||
value={sprint.blocked_issues}
|
||||
label="Blocked"
|
||||
colorClass="text-red-600"
|
||||
/>
|
||||
<StatCard
|
||||
value={sprint.todo_issues}
|
||||
label="To Do"
|
||||
colorClass="text-gray-600"
|
||||
/>
|
||||
<StatCard value={sprint.blocked_issues} label="Blocked" colorClass="text-red-600" />
|
||||
<StatCard value={sprint.todo_issues} label="To Do" colorClass="text-gray-600" />
|
||||
</div>
|
||||
|
||||
{/* Burndown Chart */}
|
||||
|
||||
@@ -81,9 +81,7 @@ export function AutonomyBadge({ level, showDescription = false, className }: Aut
|
||||
<Badge variant="secondary" className={cn('gap-1', className)} title={config.description}>
|
||||
<CircleDot className="h-3 w-3" aria-hidden="true" />
|
||||
{config.label}
|
||||
{showDescription && (
|
||||
<span className="text-muted-foreground"> - {config.description}</span>
|
||||
)}
|
||||
{showDescription && <span className="text-muted-foreground"> - {config.description}</span>}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,13 @@ export interface IssueCountSummary {
|
||||
|
||||
export interface ActivityItem {
|
||||
id: string;
|
||||
type: 'agent_message' | 'issue_update' | 'agent_status' | 'approval_request' | 'sprint_event' | 'system';
|
||||
type:
|
||||
| 'agent_message'
|
||||
| 'issue_update'
|
||||
| 'agent_status'
|
||||
| 'approval_request'
|
||||
| 'sprint_event'
|
||||
| 'system';
|
||||
agent?: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
|
||||
@@ -73,16 +73,13 @@ export function ProjectWizard({ locale, className }: ProjectWizardProps) {
|
||||
mutationFn: async (projectData: ProjectCreateData): Promise<ProjectResponse> => {
|
||||
// Call the projects API endpoint
|
||||
// Note: The API client already handles authentication via interceptors
|
||||
const response = await apiClient.instance.post<ProjectResponse>(
|
||||
'/api/v1/projects',
|
||||
{
|
||||
name: projectData.name,
|
||||
slug: projectData.slug,
|
||||
description: projectData.description,
|
||||
autonomy_level: projectData.autonomy_level,
|
||||
settings: projectData.settings,
|
||||
}
|
||||
);
|
||||
const response = await apiClient.instance.post<ProjectResponse>('/api/v1/projects', {
|
||||
name: projectData.name,
|
||||
slug: projectData.slug,
|
||||
description: projectData.description,
|
||||
autonomy_level: projectData.autonomy_level,
|
||||
settings: projectData.settings,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
@@ -123,7 +120,10 @@ export function ProjectWizard({ locale, className }: ProjectWizardProps) {
|
||||
<Card className="text-center">
|
||||
<CardContent className="space-y-6 p-8">
|
||||
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-green-100 dark:bg-green-900">
|
||||
<CheckCircle2 className="h-8 w-8 text-green-600 dark:text-green-400" aria-hidden="true" />
|
||||
<CheckCircle2
|
||||
className="h-8 w-8 text-green-600 dark:text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold">Project Created Successfully!</h2>
|
||||
@@ -192,10 +192,7 @@ export function ProjectWizard({ locale, className }: ProjectWizardProps) {
|
||||
<ArrowRight className="ml-2 h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleCreate}
|
||||
disabled={createProjectMutation.isPending}
|
||||
>
|
||||
<Button onClick={handleCreate} disabled={createProjectMutation.isPending}>
|
||||
{createProjectMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
|
||||
|
||||
@@ -29,7 +29,12 @@ export function StepIndicator({ currentStep, isScriptMode, className }: StepIndi
|
||||
</span>
|
||||
<span>{steps[displayStep - 1]}</span>
|
||||
</div>
|
||||
<div className="flex gap-1" role="progressbar" aria-valuenow={displayStep} aria-valuemax={totalSteps}>
|
||||
<div
|
||||
className="flex gap-1"
|
||||
role="progressbar"
|
||||
aria-valuenow={displayStep}
|
||||
aria-valuemax={totalSteps}
|
||||
>
|
||||
{Array.from({ length: totalSteps }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
|
||||
@@ -18,9 +18,4 @@ export type {
|
||||
} from './types';
|
||||
|
||||
// Re-export constants
|
||||
export {
|
||||
complexityOptions,
|
||||
clientModeOptions,
|
||||
autonomyOptions,
|
||||
WIZARD_STEPS,
|
||||
} from './constants';
|
||||
export { complexityOptions, clientModeOptions, autonomyOptions, WIZARD_STEPS } from './constants';
|
||||
|
||||
@@ -11,13 +11,7 @@ import { Bot, User, MessageSquare, Sparkles } from 'lucide-react';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
||||
@@ -11,12 +11,7 @@
|
||||
import { Check, AlertCircle } from 'lucide-react';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SelectableCard } from '../SelectableCard';
|
||||
import { autonomyOptions } from '../constants';
|
||||
|
||||
@@ -34,7 +34,11 @@ export function ClientModeStep({ state, updateState }: ClientModeStepProps) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2" role="radiogroup" aria-label="Client interaction mode options">
|
||||
<div
|
||||
className="grid gap-6 md:grid-cols-2"
|
||||
role="radiogroup"
|
||||
aria-label="Client interaction mode options"
|
||||
>
|
||||
{clientModeOptions.map((option) => {
|
||||
const Icon = option.icon;
|
||||
const isSelected = state.clientMode === option.id;
|
||||
|
||||
@@ -39,7 +39,11 @@ export function ComplexityStep({ state, updateState }: ComplexityStepProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2" role="radiogroup" aria-label="Project complexity options">
|
||||
<div
|
||||
className="grid gap-4 md:grid-cols-2"
|
||||
role="radiogroup"
|
||||
aria-label="Project complexity options"
|
||||
>
|
||||
{complexityOptions.map((option) => {
|
||||
const Icon = option.icon;
|
||||
const isSelected = state.complexity === option.id;
|
||||
|
||||
@@ -8,12 +8,7 @@
|
||||
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { complexityOptions, clientModeOptions, autonomyOptions } from '../constants';
|
||||
import type { WizardState } from '../types';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user