Refactor video streaming feature and update dependencies

- Replaced npm ci with npm install in docker-compose for better package management.
- Introduced remote component loading for the VideoStreamingPage with error handling.
- Updated the title in index.html to "Experiments Dashboard" for clarity.
- Added new video remote service configuration in docker-compose for improved integration.
- Removed deprecated files and components related to the video streaming feature to streamline the codebase.
- Updated package.json and package-lock.json to include @originjs/vite-plugin-federation for module federation support.
This commit is contained in:
salirezav
2025-10-30 15:36:19 -04:00
parent 9f669e7dff
commit 0b724fe59b
102 changed files with 4656 additions and 13376 deletions

View File

@@ -0,0 +1,28 @@
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
const BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
async function request<T>(path: string, method: HttpMethod = 'GET', body?: unknown): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
'Content-Type': 'application/json'
},
body: body ? JSON.stringify(body) : undefined,
})
if (!res.ok) {
const text = await res.text().catch(() => '')
throw new Error(text || `Request failed: ${res.status}`)
}
return (await res.json()) as T
}
export const apiClient = {
get: <T>(path: string) => request<T>(path, 'GET'),
post: <T>(path: string, body?: unknown) => request<T>(path, 'POST', body),
put: <T>(path: string, body?: unknown) => request<T>(path, 'PUT', body),
patch: <T>(path: string, body?: unknown) => request<T>(path, 'PATCH', body),
delete: <T>(path: string) => request<T>(path, 'DELETE'),
}

View File

@@ -0,0 +1,4 @@
export { apiClient } from './client'

View File

@@ -0,0 +1,30 @@
import { apiClient } from './client'
import type { VideoListParams, VideoListResponse } from '../types/video'
const BASE = (import.meta as any).env?.VITE_VISION_API_URL || '/api'
export const videoApi = {
async list(params: VideoListParams = {}): Promise<VideoListResponse> {
const search = new URLSearchParams()
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null) search.append(k, String(v))
})
const qs = search.toString()
return apiClient.get(`${BASE}/videos/${qs ? `?${qs}` : ''}`)
},
streamingUrl(fileId: string): string {
return `${BASE}/videos/${fileId}/stream`
},
thumbnailUrl(fileId: string, params: { timestamp?: number; width?: number; height?: number } = {}): string {
const search = new URLSearchParams()
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null) search.append(k, String(v))
})
const qs = search.toString()
return `${BASE}/videos/${fileId}/thumbnail${qs ? `?${qs}` : ''}`
}
}

View File

@@ -0,0 +1,4 @@
export { supabase, userManagement, type User } from '../../lib/supabase'
export { useAuth } from '../../hooks/useAuth'

View File

@@ -0,0 +1,6 @@
export * from './types'
export * from './ui'
export * from './api'
export * from './auth'

View File

@@ -0,0 +1,6 @@
export type ApiResult<T> = { success: true; data: T } | { success: false; error: string }
export type UserRole = 'admin' | 'conductor' | 'user'

View File

@@ -0,0 +1,34 @@
export interface VideoFile {
file_id: string
camera_name: string
filename: string
file_size_bytes: number
format: string
status: 'completed' | 'processing' | 'failed'
created_at: string
is_streamable: boolean
needs_conversion: boolean
}
export interface VideoListResponse {
videos: VideoFile[]
total_count: number
page?: number
total_pages?: number
has_next?: boolean
has_previous?: boolean
}
export interface VideoListParams {
camera_name?: string
start_date?: string
end_date?: string
limit?: number
include_metadata?: boolean
page?: number
offset?: number
}

View File

@@ -0,0 +1,4 @@
export { PrimaryButton } from './widgets/PrimaryButton'

View File

@@ -0,0 +1,19 @@
import type { ButtonHTMLAttributes, PropsWithChildren } from 'react'
type PrimaryButtonProps = PropsWithChildren<ButtonHTMLAttributes<HTMLButtonElement>> & {
loading?: boolean
}
export function PrimaryButton({ children, loading = false, className = '', ...rest }: PrimaryButtonProps) {
return (
<button
{...rest}
className={`px-4 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50 ${className}`}
disabled={loading || rest.disabled}
>
{loading ? 'Please wait…' : children}
</button>
)
}