feat: bind/unbind by id, video streaming proxy, unmute default, library refresh, hamburger click-outside

- Core API: bind/unbind accept id (integer PK) when face_id is null
- Tauri proxy: stream video responses instead of buffering for seek bar
- VideoPlayer: remove muted attribute, unmute by default
- LibraryView: call invalidateFiles() before ensureFiles() on register/unregister
- PeopleView: close sort panel on click-outside, not just hamburger toggle
- bind_face: prefer face_id, fallback to id when face_id is null
This commit is contained in:
2026-06-18 22:24:03 +08:00
parent 97af4da331
commit 7855983dc1
19 changed files with 5321 additions and 793 deletions

View File

@@ -0,0 +1,138 @@
import { ref } from 'vue'
import { apiCall } from '@/api'
export interface HistoryItem {
id: string
query: string
title: string
chat_state: string | null
mode: string | null
pinned: boolean
created_at: string | null
updated_at: string | null
}
export function useSearchHistory() {
const history = ref<HistoryItem[]>([])
const loading = ref(false)
async function loadHistory() {
loading.value = true
try {
const result = await apiCall('get_search_history', { limit: 30 })
history.value = Array.isArray(result) ? result : []
} catch (e) {
console.error('Failed to load history:', e)
} finally {
loading.value = false
}
}
async function saveToHistory(query: string, chatState: string, mode: string) {
const id = `h_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
try {
await apiCall('save_search_history', {
id,
query,
title: query,
chatState,
mode,
})
await loadHistory()
} catch (e) {
console.error('Failed to save history:', e)
}
}
async function renameHistory(id: string, title: string) {
try {
await apiCall('rename_search_history', { id, title })
await loadHistory()
} catch (e) {
console.error('Failed to rename history:', e)
}
}
async function pinHistory(id: string, pinned: boolean) {
try {
await apiCall('pin_search_history', { id, pinned })
await loadHistory()
} catch (e) {
console.error('Failed to pin history:', e)
}
}
async function deleteHistory(id: string) {
try {
await apiCall('delete_search_history', { id })
await loadHistory()
} catch (e) {
console.error('Failed to delete history:', e)
}
}
function restoreFromHistory(item: HistoryItem): any[] | null {
if (!item.chat_state) return null
try {
return JSON.parse(item.chat_state)
} catch {
return null
}
}
return {
history,
loading,
loadHistory,
saveToHistory,
renameHistory,
pinHistory,
deleteHistory,
restoreFromHistory,
}
}
export interface BookmarkItem {
id: number
label: string
history_id: string | null
created_at: string | null
}
export function useBookmarks() {
const bookmarks = ref<BookmarkItem[]>([])
async function loadBookmarks() {
try {
const result = await apiCall('get_bookmarks', {})
bookmarks.value = Array.isArray(result) ? result : []
} catch (e) {
console.error('Failed to load bookmarks:', e)
}
}
async function saveBookmark(label: string, historyId?: string) {
try {
await apiCall('save_bookmark', { label, historyId: historyId || null })
await loadBookmarks()
} catch (e) {
console.error('Failed to save bookmark:', e)
}
}
async function deleteBookmark(id: number) {
try {
await apiCall('delete_bookmark', { id })
await loadBookmarks()
} catch (e) {
console.error('Failed to delete bookmark:', e)
}
}
return {
bookmarks,
loadBookmarks,
saveBookmark,
deleteBookmark,
}
}