feat: add VLM composables for Vue 3 integration
- useVLMScenes(fileUuid): load scenes for a specific file with thumbnails - useVLMSearch(): search VLM scenes and traces with bilingual support - useSceneThumbnail(fileUuid, sceneIndex): load scene thumbnail URLs - TypeScript interfaces: VLMTag, VLMScene, VLMTrace, FileScene - Uses direct HTTP calls via getApiBase() (no Tauri IPC needed) - Follows Vue 3 composable best practices (ref, loading, error states)
This commit is contained in:
156
src/composables/useVLM.ts
Normal file
156
src/composables/useVLM.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { ref } from 'vue'
|
||||
import { getApiBase } from '@/api/config'
|
||||
|
||||
export interface VLMTag {
|
||||
en: string
|
||||
local: string
|
||||
}
|
||||
|
||||
export interface VLMScene {
|
||||
file_uuid: string
|
||||
file_name: string
|
||||
scene_index: number
|
||||
timestamp_sec: number
|
||||
frame_number: number
|
||||
thumbnail_url: string
|
||||
vlm_description: string
|
||||
vlm_description_localized?: string
|
||||
vlm_objects: string[]
|
||||
vlm_tags: VLMTag[]
|
||||
score: number
|
||||
source: string
|
||||
}
|
||||
|
||||
export interface VLMTrace {
|
||||
file_uuid: string
|
||||
file_name: string
|
||||
trace_id: number
|
||||
start_frame: number
|
||||
end_frame: number
|
||||
start_time: number
|
||||
end_time: number
|
||||
timestamp_sec: number
|
||||
thumbnail_url: string
|
||||
vlm_description: string
|
||||
vlm_description_localized?: string
|
||||
vlm_objects: string[]
|
||||
vlm_tags: VLMTag[]
|
||||
score: number
|
||||
identity_name?: string
|
||||
}
|
||||
|
||||
export interface FileScene {
|
||||
index: number
|
||||
filename: string
|
||||
timestamp_sec: number
|
||||
vlm_description: string
|
||||
vlm_description_localized?: string
|
||||
vlm_objects: string[]
|
||||
vlm_tags: VLMTag[]
|
||||
vlm_status: string
|
||||
}
|
||||
|
||||
export function useVLMScenes(fileUuid: string) {
|
||||
const scenes = ref<FileScene[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function loadScenes() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v1/file/${fileUuid}/scenes`)
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
const data = await resp.json()
|
||||
scenes.value = Array.isArray(data) ? data : (data.scenes || [])
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to load scenes'
|
||||
scenes.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getThumbnailUrl(sceneIndex: number): string {
|
||||
const base = getApiBase()
|
||||
const paddedIndex = String(sceneIndex).padStart(3, '0')
|
||||
return `${base}/api/v1/file/${fileUuid}/scenes/scene_${paddedIndex}.jpg`
|
||||
}
|
||||
|
||||
return { scenes, loading, error, loadScenes, getThumbnailUrl }
|
||||
}
|
||||
|
||||
export function useVLMSearch() {
|
||||
const scenes = ref<VLMScene[]>([])
|
||||
const traces = ref<VLMTrace[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const total = ref(0)
|
||||
|
||||
async function search(query: string, options?: {
|
||||
page_size?: number
|
||||
min_score?: number
|
||||
output_language?: string
|
||||
offset?: number
|
||||
}) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
const base = getApiBase()
|
||||
const params = {
|
||||
query,
|
||||
page_size: options?.page_size || 20,
|
||||
min_score: options?.min_score ?? 0.0,
|
||||
output_language: options?.output_language || 'en',
|
||||
...(options?.offset != null ? { offset: options.offset } : {})
|
||||
}
|
||||
|
||||
try {
|
||||
const [scenesRes, tracesRes] = await Promise.all([
|
||||
fetch(`${base}/api/v1/search/vlm/scenes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(params)
|
||||
}).then(r => r.json()),
|
||||
fetch(`${base}/api/v1/search/vlm/traces`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(params)
|
||||
}).then(r => r.json())
|
||||
])
|
||||
|
||||
scenes.value = scenesRes?.results || []
|
||||
traces.value = tracesRes?.results || []
|
||||
total.value = scenesRes?.total || 0
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Search failed'
|
||||
scenes.value = []
|
||||
traces.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearResults() {
|
||||
scenes.value = []
|
||||
traces.value = []
|
||||
total.value = 0
|
||||
}
|
||||
|
||||
return { scenes, traces, loading, error, total, search, clearResults }
|
||||
}
|
||||
|
||||
export function useSceneThumbnail(fileUuid: string, sceneIndex: number) {
|
||||
const url = ref<string | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
function loadThumbnail() {
|
||||
loading.value = true
|
||||
const base = getApiBase()
|
||||
const paddedIndex = String(sceneIndex).padStart(3, '0')
|
||||
url.value = `${base}/api/v1/file/${fileUuid}/scenes/scene_${paddedIndex}.jpg`
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
return { url, loading, loadThumbnail }
|
||||
}
|
||||
Reference in New Issue
Block a user