import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import { CronJob, Cfg, Session, Skill } from '../types'; interface AppContextType { token: string; sidebarOpen: boolean; setSidebarOpen: (open: boolean) => void; setToken: (token: string) => void; isGatewayOnline: boolean; setIsGatewayOnline: (online: boolean) => void; cfg: Cfg; setCfg: React.Dispatch>; cfgRaw: string; setCfgRaw: (raw: string) => void; nodes: string; setNodes: (nodes: string) => void; cron: CronJob[]; setCron: (cron: CronJob[]) => void; skills: Skill[]; setSkills: (skills: Skill[]) => void; sessions: Session[]; setSessions: React.Dispatch>; refreshAll: () => Promise; refreshCron: () => Promise; refreshNodes: () => Promise; refreshSkills: () => Promise; loadConfig: () => Promise; q: string; } const AppContext = createContext(undefined); export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const initialToken = (() => { try { const u = new URL(window.location.href); return u.searchParams.get('token') || 'cg_nLnov7DPd9yqZDYPEU5pHnoa'; } catch { return 'cg_nLnov7DPd9yqZDYPEU5pHnoa'; } })(); const [token, setToken] = useState(initialToken); const [sidebarOpen, setSidebarOpen] = useState(false); const [isGatewayOnline, setIsGatewayOnline] = useState(true); const [cfg, setCfg] = useState({}); const [cfgRaw, setCfgRaw] = useState('{}'); const [nodes, setNodes] = useState('[]'); const [cron, setCron] = useState([]); const [skills, setSkills] = useState([]); const [sessions, setSessions] = useState([{ key: 'webui:default', title: 'Default' }]); const q = token ? `?token=${encodeURIComponent(token)}` : ''; const loadConfig = useCallback(async () => { try { const r = await fetch(`/webui/api/config${q}`); if (!r.ok) throw new Error('Failed to load config'); const txt = await r.text(); setCfgRaw(txt); try { setCfg(JSON.parse(txt)); } catch { setCfg({}); } setIsGatewayOnline(true); } catch (e) { setIsGatewayOnline(false); console.error(e); } }, [q]); const refreshNodes = useCallback(async () => { try { const r = await fetch(`/webui/api/nodes${q}`); if (!r.ok) throw new Error('Failed to load nodes'); const j = await r.json(); setNodes(JSON.stringify(j.nodes || [], null, 2)); setIsGatewayOnline(true); } catch (e) { setIsGatewayOnline(false); console.error(e); } }, [q]); const refreshCron = useCallback(async () => { try { const r = await fetch(`/webui/api/cron${q}`); if (!r.ok) throw new Error('Failed to load cron'); const j = await r.json(); setCron(Array.isArray(j.jobs) ? j.jobs : []); setIsGatewayOnline(true); } catch (e) { setIsGatewayOnline(false); console.error(e); } }, [q]); const refreshSkills = useCallback(async () => { try { const r = await fetch(`/webui/api/skills${q}`); if (!r.ok) throw new Error('Failed to load skills'); const j = await r.json(); setSkills(Array.isArray(j.skills) ? j.skills : []); setIsGatewayOnline(true); } catch (e) { setIsGatewayOnline(false); console.error(e); } }, [q]); const refreshAll = useCallback(async () => { await Promise.all([loadConfig(), refreshCron(), refreshNodes(), refreshSkills()]); }, [loadConfig, refreshCron, refreshNodes, refreshSkills]); useEffect(() => { refreshAll(); const interval = setInterval(() => { refreshCron(); refreshNodes(); refreshSkills(); }, 10000); return () => clearInterval(interval); }, [token, refreshAll, refreshCron, refreshNodes, refreshSkills]); return ( {children} ); }; export const useAppContext = () => { const context = useContext(AppContext); if (!context) throw new Error('useAppContext must be used within an AppProvider'); return context; };