feat: OCR independent chunks + TMDb seed with file_uuid

- Rule 1 now creates OCR-only chunks instead of merging into ASRX
- generate_seed_embeddings.py supports --file-uuid parameter
- get_seeds() filters by file_uuid
- identity_matcher.py uses file_uuid for seed matching
- Push QDRANT_API_KEY to Python subprocesses
- Face clustering uses frame+bbox matching instead of face_id
- Portal uses JWT authentication
- FilesView filter logic fixed
This commit is contained in:
Accusys
2026-07-06 08:56:56 +08:00
parent cb604b74ec
commit 799ede5a0e
10 changed files with 147 additions and 38 deletions

View File

@@ -1,4 +1,4 @@
# Portal Development Environment
VITE_APP_TITLE=Momentry Portal (Development)
VITE_API_BASE_URL=http://127.0.0.1:3003
VITE_API_BASE_URL=http://127.0.0.1:3002
VITE_API_KEY=muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69

View File

@@ -75,7 +75,7 @@ export interface UnregisterResponse {
// ── Config (browser-only, stored in localStorage) ───────────────────────
const DEFAULT_CONFIG: PortalConfig = {
api_base_url: import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:3003',
api_base_url: import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:3002',
api_key: import.meta.env.VITE_API_KEY || '',
timeout_secs: 30,
}
@@ -99,13 +99,20 @@ export function saveConfig(config: PortalConfig): void {
export async function logout(): Promise<void> {
try {
const config = getConfig();
const jwtToken = localStorage.getItem('momentry_jwt');
const apiKey = config.api_key || localStorage.getItem('momentry_api_key');
if (apiKey) {
if (jwtToken || apiKey) {
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (jwtToken) {
headers['Authorization'] = `Bearer ${jwtToken}`;
} else if (apiKey) {
headers['X-API-Key'] = apiKey;
}
// Call logout API to invalidate session on server side (if implemented)
// For now, just best effort
await fetch(`${config.api_base_url}/api/v1/auth/logout`, {
method: 'POST',
headers: { 'X-API-Key': apiKey }
headers
}).catch(() => {}); // Ignore network errors
}
} catch (e) {
@@ -129,6 +136,7 @@ function handleSessionExpired() {
localStorage.removeItem('momentry_user');
localStorage.removeItem('portal_config');
localStorage.removeItem('momentry_api_key');
localStorage.removeItem('momentry_jwt');
if (window.location.pathname !== '/login') {
window.location.href = '/login';
}
@@ -139,12 +147,15 @@ export async function httpFetch<T>(url: string, options?: RequestInit, retries =
// Re-read config to ensure we have the latest key if it changed
const config = getConfig();
// Fallback key check
// Use JWT token if available, fallback to API key
const jwtToken = localStorage.getItem('momentry_jwt');
const apiKey = config.api_key || localStorage.getItem('momentry_api_key') || '';
const headers = new Headers(options?.headers);
headers.set('Content-Type', 'application/json');
if (apiKey) {
if (jwtToken) {
headers.set('Authorization', `Bearer ${jwtToken}`);
} else if (apiKey) {
headers.set('X-API-Key', apiKey);
}
@@ -156,7 +167,7 @@ export async function httpFetch<T>(url: string, options?: RequestInit, retries =
type: 'HTTP',
method,
url,
headers: { ...headers, 'X-API-Key': apiKey ? apiKey.substring(0, 10) + '...' : 'none' },
headers: { ...headers, 'Authorization': jwtToken ? 'Bearer ***' : 'none', 'X-API-Key': apiKey ? apiKey.substring(0, 10) + '...' : 'none' },
body: options?.body ? JSON.parse(options.body as string) : null,
status: 'loading',
data: null,

View File

@@ -116,6 +116,9 @@ const handleLogin = async () => {
if (data.success) {
localStorage.setItem('momentry_user', JSON.stringify(data.user))
localStorage.setItem('momentry_api_key', data.api_key)
if (data.jwt) {
localStorage.setItem('momentry_jwt', data.jwt)
}
saveConfig({ ...config, api_key: data.api_key })
const redirect = (route.query.redirect as string) || '/home'
router.push(redirect)