| |
| |
|
|
| import { ref, computed, onMounted, onUnmounted, type Ref } from 'vue' |
| import { useWebSocket } from './useWebSocket' |
| import type { Agent, AgentStatus, WebSocketEvent } from '@/types' |
|
|
| interface AgentStatusComposable { |
| |
| isStarting: Ref<boolean> |
| isStopping: Ref<boolean> |
| isRestarting: Ref<boolean> |
| isLoading: Ref<boolean> |
| error: Ref<string | null> |
| |
| |
| canStart: Ref<boolean> |
| canStop: Ref<boolean> |
| canRestart: Ref<boolean> |
| |
| |
| startAgent: () => Promise<void> |
| stopAgent: () => Promise<void> |
| restartAgent: () => Promise<void> |
| refreshStatus: () => Promise<void> |
| clearError: () => void |
| } |
|
|
| export function useAgentStatus(agent: Agent): AgentStatusComposable { |
| |
| const isStarting = ref(false) |
| const isStopping = ref(false) |
| const isRestarting = ref(false) |
| const error = ref<string | null>(null) |
| |
| |
| const { connected, on, off } = useWebSocket() |
| |
| |
| 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 |
| ) |
| |
| |
| 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() |
| } |
| |
| |
| async function startAgent(): Promise<void> { |
| if (!canStart.value) return |
| |
| isStarting.value = true |
| error.value = null |
| |
| try { |
| await apiCall('POST', `/agents/${agent.id}/start`) |
| |
| } 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`) |
| |
| } 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`) |
| |
| } 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}`) |
| |
| |
| 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 |
| } |
| |
| |
| function handleAgentStatusChanged(event: WebSocketEvent) { |
| if (event.data.agentId === agent.id) { |
| |
| Object.assign(agent, event.data) |
| |
| |
| if (event.data.status !== agent.status) { |
| isStarting.value = false |
| isStopping.value = false |
| isRestarting.value = false |
| } |
| } |
| } |
| |
| |
| onMounted(() => { |
| |
| on('agent_status_changed', handleAgentStatusChanged) |
| |
| |
| if (connected.value) { |
| refreshStatus().catch(() => { |
| |
| }) |
| } |
| }) |
| |
| onUnmounted(() => { |
| |
| off('agent_status_changed', handleAgentStatusChanged) |
| }) |
| |
| return { |
| |
| isStarting, |
| isStopping, |
| isRestarting, |
| isLoading, |
| error, |
| |
| |
| canStart, |
| canStop, |
| canRestart, |
| |
| |
| startAgent, |
| stopAgent, |
| restartAgent, |
| refreshStatus, |
| clearError |
| } |
| } |
|
|
| |
| export interface AgentStatusState { |
| agents: Record<string, Agent> |
| loading: boolean |
| error: string | null |
| } |
|
|
| |
| 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` |
| } |
|
|