Spaces:
Sleeping
Sleeping
File size: 16,875 Bytes
be15d28 dbe0232 1b5da5a 71ae2d2 dbe0232 be15d28 a437001 be15d28 1b5da5a be15d28 71ae2d2 be15d28 71ae2d2 be15d28 dbe0232 be15d28 dbe0232 be15d28 a437001 be15d28 1b5da5a be15d28 1b5da5a be15d28 1b5da5a a437001 1b5da5a be15d28 1b5da5a be15d28 1b5da5a be15d28 1b5da5a be15d28 1b5da5a be15d28 1b5da5a be15d28 1b5da5a 71ae2d2 1b5da5a 71ae2d2 1b5da5a b48cc90 1b5da5a 71ae2d2 1b5da5a 71ae2d2 1b5da5a dbe0232 1b5da5a be15d28 dbe0232 be15d28 dbe0232 be15d28 1b5da5a be15d28 dbe0232 be15d28 dbe0232 be15d28 1b5da5a be15d28 dbe0232 be15d28 dbe0232 be15d28 1b5da5a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | import React, { useState, useEffect, useRef } from 'react';
import { Users, ShieldCheck, Bot, History, ClipboardList, Lock } from 'lucide-react';
import { cn } from '../lib/utils';
import { LOG_ENTRIES } from '../constants';
import { api, hasAdminKey } from '../api';
import type { LogEntry, Proposal } from '../types';
interface AgentStatus {
name: string;
version: string;
status: string;
progress: number;
skill_count: number;
ts: string;
alert: boolean;
model?: string;
}
export const BotConfig: React.FC = () => {
const [proactiveAlerts, setProactiveAlerts] = useState(true);
const [metaNotifications, setMetaNotifications] = useState(true);
const [agents, setAgents] = useState<AgentStatus[]>([]);
const [logs, setLogs] = useState<LogEntry[]>(LOG_ENTRIES);
const [proposals, setProposals] = useState<Proposal[]>([]);
const [hallazgos, setHallazgos] = useState<Proposal[]>([]);
const teamTokenRef = useRef<HTMLInputElement>(null);
const teamChatIdRef = useRef<HTMLInputElement>(null);
const adminChatIdRef = useRef<HTMLInputElement>(null);
useEffect(() => {
api.getAgentsStatus().then(setAgents).catch(() => {});
api.getLogs().then((data: LogEntry[]) => { if (data?.length) setLogs(data); }).catch(() => {});
api.getProposals().then((data: { proposals: Proposal[]; hallazgos: Proposal[] }) => {
if (data?.proposals?.length) setProposals(data.proposals);
if (data?.hallazgos?.length) setHallazgos(data.hallazgos);
}).catch(() => {});
}, []);
const adminUnlocked = hasAdminKey();
const handleSaveBotConfig = async () => {
// En modo demo (sin key) no se guarda: evita el prompt de admin al pasar por los inputs.
if (!adminUnlocked) return;
try {
await api.saveTelegramConfig({
team_token: teamTokenRef.current?.value || '',
team_chat_id: teamChatIdRef.current?.value || '',
admin_chat_id: adminChatIdRef.current?.value || '',
proactive_alerts: proactiveAlerts,
meta_notifications: metaNotifications,
xyz_threshold: 'alta',
});
} catch {}
};
const displayAgents = agents.length > 0 ? agents : [
{ name: 'AgenteOrquestador', version: 'v2.4.12', status: 'Stable', ts: '', progress: 80, skill_count: 3, alert: false, model: 'meta-llama/Llama-3.2-1B-Instruct' },
{ name: 'AgenteML', version: 'v4.0.0', status: 'Adjusting', ts: '', progress: 55, skill_count: 5, alert: true, model: 'Qwen/Qwen2.5-7B-Instruct' },
{ name: 'AgenteMeta', version: 'v1.2.8', status: 'Active', ts: '', progress: 92, skill_count: 7, alert: false, model: 'meta-llama/Llama-3.1-8B-Instruct' },
{ name: 'AgenteNotificador', version: 'v2.1.0', status: 'Stable', ts: '', progress: 72, skill_count: 2, alert: false, model: '' },
];
return (
<div className="p-8 space-y-8">
{/* Agent Stats */}
<section className="grid grid-cols-4 gap-4">
{displayAgents.map((agent, idx) => {
const dark = idx === 2; // AgenteMeta siempre con fondo oscuro
return (
<div
key={agent.name}
className={cn(
"border-[0.5px] border-border-technical p-4 flex flex-col space-y-3",
dark ? "bg-primary-industrial text-white" : "bg-surface-container"
)}
>
<div className="flex justify-between items-start">
<div className="flex flex-col">
<span className="font-mono text-[10px] font-bold uppercase">{agent.name}</span>
<span className="font-mono text-[9px] opacity-50">{agent.version}</span>
{agent.model ? (
<span className="font-mono text-[8px] opacity-40 mt-0.5">
{agent.model.split('/').pop()?.replace('-Instruct', '') ?? agent.model}
</span>
) : (
<span className="font-mono text-[8px] opacity-30 mt-0.5">sin LLM</span>
)}
</div>
<span className={cn(
"px-1.5 py-0.5 font-mono text-[9px] font-bold uppercase border-[0.5px]",
dark ? "bg-tertiary-industrial/40 text-white border-white/30" :
agent.alert ? "bg-secondary-industrial/10 text-secondary-industrial border-secondary-industrial/30" :
"bg-tertiary-industrial/10 text-tertiary-light border-tertiary-light/30"
)}>
{agent.status}
</span>
</div>
<div className="flex items-center justify-between font-mono text-[9px] opacity-70">
<span>TS: {agent.ts || '—'}</span>
</div>
<div className={cn("relative h-1 w-full mt-2", dark ? "bg-white/10" : "bg-surface-base")}>
<div
className={cn("absolute left-0 top-0 h-full", dark ? "bg-white" : "bg-primary-industrial")}
style={{ width: `${agent.progress}%` }}
/>
<div className={cn("absolute left-[50%] top-1/2 -translate-y-1/2 w-[1px] h-3", dark ? "bg-white" : "bg-primary-industrial")} />
<div className={cn("absolute left-[80%] top-1/2 -translate-y-1/2 w-[1px] h-3", dark ? "bg-white" : "bg-primary-industrial")} />
</div>
</div>
);
})}
</section>
{/* Logs and Proposals */}
<div className="grid grid-cols-2 gap-8 items-start">
{/* Cambios Aplicados */}
<section className="bg-surface-container technical-border flex flex-col h-[500px]">
<div className="p-4 border-b-[0.5px] border-border-technical flex items-center justify-between">
<h2 className="font-mono text-xs font-bold uppercase tracking-widest flex items-center">
<History size={18} className="mr-2 text-primary-industrial" />
Cambios aplicados
</h2>
<span className="font-mono text-[10px] opacity-50 uppercase">Historial Reactivo</span>
</div>
<div className="flex-grow overflow-y-auto p-4 space-y-4">
{logs.map((log) => (
<div key={log.id} className="border-l-2 border-primary-industrial pl-4 py-2 space-y-1">
<p className="text-sm font-medium">{log.title}</p>
<div className="flex items-center space-x-4 font-mono text-[10px] opacity-60">
<span>Hash: {log.hash}</span>
<span>{log.timestamp}</span>
</div>
</div>
))}
</div>
<div className="p-4 border-t-[0.5px] border-border-technical text-center">
<button className="font-mono text-[10px] font-bold uppercase text-primary-industrial hover:underline">
Ver log completo
</button>
</div>
</section>
{/* Propuestas y Hallazgos */}
<section className="bg-surface-container technical-border flex flex-col h-[500px]">
<div className="p-4 border-b-[0.5px] border-border-technical flex items-center justify-between">
<h2 className="font-mono text-xs font-bold uppercase tracking-widest flex items-center">
<ClipboardList size={18} className="mr-2 text-primary-industrial" />
Propuestas y Hallazgos
</h2>
<span className="font-mono text-[10px] text-primary-industrial font-bold uppercase">Solo bot admin</span>
</div>
<div className="flex-grow overflow-y-auto p-4 space-y-6">
{/* Propuestas proactivas */}
{proposals.length > 0 && (
<div className="space-y-4">
<span className="font-mono text-[9px] font-bold uppercase text-slate-400 tracking-widest">Propuestas</span>
{proposals.map((p) => (
<div key={p.id} className="border-[0.5px] border-border-technical bg-white p-4 space-y-3">
<div>
<span className="font-mono text-[9px] font-bold uppercase text-slate-400">Propuesta #{p.seq}</span>
<p className="text-xs font-bold mt-0.5">{p.title}</p>
<p className="text-[11px] text-slate-600 mt-1">{p.description}</p>
</div>
<div className="bg-surface-base p-3">
<span className="font-mono text-[9px] uppercase font-bold opacity-50 block mb-1">Justificación técnica</span>
<p className="text-[11px] leading-relaxed italic">{p.justification}</p>
</div>
<div className="pt-2 border-t border-border-technical flex items-center justify-between">
<span className={cn(
"text-[9px] font-bold font-mono uppercase px-2 py-0.5 border",
p.status === "aprobado" ? "text-green-700 border-green-300 bg-green-50" :
p.status === "rechazado" ? "text-secondary-industrial border-red-200 bg-red-50" :
"text-yellow-700 border-yellow-300 bg-yellow-50"
)}>
{p.status === "aprobado" ? "Aprobado" : p.status === "rechazado" ? "Rechazado" : "Pendiente"}
</span>
{(!p.status || p.status === "pendiente") && (
<p className="text-[10px] font-mono text-slate-400">
Bot: <span className="text-primary-industrial font-bold">/aprobar {p.id}</span>
· <span className="text-secondary-industrial font-bold">/rechazar {p.id}</span>
</p>
)}
</div>
</div>
))}
</div>
)}
{/* Hallazgos del sistema */}
{hallazgos.length > 0 && (
<div className="space-y-4">
<span className="font-mono text-[9px] font-bold uppercase text-slate-400 tracking-widest">Hallazgos del sistema</span>
{hallazgos.map((h) => (
<div key={h.id} className="border-[0.5px] border-blue-100 bg-blue-50/40 p-4 space-y-2">
<div>
<span className="font-mono text-[9px] font-bold uppercase text-slate-400">Hallazgo #{h.seq}</span>
<p className="text-xs font-bold mt-0.5 text-primary-industrial">{h.title}</p>
<p className="text-[11px] text-slate-600 mt-1">{h.description}</p>
</div>
</div>
))}
</div>
)}
{proposals.length === 0 && hallazgos.length === 0 && (
<p className="text-[11px] text-slate-400 italic text-center pt-8">Sin propuestas ni hallazgos registrados.</p>
)}
</div>
</section>
</div>
{/* Bot Configuration */}
<section className="bg-surface-container technical-border">
<div className="p-4 border-b-[0.5px] border-border-technical">
<h2 className="font-mono text-xs font-bold uppercase tracking-widest flex items-center text-primary-industrial">
<Bot size={18} className="mr-2" />
Configuración del Bot
</h2>
</div>
{!adminUnlocked && (
<div className="px-4 py-2 border-b-[0.5px] border-border-technical bg-surface-base flex items-center gap-2">
<Lock size={12} className="text-yellow-600" />
<span className="text-[10px] font-mono uppercase text-slate-500">
Solo lectura en modo demo — la configuración requiere API key de administrador
</span>
</div>
)}
<div className="grid grid-cols-2 divide-x-[0.5px] divide-border-technical">
{/* Canal Equipo */}
<div className="p-6 space-y-6">
<div className="flex items-center space-x-2 mb-4">
<Users size={18} className="text-primary-industrial" />
<span className="text-xs font-bold uppercase tracking-tight">Canal Equipo</span>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col space-y-1">
<label className="font-mono text-[9px] uppercase font-bold opacity-50">Token</label>
<input
ref={teamTokenRef}
className="border-[0.5px] border-border-technical bg-white px-3 py-2 text-xs font-mono focus:ring-0 focus:border-primary-industrial outline-none disabled:opacity-50"
type="password"
placeholder="••••••••••••"
disabled={!adminUnlocked}
onBlur={handleSaveBotConfig}
/>
</div>
<div className="flex flex-col space-y-1">
<label className="font-mono text-[9px] uppercase font-bold opacity-50">Chat ID</label>
<input
ref={teamChatIdRef}
className="border-[0.5px] border-border-technical bg-white px-3 py-2 text-xs font-mono focus:ring-0 focus:border-primary-industrial outline-none disabled:opacity-50"
type="text"
placeholder="-100XXXXXXXXX"
disabled={!adminUnlocked}
onBlur={handleSaveBotConfig}
/>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-xs">Alertas Proactivas</span>
<button
onClick={() => setProactiveAlerts(!proactiveAlerts)}
className={cn(
"w-8 h-4 relative transition-colors duration-200",
proactiveAlerts ? "bg-tertiary-industrial" : "bg-slate-300"
)}
>
<div className={cn(
"absolute top-0.5 w-3 h-3 bg-white transition-all duration-200",
proactiveAlerts ? "right-0.5" : "left-0.5"
)} />
</button>
</div>
<div className="flex flex-col space-y-1">
<label className="font-mono text-[9px] uppercase font-bold opacity-50">Umbral XYZ Selector</label>
<select className="border-[0.5px] border-border-technical bg-white px-3 py-2 text-xs font-mono focus:ring-0 focus:border-primary-industrial outline-none appearance-none cursor-pointer">
<option>Alta Varianza (>15%)</option>
<option>Media Varianza (>8%)</option>
<option>Solo Críticos</option>
</select>
</div>
</div>
{/* Canal Admin */}
<div className="p-6 space-y-6">
<div className="flex items-center space-x-2 mb-4">
<ShieldCheck size={18} className="text-primary-industrial" />
<span className="text-xs font-bold uppercase tracking-tight">Canal Admin</span>
</div>
<div className="flex flex-col space-y-1">
<label className="font-mono text-[9px] uppercase font-bold opacity-50">Chat ID Admin</label>
<input
ref={adminChatIdRef}
className="border-[0.5px] border-border-technical bg-white px-3 py-2 text-xs font-mono focus:ring-0 focus:border-primary-industrial outline-none disabled:opacity-50"
type="text"
placeholder="XXXXXXXXX"
disabled={!adminUnlocked}
onBlur={handleSaveBotConfig}
/>
</div>
<div className="flex items-center justify-between">
<span className="text-xs">Notificaciones AgenteMeta</span>
<button
onClick={() => setMetaNotifications(!metaNotifications)}
className={cn(
"w-8 h-4 relative transition-colors duration-200",
metaNotifications ? "bg-tertiary-industrial" : "bg-slate-300"
)}
>
<div className={cn(
"absolute top-0.5 w-3 h-3 bg-white transition-all duration-200",
metaNotifications ? "right-0.5" : "left-0.5"
)} />
</button>
</div>
<div className="pt-4 border-t-[0.5px] border-border-technical border-dashed">
<div className="flex items-center space-x-2 text-[10px] font-mono">
<span className="w-2 h-2 bg-tertiary-light rounded-full animate-pulse"></span>
<span className="uppercase font-bold text-tertiary-light">Conectado</span>
<span className="opacity-50">· última actividad hace 2h</span>
</div>
</div>
</div>
</div>
</section>
</div>
);
};
|