- 改用 POST /api/v1/file/:uuid/json/face_cluster - 替换旧的 GET /api/v1/cluster-results - 返回正确的 cluster 数据:status, clusters, frames - 修复 face dedup 按钮无法工作的问题 - 添加 get_file_processor_json API (通用端点)
1070 lines
40 KiB
TypeScript
1070 lines
40 KiB
TypeScript
import { invoke } from '@tauri-apps/api/core'
|
|
import { isTauri, getApiBase } from './config'
|
|
|
|
// Detect base URL: use current origin when remote, fallback to localStorage or localhost
|
|
function detectBaseUrl(): string {
|
|
const hostname = typeof window !== 'undefined' ? window.location.hostname : ''
|
|
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1'
|
|
if (!isLocal && typeof window !== 'undefined') {
|
|
return window.location.origin
|
|
}
|
|
return localStorage.getItem('markbase_url') || 'http://localhost:11438'
|
|
}
|
|
|
|
// Poll command status until completed or failed
|
|
export async function pollCommandStatus(commandId: string, intervalMs: number = 2000, maxAttempts: number = 150): Promise<any> {
|
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
const status = await apiCall('get_command_status', { commandId })
|
|
if (status?.status === 'completed') {
|
|
return status
|
|
}
|
|
if (status?.status === 'failed') {
|
|
throw new Error(status.last_error || 'Command failed')
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, intervalMs))
|
|
}
|
|
throw new Error('Command polling timeout')
|
|
}
|
|
|
|
// API proxy: Tauri IPC or HTTP fetch
|
|
export async function apiCall(cmd: string, args: Record<string, any>): Promise<any> {
|
|
if (cmd === 'get_video_stream') {
|
|
const base = isTauri ? 'http://localhost:8888' : getApiBase()
|
|
const { url } = buildHttpRequest(cmd, args)
|
|
return `${base}${url}`
|
|
}
|
|
if (cmd === 'upload_profile_image') {
|
|
if (isTauri) {
|
|
return invoke('upload_profile_image', { uuid: args.uuid, filePath: args.filePath })
|
|
}
|
|
const base = getApiBase()
|
|
const { url } = buildHttpRequest(cmd, args)
|
|
const formData = new FormData()
|
|
formData.append('image', args.file)
|
|
const fullUrl = `${base}${url}`
|
|
const response = await fetch(fullUrl, { method: 'POST', body: formData })
|
|
if (!response.ok) throw new Error(`Upload failed: ${response.status}`)
|
|
return response.json()
|
|
}
|
|
if (!isTauri && (cmd === 'update_identity_starred' || cmd === 'update_identity_status')) {
|
|
const current: any = await httpCall('get_identity', { uuid: args.uuid })
|
|
const metadata = { ...(current.metadata || {}), ...(current.metadataJson ? JSON.parse(current.metadataJson) : {}) }
|
|
if (cmd === 'update_identity_starred') {
|
|
metadata.starred = args.starred
|
|
} else {
|
|
metadata.status = args.status
|
|
}
|
|
return httpCall('update_identity', { uuid: args.uuid, metadataJson: JSON.stringify(metadata) })
|
|
}
|
|
// run_cluster_agent 也沒有 Tauri command
|
|
if (cmd === 'run_cluster_agent') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
// get_cluster_results 也沒有 Tauri command
|
|
if (cmd === 'get_cluster_results') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
// get_similar_face_groups 也沒有 Tauri command
|
|
if (cmd === 'get_similar_face_groups') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
// create_pending_identity 也沒有 Tauri command
|
|
if (cmd === 'create_pending_identity') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
// bind_speakers 也沒有 Tauri command
|
|
if (cmd === 'bind_speakers') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
// get_processor_json 也沒有 Tauri command
|
|
if (cmd === 'get_processor_json') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
// get_trace_profile / get_trace_profile_file 也沒有 Tauri command
|
|
if (cmd === 'get_trace_profile' || cmd === 'get_trace_profile_file') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
// get_file_traces 也沒有 Tauri command
|
|
if (cmd === 'get_file_traces') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data, args)
|
|
}
|
|
// search_file_faces 也沒有 Tauri command
|
|
if (cmd === 'search_file_faces') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data, args)
|
|
}
|
|
// get_face_landmarks 也沒有 Tauri command
|
|
if (cmd === 'get_face_landmarks') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data, args)
|
|
}
|
|
// Admin data comes from MarkBase API (/api/v2/admin/*) in all modes — no Tauri command
|
|
if (cmd === 'get_system_stats' || cmd === 'list_admin_users' || cmd === 'list_shares' || cmd === 'validate_system_stats') {
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
if (isTauri) {
|
|
try {
|
|
const data = await invoke(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
} catch (e: any) {
|
|
console.error('[apiCall] invoke error:', typeof e, e)
|
|
const errMsg = typeof e === 'string' ? e : (e?.message || String(e))
|
|
console.error('[apiCall] falling back to HTTP for', cmd, '- error:', errMsg)
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
}
|
|
const data = await httpCall(cmd, args)
|
|
return transformResponse(cmd, data)
|
|
}
|
|
|
|
const DEFAULT_TIMEOUT = 30000
|
|
const UNREGISTER_TIMEOUT = 200000 // 200s for large file unregister (was timing out at 30s)
|
|
|
|
export enum ApiErrorType {
|
|
NETWORK = 'network',
|
|
TIMEOUT = 'timeout',
|
|
SERVER = 'server',
|
|
VALIDATION = 'validation',
|
|
NOT_FOUND = 'not_found',
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
type: ApiErrorType
|
|
status?: number
|
|
constructor(type: ApiErrorType, message: string, status?: number) {
|
|
super(message)
|
|
this.type = type
|
|
this.status = status
|
|
}
|
|
}
|
|
|
|
async function httpCall(cmd: string, args: Record<string, any>, retries = 3): Promise<any> {
|
|
const base = getApiBase()
|
|
const { url, method, body, timeout: requestTimeout } = buildHttpRequest(cmd, args)
|
|
const fullUrl = url.startsWith('http://') || url.startsWith('https://') ? url : `${base}${url}`
|
|
|
|
const controller = new AbortController()
|
|
const timeout = setTimeout(() => controller.abort(), requestTimeout ?? DEFAULT_TIMEOUT)
|
|
|
|
let response: Response | null = null
|
|
try {
|
|
for (let i = 0; i < retries; i++) {
|
|
try {
|
|
response = await fetch(fullUrl, {
|
|
method,
|
|
headers: body ? { 'Content-Type': 'application/json' } : {},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
signal: controller.signal,
|
|
})
|
|
if (response.ok) break
|
|
if (response.status >= 500 && i < retries - 1) {
|
|
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)))
|
|
response = null
|
|
continue
|
|
}
|
|
break
|
|
} catch (e: any) {
|
|
if (e.name === 'AbortError') {
|
|
throw new ApiError(ApiErrorType.TIMEOUT, `Request timeout: ${cmd}`)
|
|
}
|
|
if (i < retries - 1) {
|
|
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)))
|
|
continue
|
|
}
|
|
throw new ApiError(ApiErrorType.NETWORK, `Network error: ${e.message}`)
|
|
}
|
|
}
|
|
} finally {
|
|
clearTimeout(timeout)
|
|
}
|
|
|
|
if (!response) throw new ApiError(ApiErrorType.NETWORK, 'No response')
|
|
|
|
if (!response.ok) {
|
|
const status = response.status
|
|
const errorText = await response.text().catch(() => 'Unknown error')
|
|
if (status === 404) {
|
|
throw new ApiError(ApiErrorType.NOT_FOUND, errorText, status)
|
|
}
|
|
if (status >= 400 && status < 500) {
|
|
throw new ApiError(ApiErrorType.VALIDATION, errorText, status)
|
|
}
|
|
throw new ApiError(ApiErrorType.SERVER, errorText, status)
|
|
}
|
|
|
|
if (response.status === 204) {
|
|
return { success: true }
|
|
}
|
|
|
|
const contentType = response.headers.get('content-type') || ''
|
|
|
|
if (contentType.includes('image/') || contentType.includes('video/') || contentType.includes('octet-stream')) {
|
|
const buffer = await response.arrayBuffer()
|
|
const bytes = new Uint8Array(buffer)
|
|
if (cmd === 'get_identity_profile' || cmd === 'get_thumbnail' || cmd === 'get_face_thumbnail' || cmd === 'get_file_thumbnail_by_path') {
|
|
const ext = contentType.includes('png') ? 'png' : 'jpeg'
|
|
const blob = new Blob([bytes], { type: `image/${ext}` })
|
|
return new Promise<string>((resolve, reject) => {
|
|
const reader = new FileReader()
|
|
reader.onload = () => resolve(reader.result as string)
|
|
reader.onerror = () => reject(new Error('FileReader failed'))
|
|
reader.readAsDataURL(blob)
|
|
})
|
|
}
|
|
const chunks: string[] = []
|
|
const chunkSize = 8192
|
|
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
const slice = bytes.subarray(i, Math.min(i + chunkSize, bytes.length))
|
|
chunks.push(String.fromCharCode(...slice))
|
|
}
|
|
return btoa(chunks.join(''))
|
|
}
|
|
|
|
const text = await response.text()
|
|
try {
|
|
return JSON.parse(text)
|
|
} catch {
|
|
return text
|
|
}
|
|
}
|
|
|
|
function buildHttpRequest(cmd: string, args: Record<string, any>): { url: string; method: string; body?: any; timeout?: number } {
|
|
const a = args
|
|
|
|
switch (cmd) {
|
|
// --- Data APIs ---
|
|
case 'get_files': {
|
|
const ps = a.args?.pageSize || 500
|
|
return { url: `/api/v1/files/scan?page_size=${ps}`, method: 'GET' }
|
|
}
|
|
case 'get_people': {
|
|
return { url: `/api/v1/identities?page=${a.page || 1}&per_page=${a.perPage || 100}`, method: 'GET' }
|
|
}
|
|
case 'get_file_identities': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/identities?page=${a.page || 1}&page_size=${a.pageSize || 50}`, method: 'GET' }
|
|
}
|
|
case 'get_identity_files': {
|
|
return { url: `/api/v1/identity/${a.uuid}/files?page=${a.page || 1}&page_size=${a.pageSize || 20}`, method: 'GET' }
|
|
}
|
|
case 'get_faces': {
|
|
return { url: `/api/v1/identity/${a.uuid}/faces?page_size=${a.perPage || 100}`, method: 'GET' }
|
|
}
|
|
case 'get_traces': {
|
|
let url = `/api/v1/identity/${a.uuid}/traces?page_size=${a.perPage || 50}`
|
|
if (a.page) url += `&page=${a.page}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'get_face_candidates': {
|
|
let url = `/api/v1/faces/candidates?page=${a.page || 1}&page_size=${a.perPage || 100}`
|
|
if (a.fileUuid) url += `&file_uuid=${a.fileUuid}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'get_unassigned_traces': {
|
|
let url = `/api/v1/traces/unassigned?page=${a.page || 1}&page_size=${a.perPage || 20}`
|
|
if (a.fileUuid) url += `&file_uuid=${a.fileUuid}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'get_pending_persons': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/pending-persons`, method: 'GET' }
|
|
}
|
|
case 'get_identity': {
|
|
return { url: `/api/v1/identity/${a.uuid}`, method: 'GET' }
|
|
}
|
|
|
|
// --- Search APIs ---
|
|
case 'search_keyword': {
|
|
return { url: '/api/v1/search/keyword', method: 'POST', body: { query: a.query, limit: a.limit || 20 } }
|
|
}
|
|
case 'search_semantic': {
|
|
return { url: '/api/v1/search/semantic', method: 'POST', body: { query: a.query, limit: a.limit || 20 } }
|
|
}
|
|
case 'search_llm_smart': {
|
|
return { url: '/api/v1/search/llm-smart', method: 'POST', body: { query: a.query, limit: a.limit || 20 } }
|
|
}
|
|
case 'search_people': {
|
|
const params = new URLSearchParams()
|
|
if (a.query) params.set('query', a.query)
|
|
if (a.fileUuid) params.set('file_uuid', a.fileUuid)
|
|
if (a.limit) params.set('limit', String(a.limit))
|
|
return { url: `/api/v1/search/people?${params.toString()}`, method: 'GET' }
|
|
}
|
|
case 'search_agents': {
|
|
const body: any = { query: a.query }
|
|
if (a.conversationId) body.conversation_id = a.conversationId
|
|
return { url: '/api/v1/agents/search', method: 'POST', body }
|
|
}
|
|
|
|
// --- Image APIs ---
|
|
case 'get_thumbnail': {
|
|
return { url: `/api/v1/file/${a.uuid}/thumbnail?frame=${a.frame || 30}`, method: 'GET' }
|
|
}
|
|
case 'get_identity_profile': {
|
|
return { url: `/api/v1/identity/${a.uuid}/profile`, method: 'GET' }
|
|
}
|
|
case 'get_face_thumbnail': {
|
|
let url = `/api/v1/face-thumbnail?uuid=${a.uuid}&frame=${a.frame || 0}`
|
|
if (a.bboxX != null) url += `&bbox_x=${a.bboxX}`
|
|
if (a.bboxY != null) url += `&bbox_y=${a.bboxY}`
|
|
if (a.bboxW != null) url += `&bbox_w=${a.bboxW}`
|
|
if (a.bboxH != null) url += `&bbox_h=${a.bboxH}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'get_file_thumbnail_by_path': {
|
|
return { url: `/api/v1/file/thumbnail?path=${encodeURIComponent(a.path)}&frame=${a.frame || 30}`, method: 'GET' }
|
|
}
|
|
case 'regenerate_trace_thumbnail': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/trace/${a.traceId}/regenerate-thumbnail`, method: 'POST' }
|
|
}
|
|
|
|
// --- Video API ---
|
|
case 'get_video_stream': {
|
|
let url = `/api/v1/file/${a.uuid}/video`
|
|
const params: string[] = []
|
|
if (a.startFrame != null) params.push(`start_frame=${a.startFrame}`)
|
|
if (a.endFrame != null) params.push(`end_frame=${a.endFrame}`)
|
|
if (a.original === true) params.push('original=true')
|
|
if (params.length) url += '?' + params.join('&')
|
|
return { url, method: 'GET' }
|
|
}
|
|
|
|
// --- Identity Management ---
|
|
case 'update_identity': {
|
|
const meta: any = a.metadataJson ? JSON.parse(a.metadataJson) : {}
|
|
const body: any = {}
|
|
if (a.name) body.name = a.name
|
|
if (Object.keys(meta).length) body.metadata = meta
|
|
return { url: `/api/v1/identity/${a.uuid}`, method: 'PATCH', body }
|
|
}
|
|
case 'update_identity_name': {
|
|
return { url: `/api/v1/identity/${a.uuid}`, method: 'PATCH', body: { name: a.name } }
|
|
}
|
|
case 'update_identity_status': {
|
|
return { url: `/api/v1/identity/${a.uuid}`, method: 'PATCH', body: { metadata: { status: a.status } } }
|
|
}
|
|
case 'update_identity_starred': {
|
|
return { url: `/api/v1/identity/${a.uuid}`, method: 'PATCH', body: { metadata: { starred: a.starred } } }
|
|
}
|
|
case 'upload_profile_image': {
|
|
return { url: `/api/v1/identity/${a.uuid}/profile-image`, method: 'POST' }
|
|
}
|
|
case 'set_profile_from_face': {
|
|
const body: any = { file_uuid: a.fileUuid }
|
|
if (a.faceId) body.face_id = a.faceId
|
|
if (a.id) body.id = a.id
|
|
if (a.traceId) body.trace_id = a.traceId
|
|
if (a.frameNumber) body.frame_number = a.frameNumber
|
|
return { url: `/api/v1/identity/${a.uuid}/profile-image/from-face`, method: 'POST', body }
|
|
}
|
|
case 'delete_identity': {
|
|
return { url: `/api/v1/identity/${a.uuid}`, method: 'DELETE' }
|
|
}
|
|
case 'create_identity_from_face': {
|
|
const body: any = { name: a.name }
|
|
if (a.traceIds) body.trace_ids = a.traceIds
|
|
return { url: `/api/v1/file/${a.fileUuid}/pending-person`, method: 'POST', body }
|
|
}
|
|
|
|
// --- Identity Operations ---
|
|
case 'merge_identities': {
|
|
return { url: `/api/v1/identity/${a.uuid}/mergeinto`, method: 'POST', body: { into_uuid: a.intoUuid } }
|
|
}
|
|
|
|
// --- Trace Management ---
|
|
case 'list_traces': {
|
|
return { url: `/api/v1/file/${a.file_uuid}/traces`, method: 'POST' }
|
|
}
|
|
case 'get_file_traces': {
|
|
const fu = a.fileUuid ?? a.file_uuid
|
|
return { url: `/api/v1/file/${fu}/traces`, method: 'POST', body: { page: a.page || 1, page_size: a.page_size || 3000, limit: a.limit || 3000 } }
|
|
}
|
|
case 'search_file_faces': {
|
|
const fu = a.fileUuid ?? a.file_uuid
|
|
const params = new URLSearchParams()
|
|
if (a.q) params.set('q', a.q)
|
|
params.set('page', String(a.page || 1))
|
|
params.set('page_size', String(a.perPage || 20))
|
|
if (a.binding) params.set('binding', a.binding)
|
|
if (a.minConfidence != null) params.set('min_confidence', String(a.minConfidence))
|
|
return { url: `/api/v1/file/${fu}/faces/search?${params.toString()}`, method: 'GET' }
|
|
}
|
|
case 'get_trace_profile': {
|
|
const params = new URLSearchParams()
|
|
const fu = a.file_uuid ?? a.fileUuid
|
|
const tid = a.trace_id ?? a.traceId
|
|
if (fu) params.set('file_uuid', fu)
|
|
if (tid != null) params.set('trace_id', String(tid))
|
|
return { url: `/api/v1/trace-profile?${params.toString()}`, method: 'GET' }
|
|
}
|
|
case 'get_trace_profile_file': {
|
|
const params = new URLSearchParams()
|
|
const fu = a.file_uuid ?? a.fileUuid
|
|
const tid = a.trace_id ?? a.traceId
|
|
if (fu) params.set('file_uuid', fu)
|
|
if (tid != null) params.set('trace_id', String(tid))
|
|
return { url: `/api/v1/trace-profile-file?${params.toString()}`, method: 'GET' }
|
|
}
|
|
case 'update_trace_profile': {
|
|
const body = a.body ?? {
|
|
file_uuid: a.file_uuid ?? a.fileUuid,
|
|
trace_id: a.trace_id ?? a.traceId,
|
|
...Object.fromEntries(Object.entries(a).filter(([k]) => !['fileUuid','traceId','file_uuid','trace_id','body'].includes(k)))
|
|
}
|
|
return { url: '/api/v1/trace-profile', method: 'PUT', body }
|
|
}
|
|
case 'delete_trace': {
|
|
const body: any = { mode: a.mode ?? 'ungroup' }
|
|
if (a.mode === 'delete') {
|
|
if (a.hard_delete !== undefined) body.hard_delete = a.hard_delete
|
|
} else {
|
|
body.hard_delete = a.hard_delete ?? true
|
|
}
|
|
return { url: `/api/v1/file/${a.file_uuid}/trace/${a.trace_id}`, method: 'DELETE', body }
|
|
}
|
|
case 'restore_trace': {
|
|
return { url: `/api/v1/file/${a.file_uuid}/trace/${a.trace_id}/restore`, method: 'POST' }
|
|
}
|
|
case 'merge_trace': {
|
|
return { url: `/api/v1/file/${a.file_uuid}/trace/${a.source_id}/merge/${a.target_id}`, method: 'POST' }
|
|
}
|
|
case 'merge_groups': {
|
|
return {
|
|
url: `/api/v1/file/${a.fileUuid}/groups/merge`,
|
|
method: 'POST',
|
|
body: {
|
|
file_uuid: a.fileUuid,
|
|
source_groups: a.sourceGroups,
|
|
target_group_name: a.targetGroupName
|
|
}
|
|
}
|
|
}
|
|
case 'bind_face': {
|
|
const bindBody: any = { file_uuid: a.fileUuid }
|
|
if (a.faceId) bindBody.face_id = a.faceId
|
|
if (a.faceRowId) bindBody.id = a.faceRowId
|
|
return { url: `/api/v1/identity/${a.uuid}/bind`, method: 'POST', body: bindBody }
|
|
}
|
|
case 'unbind_face': {
|
|
const unbindBody: any = { file_uuid: a.fileUuid }
|
|
if (a.faceId) unbindBody.face_id = a.faceId
|
|
if (a.faceRowId) unbindBody.id = a.faceRowId
|
|
if (a.frameNumber != null) unbindBody.frame_number = a.frameNumber
|
|
return { url: `/api/v1/identity/${a.uuid}/unbind`, method: 'POST', body: unbindBody }
|
|
}
|
|
|
|
// --- Trace Operations ---
|
|
case 'merge_traces': {
|
|
return { url: `/api/v1/file/${a.file_uuid}/trace/${a.source_trace_id}/merge/${a.target_trace_id}`, method: 'POST' }
|
|
}
|
|
|
|
// --- Identity Undo/Redo ---
|
|
case 'identity_undo': {
|
|
const body: any = {}
|
|
if (a.steps != null) body.steps = a.steps
|
|
return { url: `/api/v1/identity/${a.uuid}/undo`, method: 'POST', body }
|
|
}
|
|
case 'identity_redo': {
|
|
const body: any = {}
|
|
if (a.steps != null) body.steps = a.steps
|
|
return { url: `/api/v1/identity/${a.uuid}/redo`, method: 'POST', body }
|
|
}
|
|
case 'identity_history': {
|
|
let url = `/api/v1/identity/${a.uuid}/history`
|
|
const params: string[] = []
|
|
if (a.page != null) params.push(`page=${a.page}`)
|
|
if (a.pageSize != null) params.push(`page_size=${a.pageSize}`)
|
|
if (params.length) url += '?' + params.join('&')
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'identity_bind_undo': {
|
|
const body: any = {}
|
|
if (a.steps != null) body.steps = a.steps
|
|
return { url: `/api/v1/identity/${a.uuid}/bind/undo`, method: 'POST', body }
|
|
}
|
|
case 'identity_bind_redo': {
|
|
const body: any = {}
|
|
if (a.steps != null) body.steps = a.steps
|
|
return { url: `/api/v1/identity/${a.uuid}/bind/redo`, method: 'POST', body }
|
|
}
|
|
case 'identity_bind_history': {
|
|
let url = `/api/v1/identity/${a.uuid}/bind/history`
|
|
const params: string[] = []
|
|
if (a.page != null) params.push(`page=${a.page}`)
|
|
if (a.pageSize != null) params.push(`page_size=${a.pageSize}`)
|
|
if (params.length) url += '?' + params.join('&')
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'merge_undo': {
|
|
return { url: `/api/v1/identity/merge/${a.mergeId}/undo`, method: 'POST' }
|
|
}
|
|
case 'merge_redo': {
|
|
return { url: `/api/v1/identity/merge/${a.mergeId}/redo`, method: 'POST' }
|
|
}
|
|
case 'merge_history': {
|
|
let url = '/api/v1/identity/merge/history'
|
|
const params: string[] = []
|
|
if (a.sourceUuid) params.push(`source_uuid=${encodeURIComponent(a.sourceUuid)}`)
|
|
if (a.targetUuid) params.push(`target_uuid=${encodeURIComponent(a.targetUuid)}`)
|
|
if (a.page != null) params.push(`page=${a.page}`)
|
|
if (a.pageSize != null) params.push(`page_size=${a.pageSize}`)
|
|
if (params.length) url += '?' + params.join('&')
|
|
return { url, method: 'GET' }
|
|
}
|
|
|
|
// --- File Operations ---
|
|
case 'get_health': {
|
|
return { url: '/api/v1/health', method: 'GET' }
|
|
}
|
|
case 'list_jobs': {
|
|
return { url: '/api/v1/jobs', method: 'POST', body: {} }
|
|
}
|
|
case 'register_file': {
|
|
return { url: '/api/v1/files/register', method: 'POST', body: { file_path: a.filePath } }
|
|
}
|
|
case 'get_command_status': {
|
|
return { url: `/api/v1/command/${a.commandId}`, method: 'GET' }
|
|
}
|
|
case 'process_file': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/process`, method: 'POST', body: { processors: a.processors } }
|
|
}
|
|
case 'get_identity_matches': {
|
|
return { url: `/api/v1/identity-matches?file_hash=${a.fileHash}`, method: 'GET' }
|
|
}
|
|
case 'run_cluster_agent': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/cluster-agent`, method: 'POST', body: { confirm: true }, timeout: 600000 }
|
|
}
|
|
case 'get_cluster_results': {
|
|
return { url: `/api/v1/file/${a.fileHash}/json/face_cluster`, method: 'POST', body: {} }
|
|
}
|
|
case 'get_similar_face_groups': {
|
|
const params = new URLSearchParams()
|
|
if (a.traceIds) params.set('trace_ids', String(a.traceIds))
|
|
if (a.topK) params.set('top_k', String(a.topK))
|
|
if (a.minScore) params.set('min_score', String(a.minScore))
|
|
return { url: `/api/v1/file/${a.fileUuid}/face-groups/similar?${params.toString()}`, method: 'GET' }
|
|
}
|
|
case 'update_trace_profile_group': {
|
|
return { url: '/api/v1/trace-profile/group', method: 'PUT', body: { file_uuid: a.fileUuid, trace_ids: a.traceIds, name: a.name, key_frame: a.keyFrame, key_face: a.keyFace } }
|
|
}
|
|
case 'get_file_profile': {
|
|
return { url: `/api/v1/file-profile?file_uuid=${a.fileUuid}`, method: 'GET' }
|
|
}
|
|
case 'update_file_profile': {
|
|
const body: any = { file_uuid: a.fileUuid, file_path: a.filePath, file_name: a.fileName }
|
|
if (a.faceFilter != null) body.face_filter = a.faceFilter
|
|
return { url: '/api/v1/file-profile', method: 'PUT', body }
|
|
}
|
|
case 'get_processor_json': {
|
|
return { url: `/api/v1/processor-json?file_hash=${a.fileHash}&processor=${a.processor}`, method: 'GET' }
|
|
}
|
|
case 'get_file_processor_json': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/json/${a.processor}`, method: 'POST', body: {} }
|
|
}
|
|
case 'create_pending_identity': {
|
|
return { url: '/api/v1/identities/pending', method: 'POST', body: { file_uuid: a.fileUuid, trace_id: a.traceId, face_id: a.faceId, cluster_id: a.clusterId, trace_count: a.traceCount } }
|
|
}
|
|
case 'bind_speakers': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/bind-speakers`, method: 'POST' }
|
|
}
|
|
case 'unregister_file': {
|
|
const body: any = { file_uuid: a.fileUuid }
|
|
if (a.deleteOutputFiles != null) body.delete_output_files = a.deleteOutputFiles
|
|
return { url: '/api/v1/unregister', method: 'POST', body, timeout: UNREGISTER_TIMEOUT }
|
|
}
|
|
case 'ingest_file': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/checkin`, method: 'POST' }
|
|
}
|
|
case 'checkout_file': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/checkout`, method: 'POST' }
|
|
}
|
|
|
|
// --- File Detail ---
|
|
case 'get_file_info': {
|
|
return { url: `/api/v1/file/${a.uuid}`, method: 'GET' }
|
|
}
|
|
case 'get_processor_counts': {
|
|
return { url: `/api/v1/file/${a.uuid}/processor-counts`, method: 'GET' }
|
|
}
|
|
case 'get_progress': {
|
|
return { url: `/api/v1/progress/${a.fileUuid}`, method: 'POST', body: {} }
|
|
}
|
|
case 'get_pipeline_stats': {
|
|
return { url: `/api/v1/stats/pipeline/${a.fileUuid}`, method: 'GET' }
|
|
}
|
|
case 'get_file_stats': {
|
|
return { url: `/api/v1/stats/file/${a.fileUuid}`, method: 'GET' }
|
|
}
|
|
|
|
// --- Search History ---
|
|
case 'get_search_history': {
|
|
const limit = a.limit ?? 30
|
|
return { url: `/api/v1/search-history?limit=${limit}`, method: 'GET' }
|
|
}
|
|
case 'save_search_history': {
|
|
return { url: '/api/v1/search-history', method: 'POST', body: { id: a.id, query: a.query, title: a.title, chat_state: a.chatState, mode: a.mode } }
|
|
}
|
|
case 'rename_search_history': {
|
|
return { url: `/api/v1/search-history/${a.id}/rename`, method: 'PATCH', body: { title: a.title } }
|
|
}
|
|
case 'pin_search_history': {
|
|
return { url: `/api/v1/search-history/${a.id}/pin`, method: 'PATCH', body: { pinned: a.pinned } }
|
|
}
|
|
case 'delete_search_history': {
|
|
return { url: `/api/v1/search-history/${a.id}`, method: 'DELETE' }
|
|
}
|
|
|
|
// --- Bookmarks ---
|
|
case 'get_bookmarks': {
|
|
return { url: '/api/v1/bookmarks', method: 'GET' }
|
|
}
|
|
case 'save_bookmark': {
|
|
return { url: '/api/v1/bookmarks', method: 'POST', body: { label: a.label, history_id: a.historyId } }
|
|
}
|
|
case 'delete_bookmark': {
|
|
return { url: `/api/v1/bookmarks/${a.id}`, method: 'DELETE' }
|
|
}
|
|
|
|
// --- Video Marks ---
|
|
case 'get_video_marks': {
|
|
return { url: `/api/v1/video-marks?file_uuid=${encodeURIComponent(a.fileUuid)}`, method: 'GET' }
|
|
}
|
|
case 'save_video_mark': {
|
|
return { url: '/api/v1/video-marks', method: 'POST', body: { file_uuid: a.fileUuid, tag: a.tag, start_frame: a.startFrame, end_frame: a.endFrame, note: a.note } }
|
|
}
|
|
case 'delete_video_mark': {
|
|
return { url: `/api/v1/video-mark/${a.id}`, method: 'DELETE' }
|
|
}
|
|
|
|
// --- Service Management ---
|
|
case 'stop_service': {
|
|
const mbUrl = detectBaseUrl()
|
|
return { url: `${mbUrl}/api/v2/service/stop`, method: 'POST', body: { name: a.name } }
|
|
}
|
|
case 'start_service': {
|
|
const mbUrl = detectBaseUrl()
|
|
return { url: `${mbUrl}/api/v2/service/start`, method: 'POST', body: { name: a.name } }
|
|
}
|
|
|
|
// --- Admin Panel ---
|
|
case 'get_system_stats':
|
|
return { url: '/api/v2/admin/system-stats', method: 'GET' }
|
|
case 'validate_system_stats': {
|
|
let url = '/api/v1/system-stats/validate'
|
|
if (a.layers) url += `?layers=${a.layers}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'list_admin_users':
|
|
return { url: '/api/v2/admin/users', method: 'GET' }
|
|
case 'list_shares':
|
|
return { url: '/api/v2/admin/shares', method: 'GET' }
|
|
|
|
// --- Pose & Appearance ---
|
|
case 'get_pose': {
|
|
let url = `/api/v1/file/${a.fileUuid}/pose?frame=${a.frame}`
|
|
if (a.bboxX != null) url += `&bbox_x=${a.bboxX}`
|
|
if (a.bboxY != null) url += `&bbox_y=${a.bboxY}`
|
|
if (a.bboxW != null) url += `&bbox_w=${a.bboxW}`
|
|
if (a.bboxH != null) url += `&bbox_h=${a.bboxH}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'get_appearance': {
|
|
let url = `/api/v1/file/${a.fileUuid}/appearance?frame=${a.frame}`
|
|
if (a.bboxX != null) url += `&bbox_x=${a.bboxX}`
|
|
if (a.bboxY != null) url += `&bbox_y=${a.bboxY}`
|
|
if (a.bboxW != null) url += `&bbox_w=${a.bboxW}`
|
|
if (a.bboxH != null) url += `&bbox_h=${a.bboxH}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
case 'get_face_landmarks': {
|
|
let url = `/api/v1/file/${a.fileUuid}/face-landmarks?frame=${a.frame}`
|
|
if (a.bboxX != null) url += `&bbox_x=${a.bboxX}`
|
|
if (a.bboxY != null) url += `&bbox_y=${a.bboxY}`
|
|
if (a.bboxW != null) url += `&bbox_w=${a.bboxW}`
|
|
if (a.bboxH != null) url += `&bbox_h=${a.bboxH}`
|
|
return { url, method: 'GET' }
|
|
}
|
|
|
|
case 'sync_file_status': {
|
|
return { url: `/api/v1/file/${a.fileUuid}/sync-status`, method: 'POST' }
|
|
}
|
|
|
|
default:
|
|
throw new Error(`Unknown command: ${cmd}`)
|
|
}
|
|
}
|
|
|
|
// Transform HTTP response to match Tauri invoke format
|
|
// Some endpoints need data reshaping to match what the Rust commands return
|
|
export function transformResponse(cmd: string, data: any, args?: Record<string, any>): any {
|
|
switch (cmd) {
|
|
case 'get_files': {
|
|
const files = data.files || data.data || data || []
|
|
return files.map((f: any) => ({
|
|
file_uuid: f.file_uuid || '',
|
|
file_name: f.file_name || '',
|
|
file_path: f.file_path || '',
|
|
file_size: f.file_size || 0,
|
|
modified_time: f.modified_time || '',
|
|
isRegistered: f.isRegistered ?? f.is_registered ?? false,
|
|
status: f.status || '',
|
|
status_updated_at: f.status_updated_at || null,
|
|
registrationTime: f.registration_time || null,
|
|
ingested: f.ingested ?? ['completed','keyword_ready','semantic_ready','face_mgmt_ready','agent_ready'].includes(f.status),
|
|
}))
|
|
}
|
|
case 'get_people': {
|
|
const identities = data.identities || data.data || data || []
|
|
return identities.map((p: any) => ({
|
|
identity_uuid: p.identity_uuid || '',
|
|
name: p.name || '',
|
|
starred: p.metadata?.starred ?? p.starred ?? false,
|
|
status: p.status || p.metadata?.status || 'pending',
|
|
metadata: p.metadata || {},
|
|
file_uuids: p.file_uuids || [],
|
|
source: p.source || '',
|
|
tmdb_id: p.tmdb_id ?? null,
|
|
}))
|
|
}
|
|
case 'get_file_identities': {
|
|
return data
|
|
}
|
|
case 'get_faces': {
|
|
const faces = data.data || data.faces || data || []
|
|
return faces.map((f: any) => ({
|
|
id: f.id,
|
|
file_uuid: f.file_uuid || '',
|
|
frame_number: f.frame_number || 0,
|
|
timestamp_secs: f.timestamp_secs || 0,
|
|
face_id: f.face_id ?? null,
|
|
confidence: f.confidence || 0,
|
|
bbox: f.bbox ? { x: f.bbox.x, y: f.bbox.y, width: f.bbox.width, height: f.bbox.height } : null,
|
|
}))
|
|
}
|
|
case 'get_traces': {
|
|
const traces = data.traces || data.data || []
|
|
return {
|
|
total: data.total || traces.length || 0,
|
|
traces: traces.map((t: any) => ({
|
|
trace_id: t.trace_id,
|
|
file_uuid: t.file_uuid || '',
|
|
frame_count: t.frame_count || 0,
|
|
first_frame: t.first_frame || 0,
|
|
last_frame: t.last_frame || 0,
|
|
first_sec: t.first_sec || 0,
|
|
last_sec: t.last_sec || 0,
|
|
avg_confidence: t.avg_confidence || 0,
|
|
face_id: t.face_id ?? null,
|
|
})),
|
|
}
|
|
}
|
|
case 'get_face_candidates': {
|
|
const candidates = data.candidates || data.data || data || []
|
|
return candidates.map((c: any) => ({
|
|
id: c.id,
|
|
face_id: c.face_id ?? null,
|
|
file_uuid: c.file_uuid || '',
|
|
frame_number: c.frame_number || 0,
|
|
confidence: c.confidence || 0,
|
|
bbox: c.bbox ? { x: c.bbox.x, y: c.bbox.y, width: c.bbox.width, height: c.bbox.height } : null,
|
|
}))
|
|
}
|
|
case 'get_unassigned_traces': {
|
|
const traces = data.traces || data.data || []
|
|
return {
|
|
total: data.total || traces.length || 0,
|
|
traces: traces.map((t: any) => ({
|
|
trace_id: t.trace_id,
|
|
file_uuid: t.file_uuid || '',
|
|
frame_count: t.frame_count || 0,
|
|
start_frame: t.start_frame || 0,
|
|
end_frame: t.end_frame || 1,
|
|
best_face_id: t.best_face_id,
|
|
best_face_frame: t.best_face_frame || 1,
|
|
best_face_confidence: t.best_face_confidence || 1,
|
|
best_face_bbox: t.best_face_bbox ? { x: t.best_face_bbox.x, y: t.best_face_bbox.y, width: t.best_face_bbox.width, height: t.best_face_bbox.height } : null,
|
|
best_face_capture_quality: t.best_capture_quality ?? null,
|
|
best_face_eye_ratio: t.best_face_eye_ratio ?? null,
|
|
best_face_nose_ratio: t.best_face_nose_ratio ?? null,
|
|
best_face_pose_score: t.best_face_pose_score ?? null,
|
|
})),
|
|
}
|
|
}
|
|
case 'get_pending_persons': {
|
|
return {
|
|
pending_persons: (data.pending_persons || data.data || []).map((p: any) => ({
|
|
identity_uuid: p.identity_uuid || '',
|
|
name: p.name || '',
|
|
trace_count: p.trace_count || 0,
|
|
})),
|
|
}
|
|
}
|
|
case 'get_file_traces': {
|
|
const traces = data.traces || data.data || []
|
|
const fu = args?.fileUuid ?? args?.file_uuid ?? ''
|
|
return {
|
|
total: data.total_traces || traces.length || 0,
|
|
traces: traces.map((t: any) => ({
|
|
trace_id: t.trace_id,
|
|
file_uuid: t.file_uuid || fu,
|
|
face_count: t.face_count || 0,
|
|
frame_count: t.face_count || 0,
|
|
start_frame: t.start_frame || 0,
|
|
end_frame: t.end_frame || 1,
|
|
start_time: t.start_time || 0,
|
|
end_time: t.end_time || 0,
|
|
duration_sec: t.duration_sec || 0,
|
|
avg_confidence: t.avg_confidence || 0,
|
|
best_face_id: t.best_face_id ?? null,
|
|
best_face_frame: t.best_face_frame || 0,
|
|
best_face_confidence: t.best_face_confidence || 0,
|
|
best_face_bbox: t.best_face_bbox || null,
|
|
best_face_capture_quality: t.best_face_capture_quality ?? null,
|
|
avg_capture_quality: t.avg_capture_quality ?? null,
|
|
thumbnail_url: t.thumbnail_url || '',
|
|
})),
|
|
}
|
|
}
|
|
case 'search_file_faces': {
|
|
const items = data.data || data.traces || []
|
|
const fu = args?.fileUuid ?? args?.file_uuid ?? ''
|
|
return {
|
|
total: data.total || items.length || 0,
|
|
page: data.page || 1,
|
|
page_size: data.page_size || 20,
|
|
traces: items.map((t: any) => ({
|
|
trace_id: t.trace_id ?? 0,
|
|
file_uuid: t.file_uuid || fu,
|
|
name: t.name ?? null,
|
|
start_frame: t.start_frame || 0,
|
|
end_frame: t.end_frame || 1,
|
|
frame_count: t.frame_count || 0,
|
|
avg_confidence: t.avg_confidence || 0,
|
|
best_face_bbox: t.best_face_bbox ?? null,
|
|
thumbnail_url: t.thumbnail_url || '',
|
|
})),
|
|
}
|
|
}
|
|
case 'search_llm_smart': {
|
|
const results = data.results || data.data || data || []
|
|
return results.map((r: any) => {
|
|
const asr = r.asr || null
|
|
const asrSegments = asr?.segments || []
|
|
const asrLang = asr?.language || ''
|
|
const asrLangProb = asr?.language_probability || 0
|
|
|
|
let asrStatus: 'no_audio_track' | 'silent_audio' | 'has_transcript' | 'processing' = 'processing'
|
|
let asrMessage = 'Processing'
|
|
|
|
if (asrSegments.length === 0) {
|
|
if (asrLang === '' && asrLangProb === 0) {
|
|
asrStatus = 'no_audio_track'
|
|
asrMessage = 'No Audio Track'
|
|
} else {
|
|
asrStatus = 'silent_audio'
|
|
asrMessage = asrLang ? `No Speech (${asrLang})` : 'No Speech'
|
|
}
|
|
} else {
|
|
asrStatus = 'has_transcript'
|
|
asrMessage = `${asrSegments.length} segments (${asrLang || 'unknown'})`
|
|
}
|
|
|
|
return {
|
|
file_uuid: r.file_uuid || '',
|
|
start_time: r.start_time ?? 0,
|
|
end_time: r.end_time ?? 0,
|
|
start_frame: r.start_frame ?? 0,
|
|
end_frame: r.end_frame ?? 0,
|
|
summary: r.summary || r.raw_text || '',
|
|
similarity: r.similarity || 0,
|
|
file_name: r.file_name || null,
|
|
asr_status: asrStatus,
|
|
asr_message: asrMessage,
|
|
asr_segment_count: asrSegments.length,
|
|
asr_language: asrLang,
|
|
}
|
|
})
|
|
}
|
|
case 'search_keyword': {
|
|
const results = data.results || data.data || data || []
|
|
return results.map((r: any) => ({
|
|
file_uuid: r.file_uuid || '',
|
|
start_time: r.start_time ?? 0,
|
|
end_time: r.end_time ?? 0,
|
|
start_frame: r.start_frame ?? 0,
|
|
end_frame: r.end_frame ?? 0,
|
|
summary: r.summary || r.raw_text || '',
|
|
similarity: r.similarity || 0,
|
|
file_name: r.file_name || null,
|
|
source_type: r.source_type || null,
|
|
}))
|
|
}
|
|
case 'search_semantic': {
|
|
const results = data.results || data.data || data || []
|
|
return results.map((r: any) => ({
|
|
file_uuid: r.file_uuid || '',
|
|
start_time: r.start_time ?? 0,
|
|
end_time: r.end_time ?? 0,
|
|
start_frame: r.start_frame ?? 0,
|
|
end_frame: r.end_frame ?? 0,
|
|
summary: r.summary || r.raw_text || '',
|
|
similarity: r.similarity || 0,
|
|
file_name: r.file_name || null,
|
|
source_type: r.source_type || null,
|
|
}))
|
|
}
|
|
case 'search_people': {
|
|
return data || { people: [] }
|
|
}
|
|
case 'search_identities': {
|
|
const results = data.results || data.data || data || []
|
|
return results.map((r: any) => ({
|
|
identity_id: r.identity_id,
|
|
name: r.name,
|
|
source: r.source || '',
|
|
tmdb_id: r.tmdb_id ?? null,
|
|
file_uuid: r.file_uuid ?? null,
|
|
start_time: r.start_time ?? 0,
|
|
end_time: r.end_time ?? 0,
|
|
start_frame: r.start_frame ?? null,
|
|
end_frame: r.end_frame ?? null,
|
|
text_content: r.text_content ?? null,
|
|
}))
|
|
}
|
|
case 'register_file': {
|
|
return {
|
|
success: data.success ?? false,
|
|
command_id: data.command_id ?? '',
|
|
status: data.status ?? '',
|
|
file_uuid: data.file_uuid ?? '',
|
|
file_name: data.file_name ?? '',
|
|
message: data.message ?? '',
|
|
}
|
|
}
|
|
case 'get_command_status': {
|
|
return data || null
|
|
}
|
|
case 'process_file': {
|
|
return {
|
|
success: data.success ?? false,
|
|
file_uuid: data.file_uuid ?? '',
|
|
message: data.message ?? '',
|
|
}
|
|
}
|
|
case 'run_cluster_agent': {
|
|
return {
|
|
success: data.success ?? false,
|
|
file_uuid: data.file_uuid ?? '',
|
|
message: data.message ?? '',
|
|
clusters: data.clusters ?? 0,
|
|
total_traces: data.total_traces ?? 0,
|
|
}
|
|
}
|
|
case 'get_cluster_results': {
|
|
return data || null
|
|
}
|
|
case 'get_similar_face_groups': {
|
|
return data || null
|
|
}
|
|
case 'get_trace_profile': { return data || null }
|
|
case 'get_trace_profile_file': { return data || null }
|
|
case 'update_trace_profile': { return data || { success: true } }
|
|
case 'update_trace_profile_group': { return data || { success: true } }
|
|
case 'get_file_profile': { return data || null }
|
|
case 'update_file_profile': { return data || { success: true } }
|
|
case 'get_processor_json': {
|
|
return data || null
|
|
}
|
|
case 'bind_speakers': {
|
|
return {
|
|
success: data.success ?? false,
|
|
bindings: data.bindings ?? 0,
|
|
message: data.message ?? '',
|
|
}
|
|
}
|
|
case 'create_pending_identity': {
|
|
return {
|
|
success: data.success ?? false,
|
|
identity_uuid: data.identity_uuid ?? '',
|
|
identity_id: data.identity_id ?? 0,
|
|
message: data.message ?? '',
|
|
}
|
|
}
|
|
case 'unregister_file': {
|
|
return {
|
|
success: data.success ?? false,
|
|
command_id: data.command_id ?? '',
|
|
file_uuid: data.file_uuid ?? '',
|
|
message: data.message ?? '',
|
|
}
|
|
}
|
|
case 'ingest_file':
|
|
case 'checkout_file': {
|
|
return {
|
|
success: data.success ?? false,
|
|
file_uuid: data.file_uuid ?? '',
|
|
message: data.message ?? '',
|
|
}
|
|
}
|
|
case 'update_identity':
|
|
case 'upload_profile_image':
|
|
case 'get_identity_profile':
|
|
case 'get_face_thumbnail':
|
|
case 'get_file_thumbnail_by_path':
|
|
case 'get_search_history':
|
|
case 'save_search_history':
|
|
case 'rename_search_history':
|
|
case 'pin_search_history':
|
|
case 'delete_search_history':
|
|
case 'get_bookmarks':
|
|
case 'save_bookmark':
|
|
case 'delete_bookmark':
|
|
case 'identity_undo':
|
|
case 'identity_redo':
|
|
case 'identity_history':
|
|
case 'identity_bind_undo':
|
|
case 'identity_bind_redo':
|
|
case 'identity_bind_history':
|
|
case 'merge_undo':
|
|
case 'merge_redo':
|
|
case 'merge_history':
|
|
return data
|
|
case 'create_identity_from_face': {
|
|
const respData = data.data || data
|
|
return {
|
|
success: data.success ?? false,
|
|
identity_uuid: respData.identity_uuid ?? respData.uuid ?? '',
|
|
name: respData.name ?? '',
|
|
}
|
|
}
|
|
case 'get_pipeline_stats':
|
|
case 'get_file_stats': {
|
|
return data
|
|
}
|
|
case 'get_pose':
|
|
case 'get_appearance':
|
|
case 'get_face_landmarks':
|
|
return data
|
|
case 'get_system_stats':
|
|
case 'list_shares':
|
|
return data
|
|
case 'list_admin_users': {
|
|
const users = data.users || data.data || data || []
|
|
return users.map((u: any) => ({
|
|
username: u.username || '',
|
|
homeDir: u.home_dir ?? u.homeDir ?? '',
|
|
status: u.status ?? 1,
|
|
}))
|
|
}
|
|
default:
|
|
return data
|
|
}
|
|
} |