// Vision System API Client for vision-system-remote // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - Vite provides import.meta.env const VISION_API_BASE_URL = (import.meta.env?.VITE_VISION_API_URL as string | undefined) || 'http://exp-dash:8000' // Types (simplified - we'll use the same types from the original) export interface SystemStatus { system_started: boolean mqtt_connected: boolean last_mqtt_message: string machines: Record cameras: Record active_recordings: number total_recordings: number uptime_seconds: number } export interface MachineStatus { name: string state: string last_updated: string last_message?: string mqtt_topic?: string } export interface CameraStatus { name?: string status: string is_recording: boolean last_checked: string last_error?: string | null device_info?: { friendly_name?: string serial_number?: string port_type?: string model?: string firmware_version?: string } current_recording_file?: string | null recording_start_time?: string | null last_frame_time?: string frame_rate?: number auto_recording_enabled: boolean auto_recording_active: boolean auto_recording_failure_count: number auto_recording_last_attempt?: string auto_recording_last_error?: string } export interface RecordingInfo { camera_name: string filename: string start_time: string state: string end_time?: string file_size_bytes?: number frame_count?: number duration_seconds?: number error_message?: string | null } export interface StorageStats { base_path: string total_files: number total_size_bytes: number cameras: Record disk_usage: { total: number used: number free: number } } export interface MqttStatus { connected: boolean broker_host: string broker_port: number subscribed_topics: string[] last_message_time: string message_count: number error_count: number uptime_seconds: number } export interface MqttEvent { machine_name: string topic: string payload: string normalized_state: string timestamp: string message_number: number } export interface MqttEventsResponse { events: MqttEvent[] total_events: number last_updated: string | null } export interface StartRecordingResponse { success: boolean message: string filename: string } export interface StopRecordingResponse { success: boolean message: string } export interface StreamStopResponse { success: boolean message: string } export interface CameraConfig { name: string machine_topic: string storage_path: string enabled: boolean auto_start_recording_enabled: boolean auto_recording_max_retries: number auto_recording_retry_delay_seconds: number exposure_ms: number gain: number target_fps: number video_format: string video_codec: string video_quality: number sharpness: number contrast: number saturation: number gamma: number noise_filter_enabled: boolean denoise_3d_enabled: boolean auto_white_balance: boolean color_temperature_preset: number wb_red_gain: number wb_green_gain: number wb_blue_gain: number anti_flicker_enabled: boolean light_frequency: number bit_depth: number hdr_enabled: boolean hdr_gain_mode: number } export interface CameraConfigUpdate { exposure_ms?: number gain?: number target_fps?: number sharpness?: number contrast?: number saturation?: number gamma?: number noise_filter_enabled?: boolean denoise_3d_enabled?: boolean auto_white_balance?: boolean color_temperature_preset?: number wb_red_gain?: number wb_green_gain?: number wb_blue_gain?: number anti_flicker_enabled?: boolean light_frequency?: number hdr_enabled?: boolean hdr_gain_mode?: number auto_start_recording_enabled?: boolean auto_recording_max_retries?: number auto_recording_retry_delay_seconds?: number } export interface CameraConfigUpdateResponse { success: boolean message: string updated_settings: string[] } export interface CameraRecoveryResponse { success: boolean message: string camera_name: string operation: string } export interface StreamStartResponse { success: boolean message: string } class VisionApiClient { private baseUrl: string constructor(baseUrl: string = VISION_API_BASE_URL) { this.baseUrl = baseUrl } private async request(endpoint: string, options: RequestInit = {}): Promise { const url = `${this.baseUrl}${endpoint}` const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...options.headers, }, ...options, }) if (!response.ok) { const errorText = await response.text() throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`) } return response.json() } async getSystemStatus(): Promise { return this.request('/system/status') } async getCameras(): Promise> { return this.request('/cameras') } async getRecordings(): Promise> { return this.request('/recordings') } async getStorageStats(): Promise { return this.request('/storage/stats') } async getMqttStatus(): Promise { return this.request('/mqtt/status') } async getMqttEvents(limit: number = 50): Promise { return this.request(`/mqtt/events?limit=${limit}`) } async publishMqttMessage(topic: string, payload: string, qos: number = 0, retain: boolean = false): Promise<{ success: boolean; message: string; topic: string; payload: string }> { return this.request('/mqtt/publish', { method: 'POST', body: JSON.stringify({ topic, payload, qos, retain }), }) } async startRecording(cameraName: string, filename?: string): Promise { return this.request(`/cameras/${cameraName}/start-recording`, { method: 'POST', body: JSON.stringify({ filename }), }) } async stopRecording(cameraName: string): Promise { return this.request(`/cameras/${cameraName}/stop-recording`, { method: 'POST', }) } async stopStream(cameraName: string): Promise { return this.request(`/cameras/${cameraName}/stop-stream`, { method: 'POST', }) } getStreamUrl(cameraName: string): string { return `${this.baseUrl}/cameras/${cameraName}/stream` } async startStream(cameraName: string): Promise { return this.request(`/cameras/${cameraName}/start-stream`, { method: 'POST', }) } async getCameraConfig(cameraName: string): Promise { return this.request(`/cameras/${cameraName}/config`) } async updateCameraConfig(cameraName: string, config: CameraConfigUpdate): Promise { return this.request(`/cameras/${cameraName}/config`, { method: 'PUT', body: JSON.stringify(config), }) } async applyCameraConfig(cameraName: string): Promise<{ success: boolean; message: string }> { return this.request(`/cameras/${cameraName}/apply-config`, { method: 'POST', }) } async reinitializeCamera(cameraName: string): Promise { return this.request(`/cameras/${cameraName}/reinitialize`, { method: 'POST', }) } async fullResetCamera(cameraName: string): Promise { return this.request(`/cameras/${cameraName}/full-reset`, { method: 'POST', }) } } export const visionApi = new VisionApiClient() // Utility functions export const formatBytes = (bytes: number): string => { if (bytes === 0) return '0 Bytes' const k = 1024 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } export const formatDuration = (seconds: number): string => { const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) const secs = Math.floor(seconds % 60) if (hours > 0) { return `${hours}h ${minutes}m ${secs}s` } else if (minutes > 0) { return `${minutes}m ${secs}s` } else { return `${secs}s` } } export const formatUptime = (seconds: number): string => { const days = Math.floor(seconds / 86400) const hours = Math.floor((seconds % 86400) / 3600) const minutes = Math.floor((seconds % 3600) / 60) if (days > 0) { return `${days}d ${hours}h ${minutes}m` } else if (hours > 0) { return `${hours}h ${minutes}m` } else { return `${minutes}m` } }