7d299554bf
Nuevos modulos: - services/ai.ts: cliente IA provider-agnostico (OpenRouter, MiniMax) - services/db.ts: Dexie core con tabla settings + project_docs - services/storage.ts: Cache-Aside + Write-Through (L1 Map → L2 Dexie → L3 localStorage) - services/parse-transcription.ts: parser .docx/.vtt/.txt/.md - services/session-analyzer.ts: extraccion IA de sesiones (resumen, tareas, decisiones) - services/project-doc.ts: documento maestro MD (Bloque 1 resumen + Bloque 2 sesiones) - stores/settings.ts: proveedores IA, modelos, API keys separadas por provider - stores/transcriptions.ts: pipeline upload → analyze → create HU en KAPPA - views/SettingsView.vue: configuracion IA (OpenRouter, MiniMax, OpenCode bridge) - views/TranscriptionsView.vue: subida multiple + analisis sesion + visor MD + calendario - components/AiProjectChat.vue: chat contextual por proyecto con selector de modelo Cambios en existentes: - stores/auth.ts, kappa-api.ts, upload-hu.ts: migrados a storage service (Dexie + localStorage) - stores/projects.ts, workitems.ts: kappa_last_project via storage - DashboardView.vue: descripcion reemplazada por AiProjectChat - NewDashboardView.vue: tabs transcriptions + settings + navigate-settings events - NavMain.vue: items Transcripciones + Configuracion - SiteHeader.vue: labels tabs + language via storage - LoginView.vue: remember_email via storage - i18n: +80 keys español/ingles - vite.config.ts: proxy CORS para MiniMax - package.json: +mammoth.js
152 lines
5.3 KiB
TypeScript
152 lines
5.3 KiB
TypeScript
/**
|
|
* Cache-Aside + Write-Through storage para Alpha.
|
|
*
|
|
* ─── Patrón ─────────────────────────────────────────────────────
|
|
*
|
|
* L1 (rápido) → Map en memoria ~0ms
|
|
* L2 (persistente) → Dexie IndexedDB ~5-50ms
|
|
* L3 (fallback) → localStorage ~1ms
|
|
*
|
|
* ─── Lectura (Cache-Aside) ─────────────────────────────────────
|
|
*
|
|
* get(key):
|
|
* 1. L1 hit → return (instantáneo)
|
|
* 2. L2 hit → poblar L1 → return
|
|
* 3. L3 hit → poblar L2 + L1 → return
|
|
* 4. Miss → return null
|
|
*
|
|
* ─── Escritura (Write-Through) ─────────────────────────────────
|
|
*
|
|
* set(key, value):
|
|
* 1. Escribir L1 (instantáneo)
|
|
* 2. Escribir L2 (Dexie, async await)
|
|
* 3. Escribir L3 (localStorage, sync)
|
|
* Si L2 falla → el dato sigue en L1 + L3 (consistencia eventual)
|
|
*
|
|
* ─── ¿Por qué este patrón? ─────────────────────────────────────
|
|
*
|
|
* - Cache-Aside: control explícito de qué se cachea y cuándo
|
|
* - Write-Through: el cache siempre refleja la fuente de verdad
|
|
* - L3 (localStorage) actúa como quorum: si IndexedDB falla,
|
|
* los datos no se pierden
|
|
* - Para RUMBO: mismo patrón, cambia L2 de Dexie a Turso/libSQL
|
|
* y L3 pasa a ser un archivo JSON en ~/.rumbo/cache.json
|
|
*
|
|
* ─── Uso ───────────────────────────────────────────────────────
|
|
*
|
|
* import { storage } from '@/services/storage'
|
|
* await storage.init()
|
|
* storage.get('key') → string | null (sync)
|
|
* await storage.set('k', v) → void (async)
|
|
* await storage.remove('k') → void (async)
|
|
* storage.getJSON<T>('k') → T | null (sync)
|
|
* await storage.setJSON() → void (async)
|
|
*/
|
|
|
|
import db from '@/services/db'
|
|
|
|
class AppStorage {
|
|
/** L1: cache en memoria */
|
|
private cache = new Map<string, { value: string; ttl: number | null }>()
|
|
private loaded = false
|
|
private initPromise: Promise<void> | null = null
|
|
|
|
// ─── Init ──────────────────────────────────────────────────
|
|
|
|
async init() {
|
|
if (this.initPromise) return this.initPromise
|
|
this.initPromise = this._load()
|
|
return this.initPromise
|
|
}
|
|
|
|
private async _load() {
|
|
const all = await db.settings.toArray()
|
|
for (const entry of all) {
|
|
this.cache.set(entry.key, { value: entry.value, ttl: null })
|
|
}
|
|
this.loaded = true
|
|
console.log(`[Storage] Init OK — ${all.length} entries en L1`)
|
|
}
|
|
|
|
isReady() { return this.loaded }
|
|
|
|
// ─── Lectura: Cache-Aside (L1 → L2 → L3) ────────────────
|
|
|
|
get(key: string): string | null {
|
|
// 1. L1 hit
|
|
const entry = this.cache.get(key)
|
|
if (entry) {
|
|
if (entry.ttl !== null && entry.ttl < Date.now()) {
|
|
this.cache.delete(key)
|
|
} else {
|
|
return entry.value
|
|
}
|
|
}
|
|
|
|
// 2. L2 (Dexie)
|
|
// Nota: Dexie.get() es async. Para mantener get() sync,
|
|
// delegamos la carga asíncrona a init(). Si no está en L1
|
|
// después de init, cae a L3.
|
|
// En la práctica, init() carga TODO al arranque, así que
|
|
// L1 siempre está poblado para keys existentes.
|
|
|
|
// 3. L3 (localStorage) — fallback + migración
|
|
const fallback = localStorage.getItem(key)
|
|
if (fallback !== null) {
|
|
this.cache.set(key, { value: fallback, ttl: null })
|
|
// Write-Through hacia L2 (fire-and-forget)
|
|
db.settings.put({ key, value: fallback }).catch(() => {})
|
|
return fallback
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
getJSON<T>(key: string): T | null {
|
|
const raw = this.get(key)
|
|
if (!raw) return null
|
|
try { return JSON.parse(raw) as T } catch { return null }
|
|
}
|
|
|
|
// ─── Escritura: Write-Through (L1 + L2 + L3) ─────────────
|
|
|
|
async set(key: string, value: string) {
|
|
// 1. L1 (instantáneo)
|
|
this.cache.set(key, { value, ttl: null })
|
|
|
|
// 2. L2 + L3 en paralelo
|
|
await Promise.all([
|
|
db.settings.put({ key, value }).catch(e => {
|
|
console.error(`[Storage] L2 error writing "${key}":`, e)
|
|
}),
|
|
Promise.resolve().then(() => {
|
|
localStorage.setItem(key, value)
|
|
}),
|
|
])
|
|
}
|
|
|
|
async setJSON(key: string, value: unknown) {
|
|
await this.set(key, JSON.stringify(value))
|
|
}
|
|
|
|
async remove(key: string) {
|
|
this.cache.delete(key)
|
|
|
|
await Promise.all([
|
|
db.settings.delete(key).catch(e => {
|
|
console.error(`[Storage] L2 error deleting "${key}":`, e)
|
|
}),
|
|
Promise.resolve().then(() => {
|
|
localStorage.removeItem(key)
|
|
}),
|
|
])
|
|
}
|
|
|
|
// ─── Utilidades ───────────────────────────────────────────
|
|
|
|
/** Expone el tamaño del cache L1 (debug) */
|
|
get cacheSize() { return this.cache.size }
|
|
}
|
|
|
|
export const storage = new AppStorage()
|