Initial commit: Momentry Studio v0.1.0
This commit is contained in:
126
src/views/SearchView.vue
Normal file
126
src/views/SearchView.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="search-view">
|
||||
<div class="search-header">
|
||||
<h1>Search</h1>
|
||||
<div class="mode-selector">
|
||||
<button v-for="m in modes" :key="m.value"
|
||||
:class="['mode-btn', { active: mode === m.value }]"
|
||||
@click="mode = m.value">
|
||||
{{ m.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-bar">
|
||||
<input v-model="query" @keyup.enter="search"
|
||||
placeholder="Enter search query..."
|
||||
:disabled="loading" />
|
||||
<button @click="search" :disabled="loading || !query">
|
||||
{{ loading ? 'Searching...' : 'Search' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="results" v-if="results.length">
|
||||
<div v-for="(r, i) in results" :key="i" class="result-card" @click="playVideo(r)">
|
||||
<div class="result-thumb">
|
||||
<div class="play-icon">▶</div>
|
||||
</div>
|
||||
<div class="result-info">
|
||||
<div class="result-meta">
|
||||
<span class="score">{{ (r.similarity * 100).toFixed(0) }}%</span>
|
||||
<span class="time">{{ formatTime(r.start_time) }} - {{ formatTime(r.end_time) }}</span>
|
||||
</div>
|
||||
<p class="summary">{{ r.summary }}</p>
|
||||
<p class="file-name">{{ r.file_name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else-if="searched" class="no-results">No results found</p>
|
||||
|
||||
<VideoPlayer
|
||||
v-if="playing"
|
||||
:file-uuid="currentVideo.fileUuid"
|
||||
:start-time="currentVideo.startTime"
|
||||
:end-time="currentVideo.endTime"
|
||||
:title="currentVideo.title"
|
||||
@close="playing = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import VideoPlayer from '../components/VideoPlayer.vue'
|
||||
|
||||
const modes = [
|
||||
{ label: 'Keyword', value: 'keyword' },
|
||||
{ label: 'Semantic', value: 'semantic' },
|
||||
{ label: 'Agent', value: 'agent' }
|
||||
]
|
||||
|
||||
const mode = ref('semantic')
|
||||
const query = ref('')
|
||||
const results = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const searched = ref(false)
|
||||
|
||||
const playing = ref(false)
|
||||
const currentVideo = ref({ fileUuid: '', startTime: 0, endTime: 0, title: '' })
|
||||
|
||||
async function search() {
|
||||
if (!query.value) return
|
||||
loading.value = true
|
||||
searched.value = false
|
||||
try {
|
||||
results.value = await invoke('search_llm_smart', { query: query.value, limit: 20 })
|
||||
searched.value = true
|
||||
} catch (e) {
|
||||
console.error('Search failed:', e)
|
||||
searched.value = true
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(sec: number): string {
|
||||
const m = Math.floor(sec / 60)
|
||||
const s = Math.floor(sec % 60)
|
||||
return `${m}:${s.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function playVideo(r: any) {
|
||||
currentVideo.value = {
|
||||
fileUuid: r.file_uuid,
|
||||
startTime: r.start_time,
|
||||
endTime: r.end_time,
|
||||
title: r.summary?.slice(0, 60) || r.file_name || 'Video'
|
||||
}
|
||||
playing.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-view { max-width: 900px; }
|
||||
.search-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; }
|
||||
h1 { font-size: 1.5rem; }
|
||||
.mode-selector { display: flex; gap: 8px; }
|
||||
.mode-btn { padding: 8px 16px; border: 1px solid #d1d5db; background: #fff; border-radius: 8px; cursor: pointer; font-size: 0.85rem; }
|
||||
.mode-btn.active { background: #4f46e5; color: #fff; border-color: #4f46e5; }
|
||||
.search-bar { display: flex; gap: 10px; margin-bottom: 24px; }
|
||||
.search-bar input { flex: 1; padding: 12px 16px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 1rem; }
|
||||
.search-bar input:disabled { background: #f3f4f6; }
|
||||
.search-bar button { padding: 12px 24px; background: #4f46e5; color: #fff; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; }
|
||||
.search-bar button:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.result-card { display: flex; gap: 16px; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; margin-bottom: 12px; cursor: pointer; transition: border-color 0.15s, box-shadow 0.15s; }
|
||||
.result-card:hover { border-color: #4f46e5; box-shadow: 0 4px 12px rgba(79,70,229,0.1); }
|
||||
.result-thumb { width: 120px; height: 68px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.play-icon { color: #fff; font-size: 1.5rem; opacity: 0.8; }
|
||||
.result-info { flex: 1; }
|
||||
.result-meta { display: flex; gap: 12px; margin-bottom: 6px; }
|
||||
.score { color: #4f46e5; font-weight: 600; font-size: 0.85rem; }
|
||||
.time { color: #6b7280; font-size: 0.85rem; }
|
||||
.summary { color: #374151; margin-bottom: 4px; }
|
||||
.file-name { color: #9ca3af; font-size: 0.8rem; }
|
||||
.no-results { text-align: center; padding: 40px; color: #6b7280; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user