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:&nbsp;<span className="text-primary-industrial font-bold">/aprobar {p.id}</span>
                          &nbsp;·&nbsp;<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 (&gt;15%)</option>
                <option>Media Varianza (&gt;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>
  );
};