Initial commit: Momentry Studio v0.1.0

This commit is contained in:
2026-06-13 17:49:02 +08:00
commit 79e0a862d4
14019 changed files with 1129062 additions and 0 deletions

126
src/views/SearchView.vue Normal file
View 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>