Spaces:
Sleeping
Sleeping
| // useAgentStatus Composable | |
| // Manages agent lifecycle operations and status tracking | |
| import { ref, computed, onMounted, onUnmounted, type Ref } from 'vue' | |
| import { useWebSocket } from './useWebSocket' | |
| import type { Agent, AgentStatus, WebSocketEvent } from '@/types' | |
| interface AgentStatusComposable { | |
| // State | |
| isStarting: Ref<boolean> | |
| isStopping: Ref<boolean> | |
| isRestarting: Ref<boolean> | |
| isLoading: Ref<boolean> | |
| error: Ref<string | null> | |
| // Computed | |
| canStart: Ref<boolean> | |
| canStop: Ref<boolean> | |
| canRestart: Ref<boolean> | |
| // Actions | |
| startAgent: () => Promise<void> | |
| stopAgent: () => Promise<void> | |
| restartAgent: () => Promise<void> | |
| refreshStatus: () => Promise<void> | |
| clearError: () => void | |
| } | |
| export function useAgentStatus(agent: Agent): AgentStatusComposable { | |
| // State | |
| const isStarting = ref(false) | |
| const isStopping = ref(false) | |
| const isRestarting = ref(false) | |
| const error = ref<string | null>(null) | |
| // WebSocket for real-time updates | |
| const { connected, on, off } = useWebSocket() | |
| // Computed | |
| const isLoading = computed(() => | |
| isStarting.value || isStopping.value || isRestarting.value | |
| ) | |
| const canStart = computed(() => | |
| agent.status === 'inactive' && !isLoading.value | |
| ) | |
| const canStop = computed(() => | |
| ['active', 'processing', 'idle'].includes(agent.status) && !isLoading.value | |
| ) | |
| const canRestart = computed(() => | |
| agent.status === 'error' && !isLoading.value | |
| ) | |
| // API client | |
| async function apiCall(method: string, endpoint: string, data?: any) { | |
| const response = await fetch(`/api/v1${endpoint}`, { | |
| method, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: data ? JSON.stringify(data) : undefined, | |
| }) | |
| if (!response.ok) { | |
| const errorData = await response.json().catch(() => ({})) | |
| throw new Error(errorData.message || `HTTP ${response.status}`) | |
| } | |
| return response.json() | |
| } | |
| // Actions | |
| async function startAgent(): Promise<void> { | |
| if (!canStart.value) return | |
| isStarting.value = true | |
| error.value = null | |
| try { | |
| await apiCall('POST', `/agents/${agent.id}/start`) | |
| // Status will be updated via WebSocket | |
| } catch (err) { | |
| error.value = err instanceof Error ? err.message : 'Failed to start agent' | |
| throw err | |
| } finally { | |
| isStarting.value = false | |
| } | |
| } | |
| async function stopAgent(): Promise<void> { | |
| if (!canStop.value) return | |
| isStopping.value = true | |
| error.value = null | |
| try { | |
| await apiCall('POST', `/agents/${agent.id}/stop`) | |
| // Status will be updated via WebSocket | |
| } catch (err) { | |
| error.value = err instanceof Error ? err.message : 'Failed to stop agent' | |
| throw err | |
| } finally { | |
| isStopping.value = false | |
| } | |
| } | |
| async function restartAgent(): Promise<void> { | |
| if (!canRestart.value) return | |
| isRestarting.value = true | |
| error.value = null | |
| try { | |
| await apiCall('POST', `/agents/${agent.id}/restart`) | |
| // Status will be updated via WebSocket | |
| } catch (err) { | |
| error.value = err instanceof Error ? err.message : 'Failed to restart agent' | |
| throw err | |
| } finally { | |
| isRestarting.value = false | |
| } | |
| } | |
| async function refreshStatus(): Promise<void> { | |
| try { | |
| const response = await apiCall('GET', `/agents/${agent.id}`) | |
| // Update agent status through parent component or store | |
| // This assumes the parent handles the agent object updates | |
| Object.assign(agent, response.data) | |
| } catch (err) { | |
| error.value = err instanceof Error ? err.message : 'Failed to refresh status' | |
| throw err | |
| } | |
| } | |
| function clearError(): void { | |
| error.value = null | |
| } | |
| // WebSocket event handlers | |
| function handleAgentStatusChanged(event: WebSocketEvent) { | |
| if (event.data.agentId === agent.id) { | |
| // Update agent status | |
| Object.assign(agent, event.data) | |
| // Clear loading states if status changed | |
| if (event.data.status !== agent.status) { | |
| isStarting.value = false | |
| isStopping.value = false | |
| isRestarting.value = false | |
| } | |
| } | |
| } | |
| // Lifecycle | |
| onMounted(() => { | |
| // Subscribe to WebSocket events | |
| on('agent_status_changed', handleAgentStatusChanged) | |
| // Initial status refresh if needed | |
| if (connected.value) { | |
| refreshStatus().catch(() => { | |
| // Silently handle initial refresh errors | |
| }) | |
| } | |
| }) | |
| onUnmounted(() => { | |
| // Unsubscribe from WebSocket events | |
| off('agent_status_changed', handleAgentStatusChanged) | |
| }) | |
| return { | |
| // State | |
| isStarting, | |
| isStopping, | |
| isRestarting, | |
| isLoading, | |
| error, | |
| // Computed | |
| canStart, | |
| canStop, | |
| canRestart, | |
| // Actions | |
| startAgent, | |
| stopAgent, | |
| restartAgent, | |
| refreshStatus, | |
| clearError | |
| } | |
| } | |
| // Agent Status Store (optional Pinia store) | |
| export interface AgentStatusState { | |
| agents: Record<string, Agent> | |
| loading: boolean | |
| error: string | null | |
| } | |
| // Helper functions | |
| export function getAgentStatusColor(status: AgentStatus): string { | |
| const colorMap = { | |
| active: 'var(--saap-success)', | |
| inactive: 'var(--saap-neutral-400)', | |
| processing: 'var(--saap-warning)', | |
| error: 'var(--saap-error)', | |
| idle: 'var(--saap-info)' | |
| } | |
| return colorMap[status] || colorMap.inactive | |
| } | |
| export function getAgentTypeIcon(type: string): string { | |
| const iconMap = { | |
| generalist: 'brain', | |
| specialist: 'target', | |
| coordinator: 'network' | |
| } | |
| return iconMap[type as keyof typeof iconMap] || 'help-circle' | |
| } | |
| export function formatAgentUptime(timestamp: string): string { | |
| const start = new Date(timestamp) | |
| const now = new Date() | |
| const diff = now.getTime() - start.getTime() | |
| const days = Math.floor(diff / (1000 * 60 * 60 * 24)) | |
| const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) | |
| const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) | |
| if (days > 0) { | |
| return `${days}d ${hours}h` | |
| } | |
| if (hours > 0) { | |
| return `${hours}h ${minutes}m` | |
| } | |
| return `${minutes}m` | |
| } | |