Files
paseo/packages/voice-mobile/components/active-processes.tsx
T
2025-10-21 18:15:05 +02:00

170 lines
5.1 KiB
TypeScript

import { View, Text, Pressable, ScrollView } from 'react-native';
import type { AgentStatus } from '@voice-assistant/server/acp/types';
export interface ActiveProcessesProps {
agents: Array<{
id: string;
status: AgentStatus;
type: 'claude';
currentModeId?: string;
availableModes?: Array<{ id: string; name: string; description?: string | null }>;
}>;
commands: Array<{
id: string;
name: string;
workingDirectory: string;
currentCommand: string;
isDead: boolean;
exitCode: number | null;
}>;
activeProcessId: string | null;
activeProcessType: 'agent' | null;
onSelectAgent: (id: string) => void;
onBackToOrchestrator: () => void;
}
function getAgentStatusColor(status: AgentStatus): string {
switch (status) {
case 'initializing':
return '#f59e0b'; // orange
case 'ready':
return '#3b82f6'; // blue
case 'processing':
return '#fbbf24'; // yellow
case 'completed':
return '#22c55e'; // green
case 'failed':
return '#ef4444'; // red
case 'killed':
return '#9ca3af'; // gray
default:
return '#9ca3af';
}
}
function getModeName(modeId?: string, availableModes?: Array<{ id: string; name: string }>): string {
if (!modeId) return 'unknown';
const mode = availableModes?.find((m) => m.id === modeId);
return mode?.name || modeId;
}
function getModeColor(modeId?: string): string {
if (!modeId) return '#9ca3af'; // gray
// Color based on common mode types
if (modeId.includes('ask')) return '#f59e0b'; // orange - asks permission
if (modeId.includes('code')) return '#22c55e'; // green - writes code
if (modeId.includes('architect') || modeId.includes('plan')) return '#3b82f6'; // blue - plans
return '#9ca3af'; // gray - unknown
}
export function ActiveProcesses({
agents,
commands,
activeProcessId,
activeProcessType,
onSelectAgent,
onBackToOrchestrator,
}: ActiveProcessesProps) {
const hasActiveProcess = activeProcessId !== null && activeProcessType !== null;
if (agents.length === 0 && commands.length === 0) {
return null;
}
return (
<View className="bg-zinc-900 border-b border-zinc-800">
{/* Header with Back button */}
{hasActiveProcess && (
<View className="px-4 py-3 border-b border-zinc-800">
<Pressable
onPress={onBackToOrchestrator}
className="bg-zinc-800 px-4 py-2 rounded-lg active:bg-zinc-700"
>
<Text className="text-white text-sm font-semibold text-center">
Back to Chat
</Text>
</Pressable>
</View>
)}
{/* Scrollable process list */}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
className="px-4 py-3"
contentContainerStyle={{ gap: 8 }}
>
{/* Agents */}
{agents.map((agent) => {
const modeName = getModeName(agent.currentModeId, agent.availableModes);
const isActive = activeProcessType === 'agent' && activeProcessId === agent.id;
return (
<Pressable
key={`agent-${agent.id}`}
onPress={() => onSelectAgent(agent.id)}
className={`flex-row items-center gap-2 px-3 py-2 rounded-lg ${
isActive ? 'bg-blue-600' : 'bg-zinc-800'
} active:opacity-70`}
>
{/* Agent icon */}
<View className="w-3 h-3 rounded-full bg-blue-500" />
{/* Agent ID (shortened) */}
<Text className="text-white text-xs font-medium">
{agent.id.substring(0, 8)}
</Text>
{/* Status indicator */}
<View
className="w-2 h-2 rounded-full"
style={{ backgroundColor: getAgentStatusColor(agent.status) }}
/>
{/* Mode indicator */}
{agent.currentModeId && (
<View
className="w-1.5 h-1.5 rounded-full opacity-30"
style={{ backgroundColor: getModeColor(agent.currentModeId) }}
/>
)}
</Pressable>
);
})}
{/* Commands */}
{commands.map((command) => {
const statusColor = command.isDead
? command.exitCode === 0
? '#22c55e'
: '#ef4444'
: '#3b82f6';
return (
<View
key={`command-${command.id}`}
className="flex-row items-center gap-2 px-3 py-2 rounded-lg bg-zinc-800"
>
{/* Command icon */}
<View className="w-3 h-3 rounded-sm bg-purple-500" />
{/* Command name */}
<Text className="text-white text-xs font-medium" numberOfLines={1}>
{command.name || command.currentCommand.substring(0, 20)}
</Text>
{/* Status indicator */}
<View
className="w-2 h-2 rounded-full"
style={{ backgroundColor: statusColor }}
/>
</View>
);
})}
</ScrollView>
</View>
);
}