Feat: System Prompts

- Se añade un sistema de prompts de gerarquía máxima para molder el asistente.
- Se añade control del sistema de prompts mediante el panel de administración.
This commit is contained in:
2025-12-05 13:02:23 -03:00
parent 67e179441d
commit a87550b890
9 changed files with 883 additions and 8 deletions

View File

@@ -7,6 +7,8 @@ import ContextManager from './ContextManager';
import LogsViewer from './LogsViewer';
import SourceManager from './SourceManager';
import SystemPromptManager from './SystemPromptManager';
interface AdminPanelProps {
onLogout: () => void;
}
@@ -33,12 +35,14 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ onLogout }) => {
<Tab label="Gestor de Contexto" />
<Tab label="Historial de Conversaciones" />
<Tab label="Gestor de Fuentes" />
<Tab label="Gestor de Prompts" />
</Tabs>
</AppBar>
{currentTab === 0 && <ContextManager onAuthError={onLogout} />}
{currentTab === 1 && <LogsViewer onAuthError={onLogout} />}
{currentTab === 2 && <SourceManager onAuthError={onLogout} />}
{currentTab === 3 && <SystemPromptManager onAuthError={onLogout} />}
</Box>
);
};

View File

@@ -0,0 +1,221 @@
import React, { useState, useEffect } from 'react';
import {
Box, Typography, Button, TextField, Paper, List, ListItem,
ListItemText, ListItemSecondaryAction, IconButton, Switch,
Dialog, DialogTitle, DialogContent, DialogActions,
Snackbar, Alert
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import apiClient from '../api/apiClient';
interface SystemPrompt {
id: number;
name: string;
content: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
interface SystemPromptManagerProps {
onAuthError: () => void;
}
const SystemPromptManager: React.FC<SystemPromptManagerProps> = ({ onAuthError }) => {
const [prompts, setPrompts] = useState<SystemPrompt[]>([]);
const [openDialog, setOpenDialog] = useState(false);
const [currentPrompt, setCurrentPrompt] = useState<Partial<SystemPrompt>>({});
const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' }>({
open: false,
message: '',
severity: 'success'
});
useEffect(() => {
fetchPrompts();
}, []);
const fetchPrompts = async () => {
try {
const response = await apiClient.get('/api/SystemPrompts');
setPrompts(response.data);
} catch (error: any) {
console.error('Error fetching prompts:', error);
if (error.response?.status === 401) onAuthError();
}
};
const handleSave = async () => {
try {
if (currentPrompt.id) {
await apiClient.put(`/api/SystemPrompts/${currentPrompt.id}`, currentPrompt);
setSnackbar({ open: true, message: 'Prompt actualizado correctamente', severity: 'success' });
} else {
await apiClient.post('/api/SystemPrompts', currentPrompt);
setSnackbar({ open: true, message: 'Prompt creado correctamente', severity: 'success' });
}
setOpenDialog(false);
fetchPrompts();
} catch (error: any) {
console.error('Error saving prompt:', error);
setSnackbar({ open: true, message: 'Error al guardar el prompt', severity: 'error' });
if (error.response?.status === 401) onAuthError();
}
};
const handleDelete = async (id: number) => {
if (!window.confirm('¿Seguro que deseas eliminar este prompt?')) return;
try {
await apiClient.delete(`/api/SystemPrompts/${id}`);
setSnackbar({ open: true, message: 'Prompt eliminado', severity: 'success' });
fetchPrompts();
} catch (error: any) {
console.error('Error deleting prompt:', error);
if (error.response?.status === 401) onAuthError();
}
};
const handleToggleActive = async (id: number) => {
try {
await apiClient.post(`/api/SystemPrompts/ToggleActive/${id}`);
fetchPrompts();
} catch (error: any) {
console.error('Error toggling prompt:', error);
if (error.response?.status === 401) onAuthError();
}
};
const openEdit = (prompt: SystemPrompt) => {
setCurrentPrompt(prompt);
setOpenDialog(true);
};
const openCreate = () => {
setCurrentPrompt({ name: '', content: '', isActive: false });
setOpenDialog(true);
};
return (
<Box sx={{ p: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h5">Gestión de Prompts del Sistema</Typography>
<Button variant="contained" color="primary" onClick={openCreate}>
Nuevo Prompt
</Button>
</Box>
<Paper>
<List>
{prompts.map((prompt) => (
<ListItem key={prompt.id} divider>
<ListItemText
primary={
<Box display="flex" alignItems="center">
<Typography variant="subtitle1" fontWeight="bold">
{prompt.name}
</Typography>
{prompt.isActive && (
<Box
component="span"
sx={{
ml: 2,
px: 1,
py: 0.5,
bgcolor: 'success.light',
color: 'white',
borderRadius: 1,
fontSize: '0.75rem'
}}
>
ACTIVO
</Box>
)}
</Box>
}
secondary={
<Typography
variant="body2"
color="textSecondary"
sx={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden'
}}
>
{prompt.content}
</Typography>
}
/>
<ListItemSecondaryAction>
<Switch
edge="end"
checked={prompt.isActive}
onChange={() => handleToggleActive(prompt.id)}
color="primary"
/>
<IconButton edge="end" aria-label="edit" onClick={() => openEdit(prompt)} sx={{ ml: 1 }}>
<EditIcon />
</IconButton>
<IconButton edge="end" aria-label="delete" onClick={() => handleDelete(prompt.id)} sx={{ ml: 1 }}>
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
{prompts.length === 0 && (
<ListItem>
<ListItemText primary="No hay prompts definidos. El sistema usará el comportamiento por defecto." />
</ListItem>
)}
</List>
</Paper>
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} maxWidth="md" fullWidth>
<DialogTitle>{currentPrompt.id ? 'Editar Prompt' : 'Nuevo Prompt'}</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Nombre (Identificador)"
fullWidth
value={currentPrompt.name || ''}
onChange={(e) => setCurrentPrompt({ ...currentPrompt, name: e.target.value })}
sx={{ mb: 2 }}
/>
<TextField
margin="dense"
label="Contenido del Prompt (Instrucciones de Sistema)"
fullWidth
multiline
rows={10}
value={currentPrompt.content || ''}
onChange={(e) => setCurrentPrompt({ ...currentPrompt, content: e.target.value })}
helperText="Estas instrucciones se inyectarán en el contexto del sistema de la IA."
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)} color="secondary">
Cancelar
</Button>
<Button onClick={handleSave} color="primary" variant="contained">
Guardar
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={snackbar.open}
autoHideDuration={6000}
onClose={() => setSnackbar({ ...snackbar, open: false })}
>
<Alert onClose={() => setSnackbar({ ...snackbar, open: false })} severity={snackbar.severity} sx={{ width: '100%' }}>
{snackbar.message}
</Alert>
</Snackbar>
</Box>
);
};
export default SystemPromptManager;