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