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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
221
chatbot-admin/src/components/SystemPromptManager.tsx
Normal file
221
chatbot-admin/src/components/SystemPromptManager.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user