K-10 pipeline transcripciones + settings IA + cache-aside + session doc
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
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import { PROVIDER_CONFIG, getProviderApiKey, type AIProvider } from '@/stores/settings'
|
||||
import { storage } from '@/services/storage'
|
||||
|
||||
export interface AIExtractedHU {
|
||||
title: string
|
||||
description: string
|
||||
acceptance_criteria: string[]
|
||||
priority?: string
|
||||
story_points?: number
|
||||
type?: 'feature' | 'bug' | 'task' | 'improvement'
|
||||
}
|
||||
|
||||
export interface AIAnalysisResult {
|
||||
hus: AIExtractedHU[]
|
||||
summary: string
|
||||
}
|
||||
|
||||
interface ActiveConfig {
|
||||
baseUrl: string
|
||||
apiKey: string
|
||||
model: string
|
||||
provider: AIProvider
|
||||
}
|
||||
|
||||
function getActiveConfig(): ActiveConfig {
|
||||
const raw = storage.getJSON<{ provider: AIProvider; modelId: string }>('alpha_settings')
|
||||
if (raw) {
|
||||
const provider = raw.provider || 'openrouter'
|
||||
const model = raw.modelId || 'deepseek/deepseek-chat-v3-0324:free'
|
||||
const baseUrl = PROVIDER_CONFIG[provider]?.baseUrl || ''
|
||||
const apiKey = getProviderApiKey(provider)
|
||||
return { baseUrl, apiKey, model, provider }
|
||||
}
|
||||
return {
|
||||
baseUrl: PROVIDER_CONFIG.openrouter.baseUrl,
|
||||
apiKey: getProviderApiKey('openrouter'),
|
||||
model: 'deepseek/deepseek-chat-v3-0324:free',
|
||||
provider: 'openrouter',
|
||||
}
|
||||
}
|
||||
|
||||
function buildHeaders(config: ActiveConfig): Record<string, string> {
|
||||
const h: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
if (config.provider === 'openrouter') {
|
||||
h['Authorization'] = `Bearer ${config.apiKey}`
|
||||
h['HTTP-Referer'] = window.location.origin
|
||||
h['X-Title'] = 'KAPPA Hub Alpha'
|
||||
} else {
|
||||
h['Authorization'] = `Bearer ${config.apiKey}`
|
||||
h['Authorization'] = `Bearer ${config.apiKey}`
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'system' | 'user' | 'assistant'
|
||||
content: string
|
||||
}
|
||||
|
||||
export async function callAI(
|
||||
messages: ChatMessage[],
|
||||
temperature = 0.3,
|
||||
maxTokens = 4096,
|
||||
signal?: AbortSignal
|
||||
): Promise<string> {
|
||||
const config = getActiveConfig()
|
||||
|
||||
if (!config.apiKey) {
|
||||
const label = PROVIDER_CONFIG[config.provider]?.label || config.provider
|
||||
throw new Error(`No hay API key configurada para ${label}`)
|
||||
}
|
||||
if (!config.baseUrl) {
|
||||
throw new Error(`El proveedor ${config.provider} no tiene API configurada`)
|
||||
}
|
||||
|
||||
console.log(`[Alpha] AI call — provider: ${config.provider}, model: ${config.model}`)
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
model: config.model,
|
||||
messages,
|
||||
temperature,
|
||||
max_tokens: maxTokens,
|
||||
}
|
||||
|
||||
const res = await fetch(config.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: buildHeaders(config),
|
||||
body: JSON.stringify(body),
|
||||
signal,
|
||||
})
|
||||
|
||||
const rawText = await res.text()
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`API error (${config.provider}): ${res.status} — ${rawText.slice(0, 300)}`)
|
||||
}
|
||||
|
||||
const data = JSON.parse(rawText)
|
||||
const content = data.choices?.[0]?.message?.content || null
|
||||
|
||||
if (!content) throw new Error('Respuesta vacía del proveedor de IA')
|
||||
return content
|
||||
}
|
||||
|
||||
export async function chatWithAI(
|
||||
messages: { role: 'user' | 'assistant'; content: string }[],
|
||||
systemPrompt?: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<string> {
|
||||
const msgs: ChatMessage[] = []
|
||||
if (systemPrompt) {
|
||||
msgs.push({ role: 'system', content: systemPrompt })
|
||||
}
|
||||
msgs.push(...messages)
|
||||
return callAI(msgs, 0.7, 2048, signal)
|
||||
}
|
||||
|
||||
const SYSTEM_PROMPT = `Eres un analista funcional experto en metodologías ágiles. Tu tarea es analizar transcripciones de reuniones y extraer Historias de Usuario (HUs) en formato estructurado.
|
||||
|
||||
Reglas:
|
||||
1. Identifica cada requisito, funcionalidad, bug o mejora mencionada en la transcripción
|
||||
2. Convierte cada uno en una HU con: título claro, descripción detallada, criterios de aceptación
|
||||
3. Los criterios de aceptación deben ser verificables (condiciones específicas)
|
||||
4. Usa el formato "Como [rol] quiero [funcionalidad] para [beneficio]" cuando sea posible
|
||||
5. Asigna prioridad (Alta/Media/Baja) basada en urgencia implícita
|
||||
6. No inventes información que no esté en la transcripción
|
||||
7. Si el texto no contiene información relevante para HUs, devuelve un arreglo vacío
|
||||
|
||||
Responde SOLO con JSON válido en este formato:
|
||||
{
|
||||
"hus": [
|
||||
{
|
||||
"title": "Título de la HU",
|
||||
"description": "Descripción detallada",
|
||||
"acceptance_criteria": ["Criterio 1", "Criterio 2"],
|
||||
"priority": "Alta|Media|Baja",
|
||||
"story_points": 3,
|
||||
"type": "feature|bug|task|improvement"
|
||||
}
|
||||
],
|
||||
"summary": "Resumen breve del análisis (2-3 líneas)"
|
||||
}`
|
||||
|
||||
export async function analyzeTranscription(
|
||||
text: string,
|
||||
projectName?: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<AIAnalysisResult> {
|
||||
const userContent = projectName
|
||||
? `Proyecto: ${projectName}\n\nTranscripción:\n${text}`
|
||||
: `Transcripción:\n${text}`
|
||||
|
||||
console.log(`[Alpha] AI analyze — text: ${text.length} chars`)
|
||||
|
||||
const content = await callAI(
|
||||
[
|
||||
{ role: 'system', content: SYSTEM_PROMPT },
|
||||
{ role: 'user', content: userContent },
|
||||
],
|
||||
0.3,
|
||||
4096,
|
||||
signal,
|
||||
)
|
||||
|
||||
try {
|
||||
const jsonStr = content.replace(/```json\s*/gi, '').replace(/```\s*$/g, '').trim()
|
||||
const result: AIAnalysisResult = JSON.parse(jsonStr)
|
||||
console.log(`[Alpha] AI analysis complete — ${result.hus.length} HUs`)
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('[Alpha] Failed to parse AI response:', content)
|
||||
throw new Error('No se pudo parsear la respuesta de la IA')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user