Sistema de prompts dinámicos: prompts editables desde Settings + tabla Dexie + fallback a defaults

- prompts-db.ts: CRUD de prompts con 5 tipos (transcription, project_gap, session, qa, chat)
- db.ts: schema v9 con tabla prompts (&key)
- App.vue: initPrompts() al arrancar la app
- Servicios AI modificados: usan getPrompt(key) con fallback a default hardcodeado
- AiProjectChat.vue: prompt con {{variables}} reemplazadas por datos reales
- SettingsView: editor de prompts con textarea + guardar/restaurar
This commit is contained in:
2026-05-29 14:41:40 -05:00
parent b45caee583
commit 9909f16229
9 changed files with 328 additions and 141 deletions
+2
View File
@@ -3,6 +3,7 @@ import { ref, onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useProjectsStore } from '@/stores/projects' import { useProjectsStore } from '@/stores/projects'
import { storage } from '@/services/storage' import { storage } from '@/services/storage'
import { initPrompts } from '@/services/prompts-db'
import LoginView from '@/views/LoginView.vue' import LoginView from '@/views/LoginView.vue'
import NewDashboardView from '@/views/NewDashboardView.vue' import NewDashboardView from '@/views/NewDashboardView.vue'
@@ -11,6 +12,7 @@ const projectsStore = useProjectsStore()
onMounted(async () => { onMounted(async () => {
await storage.init() await storage.init()
await initPrompts()
if (auth.isAuthenticated) { if (auth.isAuthenticated) {
projectsStore.fetchProjects() projectsStore.fetchProjects()
} }
+8 -17
View File
@@ -2,6 +2,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { chatWithAI } from '@/services/ai' import { chatWithAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db'
import { storage } from '@/services/storage' import { storage } from '@/services/storage'
import { useSettingsStore, AVAILABLE_MODELS, PROVIDER_CONFIG, hasProviderApiKey, type AIProvider } from '@/stores/settings' import { useSettingsStore, AVAILABLE_MODELS, PROVIDER_CONFIG, hasProviderApiKey, type AIProvider } from '@/stores/settings'
import { useWorkItemsStore } from '@/stores/workitems' import { useWorkItemsStore } from '@/stores/workitems'
@@ -122,24 +123,14 @@ async function sendPrompt() {
try { try {
const context = await buildContext() const context = await buildContext()
const systemPrompt = `Eres un asistente de gestión de proyectos ágiles para el equipo de desarrollo. const rawPrompt = await getPrompt('chat')
const systemPrompt = rawPrompt
.replace(/\{\{projectName\}\}/g, props.projectName)
.replace(/\{\{projectDescription\}\}/g, props.projectDescription || '')
.replace(/\{\{epicCount\}\}/g, String(props.epicCount))
.replace(/\{\{huCount\}\}/g, String(props.huCount))
Proyecto: ${props.projectName}. ${props.projectDescription || ''} Épicas: ${props.epicCount} HUs: ${props.huCount}. const msgs = [{ role: 'user' as const, content: `Datos del proyecto:\n${context || '{}'}\n\n${text}` }]
Tienes acceso a estos datos del proyecto (JSON):
${context || '{}'}
El campo "team" contiene los miembros del equipo asignados a este proyecto con su rol. Úsalo para responder quién trabaja en qué, sugerir asignaciones de HUs, y alertar sobre capacidad.
Cada HU puede tener "sp" (story points) y "sprint". Usa esta información para:
- Recomendar asignaciones equilibradas según capacidad del equipo
- Detectar sobrecarga de trabajo en un sprint
- Sugerir recursos adicionales si una HU requiere expertise que no está en el equipo (célula transversal)
- Alertar cuando el cronograma está en riesgo por falta de capacidad
Respondé en el mismo idioma del usuario. Sé conciso y práctico. Si algo no está en los datos, decilo.`
const msgs = [{ role: 'user' as const, content: text }]
const result = await chatWithAI(msgs, systemPrompt) const result = await chatWithAI(msgs, systemPrompt)
response.value = result response.value = result
} catch (e: any) { } catch (e: any) {
+3 -27
View File
@@ -1,5 +1,6 @@
import { PROVIDER_CONFIG, getProviderApiKey, type AIProvider } from '@/stores/settings' import { PROVIDER_CONFIG, getProviderApiKey, type AIProvider } from '@/stores/settings'
import { storage } from '@/services/storage' import { storage } from '@/services/storage'
import { getPrompt } from '@/services/prompts-db'
export interface AIExtractedHU { export interface AIExtractedHU {
title: string title: string
@@ -134,32 +135,6 @@ export async function chatWithAI(
return callAI(msgs, 0.7, 2048, signal) 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( export async function analyzeTranscription(
text: string, text: string,
projectName?: string, projectName?: string,
@@ -171,9 +146,10 @@ export async function analyzeTranscription(
console.log(`[Alpha] AI analyze — text: ${text.length} chars`) console.log(`[Alpha] AI analyze — text: ${text.length} chars`)
const systemPrompt = await getPrompt('analysis_transcription')
const content = await callAI( const content = await callAI(
[ [
{ role: 'system', content: SYSTEM_PROMPT }, { role: 'system', content: systemPrompt },
{ role: 'user', content: userContent }, { role: 'user', content: userContent },
], ],
0.3, 0.3,
+10 -1
View File
@@ -128,6 +128,13 @@ export interface DexieUserStory {
created_at: string | null created_at: string | null
} }
export interface PromptRecord {
key: string
content: string
label: string
updatedAt: string
}
const db = new Dexie('alpha-core') as Dexie & { const db = new Dexie('alpha-core') as Dexie & {
settings: Dexie.Table<SettingEntry, string> settings: Dexie.Table<SettingEntry, string>
project_docs: Dexie.Table<ProjectDocRecord, number> project_docs: Dexie.Table<ProjectDocRecord, number>
@@ -141,9 +148,10 @@ const db = new Dexie('alpha-core') as Dexie & {
cells: Dexie.Table<CellRecord, string> cells: Dexie.Table<CellRecord, string>
cell_members: Dexie.Table<CellMemberRecord, string> cell_members: Dexie.Table<CellMemberRecord, string>
user_stories: Dexie.Table<DexieUserStory, number> user_stories: Dexie.Table<DexieUserStory, number>
prompts: Dexie.Table<PromptRecord, string>
} }
db.version(8).stores({ db.version(9).stores({
settings: '&key', settings: '&key',
project_docs: '&projectId, projectName, updatedAt', project_docs: '&projectId, projectName, updatedAt',
sessions: '++id, projectId, date', sessions: '++id, projectId, date',
@@ -156,6 +164,7 @@ db.version(8).stores({
cells: '&id', cells: '&id',
cell_members: '[cellId+userId], cellId, userId', cell_members: '[cellId+userId], cellId, userId',
user_stories: '&id, initiative_id', user_stories: '&id, initiative_id',
prompts: '&key',
}) })
export default db export default db
+3 -35
View File
@@ -1,4 +1,5 @@
import { callAI } from '@/services/ai' import { callAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db'
import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db' import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db'
import { saveDraft, createDraftId } from '@/services/hu-drafts-db' import { saveDraft, createDraftId } from '@/services/hu-drafts-db'
import { generateAndSavePlan } from '@/services/qa-analyzer' import { generateAndSavePlan } from '@/services/qa-analyzer'
@@ -25,40 +26,6 @@ interface AnalysisResult {
summary: string summary: string
} }
const ANALYSIS_SYSTEM_PROMPT = `Eres un analista funcional experto. Tu tarea es analizar TODO el contexto de un proyecto y generar las Épicas e Historias de Usuario (HUs) que faltan.
Reglas:
1. Analizá TODA la información disponible: sesiones, resúmenes, estado del proyecto, HUs existentes
2. Identificá requisitos, funcionalidades, mejoras o bugs que NO estén cubiertos
3. Agrupá HUs relacionadas en Épicas. Cada épica agrupa funcionalidades de un mismo tema
4. Cada HU debe tener: título claro, descripción, criterios de aceptación verificables
5. No generes duplicados. Compará con la lista existente
6. Priorizá según urgencia implícita (Alta/Media/Baja)
7. Si todo ya está cubierto, devolvé arreglos vacíos
8. Respondé SOLO con JSON válido
Formato de respuesta:
{
"epics": [
{
"name": "Nombre de la Épica",
"description": "Descripción de la épica",
"linkedHuTitles": ["Título HU 1", "Título HU 2"],
"estimatedStart": "YYYY-MM-DD",
"estimatedEnd": "YYYY-MM-DD"
}
],
"hus": [
{
"title": "Título de la HU",
"description": "Descripción detallada",
"acceptance_criteria": ["Criterio 1", "Criterio 2"],
"priority": "Alta|Media|Baja"
}
],
"summary": "Resumen del análisis"
}`
export async function analyzeProject( export async function analyzeProject(
projectId: number, projectId: number,
projectName: string, projectName: string,
@@ -96,8 +63,9 @@ export async function analyzeProject(
console.log(`[Alpha] Project analysis — ${projectId}, ${existingHUs.length} HUs existentes, ${sessions.length} sesiones`) console.log(`[Alpha] Project analysis — ${projectId}, ${existingHUs.length} HUs existentes, ${sessions.length} sesiones`)
const systemPrompt = await getPrompt('project_gap')
const content = await callAI( const content = await callAI(
[{ role: 'system', content: ANALYSIS_SYSTEM_PROMPT }, { role: 'user', content: userContent }], [{ role: 'system', content: systemPrompt }, { role: 'user', content: userContent }],
0.3, 8192, signal, 0.3, 8192, signal,
) )
+213
View File
@@ -0,0 +1,213 @@
import db from '@/services/db'
export type PromptKey = 'analysis_transcription' | 'project_gap' | 'session' | 'qa' | 'chat'
export interface PromptRecord {
key: PromptKey
content: string
label: string
updatedAt: string
}
const DEFAULT_PROMPTS: Record<PromptKey, { label: string; content: string }> = {
analysis_transcription: {
label: 'Análisis de transcripciones',
content: `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)"
}`,
},
project_gap: {
label: 'Análisis de brechas del proyecto',
content: `Eres un analista funcional experto. Tu tarea es analizar TODO el contexto de un proyecto y generar las Épicas e Historias de Usuario (HUs) que faltan.
Reglas:
1. Analizá TODA la información disponible: sesiones, resúmenes, estado del proyecto, HUs existentes
2. Identificá requisitos, funcionalidades, mejoras o bugs que NO estén cubiertos
3. Agrupá HUs relacionadas en Épicas. Cada épica agrupa funcionalidades de un mismo tema
4. Cada HU debe tener: título claro, descripción, criterios de aceptación verificables
5. No generes duplicados. Compará con la lista existente
6. Priorizá según urgencia implícita (Alta/Media/Baja)
7. Si todo ya está cubierto, devolvé arreglos vacíos
8. Respondé SOLO con JSON válido
Formato de respuesta:
{
"epics": [
{
"name": "Nombre de la Épica",
"description": "Descripción de la épica",
"linkedHuTitles": ["Título HU 1", "Título HU 2"],
"estimatedStart": "YYYY-MM-DD",
"estimatedEnd": "YYYY-MM-DD"
}
],
"hus": [
{
"title": "Título de la HU",
"description": "Descripción detallada",
"acceptance_criteria": ["Criterio 1", "Criterio 2"],
"priority": "Alta|Media|Baja"
}
],
"summary": "Resumen del análisis"
}`,
},
session: {
label: 'Análisis de sesiones',
content: `Eres un asistente de gestión de proyectos. Analizás transcripciones de reuniones y extraés información estructurada.
Reglas:
1. Identificá la fecha de la sesión (si no está explícita, usá la fecha actual)
2. Identificá el título de la sesión basado en el contenido
3. Extraé un resumen ejecutivo de 2-3 oraciones
4. Listá objetivos mencionados, marcando cuáles son NUEVOS vs existentes
5. Extraé tareas pendientes con su origen y prioridad (Alta/Media/Baja)
6. Identificá compromisos con responsable, fecha límite y estado
7. Listá decisiones tomadas durante la sesión
8. Detectá tareas completadas (si hay evidencia)
9. Incluí puntos clave, bloqueos o descubrimientos
10. No inventes información que no esté en la transcripción
11. Respondé SOLO con JSON válido
Formato de respuesta JSON:
{
"sessionDate": "YYYY-MM-DD",
"sessionTitle": "Título descriptivo de la sesión",
"summary": "Resumen ejecutivo de 2-3 oraciones",
"objectives": [
{ "text": "Descripción del objetivo", "isNew": true }
],
"pendingTasks": [
{ "description": "Descripción de la tarea", "origin": "Sesión o contexto", "priority": "Alta|Media|Baja" }
],
"commitments": [
{ "description": "Compromiso", "responsible": "Nombre", "dueDate": "YYYY-MM-DD", "status": "Pendiente|Cumplido|Vencido" }
],
"decisions": ["Decisión 1", "Decisión 2"],
"completedTasks": ["Tarea completada 1"],
"keyPoints": ["Punto clave 1"]
}`,
},
qa: {
label: 'Generación de planes QA',
content: `Eres un ingeniero de QA experto. Generá un plan de pruebas detallado para una Historia de Usuario.
Formato de respuesta JSON:
{
"acceptanceCriteria": ["Criterio 1", "Criterio 2"],
"testCases": [
{
"type": "Tipo de prueba",
"description": "Descripción de lo que verifica",
"automatizable": "SÍ|PARCIAL|MANUAL",
"tool": "Playwright|API Testing|Manual"
}
],
"manualSteps": ["Paso manual 1"],
"criticalTests": ["Prueba crítica manual"]
}
Reglas:
- SÍ = completamente automatizable
- PARCIAL = requiere validación manual complementaria
- MANUAL = requiere intervención humana
- Incluí al menos 3-5 casos de prueba
- Marcá como crítica pruebas con datos reales, ERPs externos, o cálculos financieros`,
},
chat: {
label: 'Chat del proyecto',
content: `Eres un asistente de gestión de proyectos ágiles para el equipo de desarrollo.
Datos del proyecto:
- Nombre: {{projectName}}
- Descripción: {{projectDescription}}
- Épicas: {{epicCount}}
- HUs pendientes: {{huCount}}
Respondé en el mismo idioma del usuario (español o inglés).
Usá la data del proyecto para dar respuestas contextualizadas.
Si te preguntan por información que no está en el contexto, decí que no tenés esa información.`,
},
}
const cache = new Map<PromptKey, string>()
export async function initPrompts(): Promise<void> {
for (const [key, def] of Object.entries(DEFAULT_PROMPTS)) {
const existing = await db.prompts.get(key as PromptKey)
if (!existing) {
await db.prompts.put({
key: key as PromptKey,
content: def.content,
label: def.label,
updatedAt: new Date().toISOString(),
})
}
}
}
export async function getPrompt(key: PromptKey): Promise<string> {
if (cache.has(key)) return cache.get(key)!
const record = await db.prompts.get(key)
if (record) {
cache.set(key, record.content)
return record.content
}
const def = DEFAULT_PROMPTS[key]
if (def) {
cache.set(key, def.content)
return def.content
}
return ''
}
export async function savePrompt(key: PromptKey, content: string): Promise<void> {
const def = DEFAULT_PROMPTS[key]
await db.prompts.put({
key,
content,
label: def?.label ?? key,
updatedAt: new Date().toISOString(),
})
cache.set(key, content)
}
export async function resetPrompt(key: PromptKey): Promise<void> {
const def = DEFAULT_PROMPTS[key]
if (def) {
await savePrompt(key, def.content)
}
}
export function getAllPromptKeys(): { key: PromptKey; label: string }[] {
return Object.entries(DEFAULT_PROMPTS).map(([key, def]) => ({
key: key as PromptKey,
label: def.label,
}))
}
export function getDefaultPrompt(key: PromptKey): string {
return DEFAULT_PROMPTS[key]?.content ?? ''
}
+3 -25
View File
@@ -1,4 +1,5 @@
import { callAI } from '@/services/ai' import { callAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db'
import db from '@/services/db' import db from '@/services/db'
export interface QATestCase { export interface QATestCase {
@@ -18,34 +19,11 @@ export interface HUQAPlan {
criticalTests: string[] criticalTests: string[]
} }
const QA_PROMPT = `Eres un ingeniero de QA experto. Generá un plan de pruebas detallado para una Historia de Usuario.
Formato de respuesta JSON:
{
"acceptanceCriteria": ["Criterio 1", "Criterio 2"],
"testCases": [
{
"type": "Tipo de prueba",
"description": "Descripción de lo que verifica",
"automatizable": "SÍ|PARCIAL|MANUAL",
"tool": "Playwright|API Testing|Manual"
}
],
"manualSteps": ["Paso manual 1"],
"criticalTests": ["Prueba crítica manual"]
}
Reglas:
- SÍ = completamente automatizable
- PARCIAL = requiere validación manual complementaria
- MANUAL = requiere intervención humana
- Incluí al menos 3-5 casos de prueba
- Marcá como crítica pruebas con datos reales, ERPs externos, o cálculos financieros`
export async function generateQAPlan(huTitle: string, huDescription: string, acceptanceCriteria: string): Promise<HUQAPlan> { export async function generateQAPlan(huTitle: string, huDescription: string, acceptanceCriteria: string): Promise<HUQAPlan> {
const userContent = `HU: ${huTitle}\nDescripción: ${huDescription}\nCriterios: ${acceptanceCriteria}` const userContent = `HU: ${huTitle}\nDescripción: ${huDescription}\nCriterios: ${acceptanceCriteria}`
const systemPrompt = await getPrompt('qa')
const content = await callAI( const content = await callAI(
[{ role: 'system', content: QA_PROMPT }, { role: 'user', content: userContent }], [{ role: 'system', content: systemPrompt }, { role: 'user', content: userContent }],
0.3, 4096, 0.3, 4096,
) )
try { try {
+3 -35
View File
@@ -1,4 +1,5 @@
import { callAI } from '@/services/ai' import { callAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db'
export interface SessionExtraction { export interface SessionExtraction {
sessionDate: string // YYYY-MM-DD sessionDate: string // YYYY-MM-DD
@@ -12,40 +13,6 @@ export interface SessionExtraction {
keyPoints: string[] keyPoints: string[]
} }
const SESSION_SYSTEM_PROMPT = `Eres un asistente de gestión de proyectos. Analizás transcripciones de reuniones y extraés información estructurada.
Reglas:
1. Identificá la fecha de la sesión (si no está explícita, usá la fecha actual)
2. Identificá el título de la sesión basado en el contenido
3. Extraé un resumen ejecutivo de 2-3 oraciones
4. Listá objetivos mencionados, marcando cuáles son NUEVOS vs existentes
5. Extraé tareas pendientes con su origen y prioridad (Alta/Media/Baja)
6. Identificá compromisos con responsable, fecha límite y estado
7. Listá decisiones tomadas durante la sesión
8. Detectá tareas completadas (si hay evidencia)
9. Incluí puntos clave, bloqueos o descubrimientos
10. No inventes información que no esté en la transcripción
11. Respondé SOLO con JSON válido
Formato de respuesta JSON:
{
"sessionDate": "YYYY-MM-DD",
"sessionTitle": "Título descriptivo de la sesión",
"summary": "Resumen ejecutivo de 2-3 oraciones",
"objectives": [
{ "text": "Descripción del objetivo", "isNew": true }
],
"pendingTasks": [
{ "description": "Descripción de la tarea", "origin": "Sesión o contexto", "priority": "Alta|Media|Baja" }
],
"commitments": [
{ "description": "Compromiso", "responsible": "Nombre", "dueDate": "YYYY-MM-DD", "status": "Pendiente|Cumplido|Vencido" }
],
"decisions": ["Decisión 1", "Decisión 2"],
"completedTasks": ["Tarea completada 1"],
"keyPoints": ["Punto clave 1"]
}`
export async function analyzeSession( export async function analyzeSession(
transcription: string, transcription: string,
projectName: string, projectName: string,
@@ -55,9 +22,10 @@ export async function analyzeSession(
console.log(`[Alpha] Session analyze — project: ${projectName}, text: ${transcription.length} chars`) console.log(`[Alpha] Session analyze — project: ${projectName}, text: ${transcription.length} chars`)
const systemPrompt = await getPrompt('session')
const content = await callAI( const content = await callAI(
[ [
{ role: 'system', content: SESSION_SYSTEM_PROMPT }, { role: 'system', content: systemPrompt },
{ role: 'user', content: userContent }, { role: 'user', content: userContent },
], ],
0.3, 0.3,
+83 -1
View File
@@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useSettingsStore, PROVIDER_CONFIG, type AIProvider } from '@/stores/settings' import { useSettingsStore, PROVIDER_CONFIG, type AIProvider } from '@/stores/settings'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { getAllPromptKeys, getPrompt, savePrompt, resetPrompt, type PromptKey } from '@/services/prompts-db'
import { import {
Card, Card,
CardContent, CardContent,
@@ -31,6 +32,42 @@ const { t } = useI18n()
const settings = useSettingsStore() const settings = useSettingsStore()
const auth = useAuthStore() const auth = useAuthStore()
// ─── Prompt editor ────────────────────────────────────
const promptKeys = getAllPromptKeys()
const promptContents = ref<Record<string, string>>({})
const promptSaving = ref<Record<string, boolean>>({})
const promptDirty = ref<Record<string, boolean>>({})
async function loadPrompts() {
for (const { key } of promptKeys) {
promptContents.value[key] = await getPrompt(key)
}
}
async function savePromptHandler(key: PromptKey) {
promptSaving.value[key] = true
try {
await savePrompt(key, promptContents.value[key])
promptDirty.value[key] = false
} catch (e: any) {
console.error('[Alpha] Error saving prompt:', e)
} finally {
promptSaving.value[key] = false
}
}
async function resetPromptHandler(key: PromptKey) {
await resetPrompt(key)
promptContents.value[key] = await getPrompt(key)
promptDirty.value[key] = false
}
function markDirty(key: string) {
promptDirty.value[key] = true
}
onMounted(loadPrompts)
const newKey = ref('') const newKey = ref('')
const keySaved = ref(false) const keySaved = ref(false)
@@ -246,6 +283,51 @@ const tierColors: Record<string, string> = {
</CardContent> </CardContent>
</Card> </Card>
<!-- Prompts -->
<Card id="settings-prompts" class="border-dashed">
<CardHeader>
<CardTitle class="text-sm font-medium flex items-center gap-2">
<Sparkles class="size-4" />
Prompts de IA
</CardTitle>
<CardDescription class="text-xs">
Editá los prompts que usa la IA para cada función. Los cambios se aplican inmediatamente.
</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<div v-for="{ key, label } in promptKeys" :key="key" class="space-y-1.5">
<div class="flex items-center justify-between">
<Label class="text-xs font-medium">{{ label }}</Label>
<div class="flex gap-1">
<Button
v-if="promptDirty[key]"
size="sm"
variant="default"
class="text-xs h-6 px-2"
:disabled="promptSaving[key]"
@click="savePromptHandler(key as PromptKey)"
>
{{ promptSaving[key] ? 'Guardando...' : 'Guardar' }}
</Button>
<Button
size="sm"
variant="ghost"
class="text-xs h-6 px-2 text-muted-foreground"
@click="resetPromptHandler(key as PromptKey)"
>
Restaurar
</Button>
</div>
</div>
<textarea
:value="promptContents[key]"
@input="(e: any) => { promptContents[key] = e.target.value; markDirty(key) }"
class="w-full h-32 rounded-md border border-input bg-background p-2 text-xs font-mono outline-none focus:border-ring focus:ring-1 focus:ring-ring resize-y"
/>
</div>
</CardContent>
</Card>
<!-- Account --> <!-- Account -->
<Card id="settings-account"> <Card id="settings-account">
<CardHeader> <CardHeader>