agentic-os/interface/hq-dashboard/app/page.jsx

1610 lines
87 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useState, useEffect, useRef } from "react";
import {
Activity,
Terminal,
User,
Cpu,
ShieldAlert,
CheckCircle,
XCircle,
Database,
FileText,
Sparkles,
Send,
RefreshCw,
Play,
GitPullRequest,
Layers,
Settings,
AlertCircle,
Trash2,
HeartPulse,
LogOut,
Maximize2,
CloudSun,
Wind,
Droplets,
Thermometer,
Compass,
MessageSquare,
Mail
} from "lucide-react";
import WhatsAppAgentUI from "../components/WhatsAppAgentUI";
import GmailAgentUI from "../components/GmailAgentUI";
export default function Page() {
const [activeTab, setActiveTab] = useState("dashboard");
// Claw Chat State
const [messages, setMessages] = useState([
{
role: "assistant",
content: "Hello! I am Claw, your local agent. Try:\n• `weather` — live weather for Mars, PA\n• `whatsapp me: message` — send now to your phone\n• `remind me at 3pm: call doctor` — schedule for later\n• `remind me tomorrow at 6am: gym` — future reminders\n• Or give me a list with times and I'll schedule them all!",
timestamp: "Just now"
}
]);
const [inputValue, setInputValue] = useState("");
const [isClawTyping, setIsClawTyping] = useState(false);
const [clawTerminalLogs, setClawTerminalLogs] = useState([]);
const [weatherCard, setWeatherCard] = useState(null);
const [weatherSending, setWeatherSending] = useState(false);
const [weatherSent, setWeatherSent] = useState(false);
const [scheduledReminders, setScheduledReminders] = useState([]);
// OpsAgent State
const [pendingApprovals, setPendingApprovals] = useState([
{
id: "ops-req-102",
targetType: "proxmox",
targetId: "102",
action: "clean_disk",
details: "LXC container root filesystem is at 94% disk usage. Recommending apt cache clean and temporary file removal.",
timestamp: "5 mins ago",
status: "pending"
},
{
id: "ops-req-201",
targetType: "kubernetes",
targetId: "curio-app-xyz",
action: "restart_pod",
details: "Pod curio-app-xyz is in CrashLoopBackOff state. Logs indicate: 'Database connection failed: Connection refused by 10.96.20.10:5432'. Recommending pod deletion to trigger k8s reschedule.",
timestamp: "10 mins ago",
status: "pending"
}
]);
const [opsLogs, setOpsLogs] = useState([
"2026-05-29 01:10:00 [OpsAgent] Initialized diagnostic run on K8s cluster...",
"2026-05-29 01:10:15 [OpsAgent] Found Pod 'curio-app-xyz' in CrashLoopBackOff. Emitting warning.",
"2026-05-29 01:12:02 [OpsAgent] Proxmox VMID 102 root usage checked: 94%. Action required."
]);
// Curio HMS State
const [dictationText, setDictationText] = useState(
"Patient is a 45-year-old male presenting with severe, crushing chest pain radiating to his left arm. He has a past medical history of hypertension. Admitting to Cardiac ICU."
);
const [clinicalNote, setClinicalNote] = useState(null);
const [billingCodes, setBillingCodes] = useState([]);
const [isProcessingClinical, setIsProcessingClinical] = useState(false);
// Workflow logs state
const [workflows, setWorkflows] = useState([
{ id: "wf-bernard-001", name: "PRReviewWorkflow", agent: "Bernard", target: "pr-401", status: "completed", result: "Approved with suggestions" },
{ id: "wf-gumbo-002", name: "GumboSummarizeWorkflow", agent: "Gumbo", target: "api-spec.md", status: "completed", result: "Summary persisted in PG" },
{ id: "wf-ops-003", name: "OpsRemediateWorkflow", agent: "OpsAgent", target: "VMID 102", status: "running", result: "Awaiting approval signal" }
]);
// Trips State
const [tripsList, setTripsList] = useState([]);
const [selectedTripId, setSelectedTripId] = useState(null);
const [tripEditorJson, setTripEditorJson] = useState("");
const [isSavingTrip, setIsSavingTrip] = useState(false);
const [isTestingTrip, setIsTestingTrip] = useState(false);
// Config State
const [configData, setConfigData] = useState({ defaultGroupUrl: "", tripGroupUrl: "" });
const [isSavingConfig, setIsSavingConfig] = useState(false);
const [configLoaded, setConfigLoaded] = useState(false);
const messagesEndRef = useRef(null);
const terminalEndRef = useRef(null);
useEffect(() => {
if (activeTab === "config" && !configLoaded) {
fetch('/api/config/whatsapp')
.then(res => res.json())
.then(data => {
if (data.success) {
setConfigData({
defaultGroupUrl: data.data['default-group-url'] || "",
tripGroupUrl: data.data['trip-group-url'] || ""
});
setConfigLoaded(true);
}
})
.catch(console.error);
}
}, [activeTab, configLoaded]);
const handleSaveConfig = async () => {
setIsSavingConfig(true);
try {
const res = await fetch('/api/config/whatsapp', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(configData)
});
const data = await res.json();
if (data.success) {
alert("Configuration saved successfully to Kubernetes manifest!");
} else {
alert("Failed to save: " + data.error);
}
} catch (e) {
alert("Error saving config: " + e.message);
} finally {
setIsSavingConfig(false);
}
};
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, isClawTyping]);
useEffect(() => {
terminalEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [clawTerminalLogs]);
useEffect(() => {
if (activeTab === "trips") {
fetch('/api/trips').then(res => res.json()).then(data => {
if(Array.isArray(data)) setTripsList(data);
}).catch(() => {});
}
}, [activeTab]);
// Simulated Claw response logic
const handleSendMessage = async (e) => {
e.preventDefault();
if (!inputValue.trim()) return;
const userText = inputValue;
setMessages(prev => [...prev, { role: "user", content: userText, timestamp: "Just now" }]);
setInputValue("");
setIsClawTyping(true);
setWeatherCard(null);
setWeatherSent(false);
setClawTerminalLogs(prev => [...prev, `[Claw] Received instruction: "${userText}"`]);
let responseText = "";
let logs = [];
const cleanInput = userText.toLowerCase();
// ── WhatsApp Send Command ────────────────────────────────────────────────
// Matches: "whatsapp me: ...", "send to whatsapp ...", "message me ...",
// "text me ...", "wa me ...", "send 'X' to my whatsapp"
const waIntent = /(?:whatsapp\s+me|message\s+me|text\s+me|send\s+(?:to\s+)?(?:my\s+)?whatsapp|wa\s+me|remind\s+me\s+via\s+whatsapp)[:\s]+(.+)/i;
const waMatch = userText.match(waIntent);
if (waMatch) {
const waMsg = waMatch[1].replace(/^["'`]|["'`]$/g, '').trim(); // strip surrounding quotes
setClawTerminalLogs(prev => [...prev,
`[Claw] WhatsApp send detected. Message: "${waMsg}"`,
`[Claw] Calling /api/send-whatsapp (personalOnly) ...`
]);
try {
const res = await fetch('/api/send-whatsapp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: `📌 *Claw Reminder*\n\n${waMsg}`, personalOnly: true }),
});
const data = await res.json();
if (data.success) {
setClawTerminalLogs(prev => [...prev, `[Claw] ✅ Delivered to +14085505485. MsgID: ${data.messageId}`]);
responseText = `✅ Done! I've sent that to your WhatsApp (+14085505485):\n\n> "${waMsg}"\n\nYou should see it arrive shortly.`;
} else {
throw new Error(data.error || 'Gateway error');
}
} catch (err) {
setClawTerminalLogs(prev => [...prev, `[Claw] ❌ WhatsApp send failed: ${err.message}`]);
responseText = `❌ Couldn't send to WhatsApp: ${err.message}. Make sure the gateway is running.`;
}
// ── Scheduled Reminder ─────────────────────────────────────────────────────
// Patterns: "remind me at X: msg", "schedule (whatsapp) at X: msg",
// "send to whatsapp at X: msg", multi-bullet lists
} else if (
/(?:remind\s+me|schedule\s+(?:a\s+)?(?:whatsapp\s+)?(?:reminder|message)?)\s+(?:at|on|for|in|tomorrow|monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i.test(userText) ||
/send\s+to\s+whatsapp\s+(?:at|in|tomorrow|on\s+)/i.test(userText) ||
// multi-bullet list: "schedule these:\n- msg at 3pm\n- msg2 at 6am"
(/(?:schedule|remind\s+me)/i.test(userText) && userText.includes('\n'))
) {
// Check for multi-item list (lines starting with - or •)
const lines = userText.split('\n').map(l => l.trim()).filter(Boolean);
const bulletLines = lines.filter(l => /^[-*]\s+/.test(l) || (lines.length > 2 && l !== lines[0]));
const isBatch = bulletLines.length > 1;
if (isBatch) {
// Batch scheduling
const items = [];
const skipped = [];
for (const line of bulletLines) {
const clean = line.replace(/^[-•*]\s*/, '').trim();
// Try to split into message + time: "call doctor at 2pm" or "2pm: call doctor"
// Strategy: extract time, rest is the message
const sendAt = parseScheduleDateTime(clean);
if (!sendAt) { skipped.push(clean); continue; }
// Remove the time expression from the label
const label = clean.replace(/(at|by|on|for)?\s*\d{1,2}(?::\d{2})?\s*(am|pm)/gi, '').replace(/tomorrow|today|monday|tuesday|wednesday|thursday|friday|saturday|sunday/gi, '').replace(/in\s+\d+\s+(hour|hr|minute|min)s?/gi, '').trim().replace(/\s+/g, ' ');
const formatted = `⏰ *Claw Reminder*\n\n${label}\n\n_Scheduled for ${sendAt.toLocaleString('en-US', { timeZone: 'America/New_York', weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true })} ET_`;
items.push({ message: formatted, sendAt: sendAt.toISOString(), label });
}
if (items.length === 0) {
responseText = `❌ I couldn't parse any valid times from your list. Make sure each item includes a time like "at 3pm", "tomorrow at 9am", or "in 2 hours".`;
} else {
setClawTerminalLogs(prev => [...prev, `[Claw] Scheduling ${items.length} reminders...`]);
try {
const res = await fetch('/api/reminders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items }) });
const data = await res.json();
const lines2 = data.created?.map(c => `✅ "${c.label}" → ${formatReminderTime(c.sendAt)} ET`) || [];
if (skipped.length) lines2.push(...skipped.map(s => `⚠️ Skipped (no time): "${s}"`));
responseText = `📅 Scheduled ${data.created?.length || 0} reminder(s):\n\n${lines2.join('\n')}`;
// Refresh reminder panel
fetch('/api/reminders?status=pending').then(r => r.json()).then(d => { if (Array.isArray(d)) setScheduledReminders(d); });
setClawTerminalLogs(prev => [...prev, `[Claw] ✅ Batch scheduled: ${items.length} reminders`]);
} catch (err) {
responseText = `❌ Failed to schedule: ${err.message}`;
}
}
} else {
// Single reminder
// Parse message after colon: "remind me at 3pm: call doctor" → msg = "call doctor"
const colonSplit = userText.match(/[:\u2014]\s*(.+)$/s);
const msgText = colonSplit ? colonSplit[1].trim() : userText;
const sendAt = parseScheduleDateTime(userText);
if (!sendAt) {
responseText = `❌ I couldn't find a valid time in your message. Try: "remind me at 3pm: call doctor" or "remind me tomorrow at 9am: gym".`;
} else {
const etLabel = sendAt.toLocaleString('en-US', { timeZone: 'America/New_York', weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true });
const waMsg = `⏰ *Claw Reminder*\n\n${msgText}\n\n_Scheduled for ${etLabel} ET_`;
setClawTerminalLogs(prev => [...prev, `[Claw] Scheduling: "${msgText}" → ${sendAt.toISOString()}`]);
try {
const res = await fetch('/api/schedule-reminder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: waMsg, sendAt: sendAt.toISOString(), label: msgText }),
});
const data = await res.json();
if (data.success) {
responseText = `✅ Reminder scheduled!\n\n📌 **"${msgText}"**\n🕐 Delivers at **${etLabel} ET** to your WhatsApp (+14085505485)`;
fetch('/api/reminders?status=pending').then(r => r.json()).then(d => { if (Array.isArray(d)) setScheduledReminders(d); });
setClawTerminalLogs(prev => [...prev, `[Claw] ✅ Scheduled ID: ${data.id}`]);
} else {
throw new Error(data.error || 'Scheduling failed');
}
} catch (err) {
responseText = `❌ Couldn't schedule: ${err.message}`;
setClawTerminalLogs(prev => [...prev, `[Claw] ❌ ${err.message}`]);
}
}
}
} else if (cleanInput.includes("weather")) {
// Extract optional location override
const locMatch = userText.match(/(?:weather\s+(?:in|for|at)?\s*)(.+)/i);
const location = locMatch ? locMatch[1].trim() : '16046';
logs = [
`[Claw] Fetching live weather data for "${location}"...`,
"[Claw] Connecting to wttr.in weather service...",
];
setClawTerminalLogs(prev => [...prev, ...logs]);
try {
const res = await fetch(`/api/weather?location=${encodeURIComponent(location)}`);
const wx = await res.json();
if (wx.error) throw new Error(wx.error);
setWeatherCard(wx);
setClawTerminalLogs(prev => [...prev,
`[Claw] Got weather for ${wx.location}: ${wx.current.tempF}°F, ${wx.current.desc}`,
"[Claw] Rendering weather card. Use the button to push to WhatsApp."
]);
const emoji = getWeatherEmoji(wx.current.desc);
responseText = `${emoji} Here's the live weather for **${wx.location}**:\n\n🌡️ **${wx.current.tempF}°F** (feels like ${wx.current.feelsLikeF}°F) — ${wx.current.desc}\n💧 Humidity: ${wx.current.humidity}% | 💨 Wind: ${wx.current.windMph} mph ${wx.current.windDir}\n📊 Today: ${wx.today.minF}°F ${wx.today.maxF}°F | 🌅 ${wx.today.sunrise} / 🌇 ${wx.today.sunset}\n\nI've generated the full report card below. Hit **Send to WhatsApp** to push it to your group!`;
} catch (err) {
responseText = `❌ Could not fetch weather: ${err.message}. Check your internet or try a city name like \`weather Toronto\`.`;
setClawTerminalLogs(prev => [...prev, `[Claw] Error: ${err.message}`]);
}
} else if (cleanInput.includes("pod") || cleanInput.includes("k8s") || cleanInput.includes("kubectl")) {
await new Promise(resolve => setTimeout(resolve, 1500));
logs = [
"Connecting to Kubernetes API server...",
"Executing: kubectl get pods -n default",
"Found 2 pods in namespace 'default':",
" - web-pod-xyz 0/1 CrashLoopBackOff 5 2h",
" - db-pod-abc 1/1 Running 0 5d",
"Fetching logs for 'web-pod-xyz'...",
" Error detected: Database connection refused by 10.96.20.10:5432"
];
responseText = "I've checked the Kubernetes pods. Pod `web-pod-xyz` is currently failing due to a database connection refusal. You can use the **OpsAgent** tab to approve a pod restart or scale the DB deployment.";
} else if (cleanInput.includes("proxmox") || cleanInput.includes("vmid") || cleanInput.includes("disk")) {
await new Promise(resolve => setTimeout(resolve, 1500));
logs = [
"Establishing SSH connection to Proxmox cluster host 192.168.8.225...",
"Running: pct status 102",
" status: running",
"Running: pct exec 102 -- df -h /",
" Filesystem Size Used Avail Use% Mounted on",
" /dev/loop0 20G 19G 0.2G 94% /",
"Disk usage critically high! Proposed action: clean_disk"
];
responseText = "Proxmox Container ID `102` is running out of disk space (94% used). I have created a pending action under the **OpsAgent Center** to perform automated cleanup. Would you like me to execute it?";
} else if (cleanInput.includes("hello") || cleanInput.includes("hi")) {
await new Promise(resolve => setTimeout(resolve, 800));
responseText = "Hi there! I am Claw, your local personal assistant. Try asking me to `check k8s pods`, `diagnose proxmox container 102`, or just type `weather` for a live report!";
} else {
await new Promise(resolve => setTimeout(resolve, 1200));
responseText = `I've analyzed your query: "${userText}". I recommend initiating an **OpsRemediateWorkflow** to check the status or scanning your configuration files. Let me know if you would like me to trigger that for you.`;
}
setClawTerminalLogs(prev => [...prev, ...logs, "[Claw] Response formulated successfully."]);
setMessages(prev => [...prev, { role: "assistant", content: responseText, timestamp: "Just now" }]);
setIsClawTyping(false);
};
// Weather emoji helper
const getWeatherEmoji = (desc) => {
const d = (desc || '').toLowerCase();
if (d.includes('sunny') || d.includes('clear')) return '☀️';
if (d.includes('partly cloudy')) return '⛅';
if (d.includes('cloud') || d.includes('overcast')) return '☁️';
if (d.includes('thunder') || d.includes('storm')) return '⛈️';
if (d.includes('rain') || d.includes('drizzle')) return '🌧️';
if (d.includes('snow') || d.includes('blizzard')) return '❄️';
if (d.includes('fog') || d.includes('mist')) return '🌫️';
return '🌤️';
};
// Send weather to WhatsApp
const handleSendWeatherToWhatsApp = async () => {
if (!weatherCard) return;
setWeatherSending(true);
const wx = weatherCard;
const emoji = getWeatherEmoji(wx.current.desc);
const msg = [
`${emoji} *Good Morning — Daily Weather Report*`,
`📍 *${wx.location}* (ZIP: ${wx.zip})`,
``,
`${emoji} ${wx.current.desc}`,
`🌡️ *Temp:* ${wx.current.tempF}°F (feels like ${wx.current.feelsLikeF}°F)`,
`💧 *Humidity:* ${wx.current.humidity}%`,
`💨 *Wind:* ${wx.current.windMph} mph ${wx.current.windDir}`,
`👁️ *Visibility:* ${wx.current.visibility} mi`,
`☀️ *UV Index:* ${wx.current.uvIndex}`,
``,
`📊 *Today:* ${wx.today.minF}°F ${wx.today.maxF}°F`,
`🌅 Sunrise: ${wx.today.sunrise} 🌇 Sunset: ${wx.today.sunset}`,
wx.tomorrow ? `🔮 *Tomorrow:* ${wx.tomorrow.minF}°F ${wx.tomorrow.maxF}°F ${getWeatherEmoji(wx.tomorrow.desc)} ${wx.tomorrow.desc}` : '',
``,
`_Agentic OS HQ • hq.applaude.net_ 🏠`,
].filter(Boolean).join('\n');
try {
const res = await fetch('/api/send-whatsapp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: msg })
});
const data = await res.json();
if (data.success) {
setWeatherSent(true);
setMessages(prev => [...prev, { role: "assistant", content: "✅ Weather report sent to your WhatsApp group!", timestamp: "Just now" }]);
} else {
setMessages(prev => [...prev, { role: "assistant", content: `❌ Failed to send: ${data.error}`, timestamp: "Just now" }]);
}
} catch (err) {
setMessages(prev => [...prev, { role: "assistant", content: `❌ Error: ${err.message}`, timestamp: "Just now" }]);
} finally {
setWeatherSending(false);
}
};
// Poll scheduled reminders every 30s when on claw tab
useEffect(() => {
if (activeTab !== 'claw') return;
const fetchReminders = async () => {
try {
const res = await fetch('/api/reminders?status=pending');
if (res.ok) {
const data = await res.json();
if (Array.isArray(data)) setScheduledReminders(data);
}
} catch { /* reminder service may not be up yet */ }
};
fetchReminders();
const interval = setInterval(fetchReminders, 30000);
return () => clearInterval(interval);
}, [activeTab]);
// ── Natural-language date/time parser ────────────────────────────────────────
const parseScheduleDateTime = (text) => {
const t = text.toLowerCase();
const now = new Date();
// "in X hours/minutes"
const inMatch = t.match(/\bin\s+(\d+)\s+(hour|hr|minute|min)s?\b/);
if (inMatch) {
const n = parseInt(inMatch[1]);
const ms = inMatch[2].startsWith('h') ? n * 3600000 : n * 60000;
return new Date(now.getTime() + ms);
}
// Extract time component: "3pm", "6:30am", "14:00"
let hours = null, minutes = 0;
const ampm = t.match(/(\d{1,2})(?::(\d{2}))?\s*(am|pm)/);
const h24 = t.match(/(\d{2}):(\d{2})(?!\s*(am|pm))/);
if (ampm) {
hours = parseInt(ampm[1]);
minutes = ampm[2] ? parseInt(ampm[2]) : 0;
if (ampm[3] === 'pm' && hours !== 12) hours += 12;
if (ampm[3] === 'am' && hours === 12) hours = 0;
} else if (h24) {
hours = parseInt(h24[1]);
minutes = parseInt(h24[2]);
}
// Build date
let date = new Date(now);
let explicitDate = false;
if (t.includes('tomorrow')) {
date.setDate(date.getDate() + 1);
explicitDate = true;
} else {
const days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday'];
const dayIdx = days.findIndex(d => t.includes(d));
if (dayIdx !== -1) {
const diff = ((dayIdx - now.getDay()) + 7) % 7 || 7; // next occurrence
date.setDate(date.getDate() + diff);
explicitDate = true;
} else {
// Named month: "May 30", "June 1"
const months = ['january','february','march','april','may','june','july','august','september','october','november','december'];
const shortM = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'];
let mIdx = months.findIndex(m => t.includes(m));
if (mIdx === -1) mIdx = shortM.findIndex(m => t.match(new RegExp('\\b' + m + '\\b')));
if (mIdx !== -1) {
const dayNum = t.match(/(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\w*\s+(\d{1,2})/i);
if (dayNum) {
date.setMonth(mIdx, parseInt(dayNum[1]));
if (date < now) date.setFullYear(date.getFullYear() + 1);
explicitDate = true;
}
}
}
}
if (hours === null) return null; // no time found
date.setHours(hours, minutes, 0, 0);
// If the computed time is in the past and no explicit date was given, push to tomorrow
if (date <= now && !explicitDate) date.setDate(date.getDate() + 1);
return date;
};
// ── Format a Date nicely for display ─────────────────────────────────────────
const formatReminderTime = (isoStr) => {
const d = new Date(isoStr);
return d.toLocaleString('en-US', {
timeZone: 'America/New_York',
weekday: 'short', month: 'short', day: 'numeric',
hour: 'numeric', minute: '2-digit', hour12: true
});
};
// ── Cancel a reminder ─────────────────────────────────────────────────────────
const handleCancelReminder = async (id) => {
try {
const res = await fetch(`/api/reminders?id=${id}`, { method: 'DELETE' });
if (res.ok) setScheduledReminders(prev => prev.filter(r => r.id !== id));
} catch { /* silent */ }
};
const handleApprove = async (id, action, target) => {
setPendingApprovals(prev => prev.map(item => item.id === id ? { ...item, status: "approved" } : item));
setOpsLogs(prev => [...prev, `2026-05-29 01:14:10 [OpsAgent] RECEIVED APPROVAL SIGNAL for request ${id}`]);
// Simulate execution log
let execLogs = [
`[OpsAgent] Initiating execution of '${action}' on target '${target}'...`,
`[OpsAgent] Sending signal to Temporal workflow...`,
];
if (action === "clean_disk") {
execLogs.push(
"[OpsAgent] SSH into Proxmox node VMID 102...",
"[OpsAgent] running: apt-get clean && rm -rf /tmp/*",
"[OpsAgent] Disk space cleared. Root filesystem usage reduced from 94% to 71%."
);
} else {
execLogs.push(
`[OpsAgent] Running: kubectl delete pod ${target} -n default`,
`[OpsAgent] Pod deleted. K8s controller successfully scheduled a replica.`
);
}
// Delay log write to simulate real execution
for (const log of execLogs) {
await new Promise(resolve => setTimeout(resolve, 800));
setOpsLogs(prev => [...prev, log]);
}
setPendingApprovals(prev => prev.filter(item => item.id !== id));
setWorkflows(prev => prev.map(wf => wf.target === (action === "clean_disk" ? "VMID 102" : target) ? { ...wf, status: "completed", result: "Remediated successfully" } : wf));
};
const handleReject = (id) => {
setPendingApprovals(prev => prev.filter(item => item.id !== id));
setOpsLogs(prev => [...prev, `2026-05-29 01:14:30 [OpsAgent] Request ${id} REJECTED by operator. Action aborted.`]);
};
// Clinical note simulation
const handleProcessClinical = async () => {
setIsProcessingClinical(true);
setClinicalNote(null);
setBillingCodes([]);
await new Promise(resolve => setTimeout(resolve, 1500));
setClinicalNote({
subjective: "Patient is a 45-year-old male presenting with severe, crushing chest pain radiating to his left arm. He reports shortness of breath.",
objective: "Alert and oriented. Reports pain scale 8/10.",
assessment: "Acute Myocardial Infarction suspected.",
plan: "Order EKG, troponin levels. Administer Aspirin 324mg immediately. Admit to Cardiac ICU."
});
setBillingCodes([
{ code: "I21.9", desc: "Acute myocardial infarction, unspecified" },
{ code: "93000", desc: "Electrocardiogram, tracing and report" },
{ code: "99223", desc: "Initial hospital care, high level severity" }
]);
setIsProcessingClinical(false);
};
const handleSaveTrip = async () => {
try {
setIsSavingTrip(true);
let parsed = JSON.parse(tripEditorJson);
if (selectedTripId) {
await fetch(`/api/trips/${selectedTripId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(parsed)
});
} else {
await fetch('/api/trips', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(parsed)
});
}
const data = await fetch('/api/trips').then(r => r.json());
setTripsList(Array.isArray(data) ? data : []);
setSelectedTripId(null);
setTripEditorJson("");
} catch (e) {
alert("Invalid JSON or save failed: " + e.message);
} finally {
setIsSavingTrip(false);
}
};
const handleTestTrip = async (id) => {
setIsTestingTrip(true);
try {
const res = await fetch(`/api/trips/${id}/test`, { method: 'POST' });
const data = await res.json();
if (data.success) {
alert("Test briefing fired to WhatsApp successfully!");
} else {
alert("Test failed: " + JSON.stringify(data));
}
} catch (e) {
alert("Error triggering test: " + e.message);
} finally {
setIsTestingTrip(false);
}
};
return (
<div className="app-container">
{/* Sidebar Navigation */}
<aside className="sidebar">
<div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
{/* Dashboard Title */}
<div style={{ display: "flex", alignItems: "center", gap: "10px", padding: "0 8px" }}>
<div style={{ width: "32px", height: "32px", borderRadius: "8px", background: "linear-gradient(135deg, var(--accent-violet), var(--accent-cyan))", display: "flex", alignItems: "center", justifyContent: "center" }}>
<Terminal size={18} style={{ color: "#fff" }} />
</div>
<div>
<h2 style={{ fontSize: "16px", color: "var(--text-primary)" }}>Agentic OS</h2>
<span style={{ fontSize: "11px", color: "var(--text-muted)", letterSpacing: "1px", textTransform: "uppercase" }}>Control HQ</span>
</div>
</div>
{/* Nav Items */}
<nav style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<button
onClick={() => setActiveTab("dashboard")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "dashboard" ? "rgba(139, 92, 246, 0.15)" : "transparent",
color: activeTab === "dashboard" ? "var(--accent-violet)" : "var(--text-secondary)",
fontWeight: activeTab === "dashboard" ? "600" : "500",
transition: "all 0.2s"
}}
>
<Cpu size={18} />
Dashboard Home
</button>
<button
onClick={() => setActiveTab("claw")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "claw" ? "rgba(6, 182, 212, 0.15)" : "transparent",
color: activeTab === "claw" ? "var(--accent-cyan)" : "var(--text-secondary)",
fontWeight: activeTab === "claw" ? "600" : "500",
transition: "all 0.2s"
}}
>
<Sparkles size={18} />
Claw Assistant
</button>
<button
onClick={() => setActiveTab("ops")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "ops" ? "rgba(245, 158, 11, 0.15)" : "transparent",
color: activeTab === "ops" ? "var(--accent-amber)" : "var(--text-secondary)",
fontWeight: activeTab === "ops" ? "600" : "500",
transition: "all 0.2s"
}}
>
<ShieldAlert size={18} />
OpsAgent Center
{pendingApprovals.length > 0 && (
<span style={{ fontSize: "10px", padding: "2px 6px", borderRadius: "10px", background: "var(--accent-amber)", color: "#000", fontWeight: "700", marginLeft: "auto" }}>
{pendingApprovals.length}
</span>
)}
</button>
<button
onClick={() => setActiveTab("curio")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "curio" ? "rgba(16, 185, 129, 0.15)" : "transparent",
color: activeTab === "curio" ? "var(--accent-emerald)" : "var(--text-secondary)",
fontWeight: activeTab === "curio" ? "600" : "500",
transition: "all 0.2s"
}}
>
<HeartPulse size={18} />
Clinical Workspace
</button>
<button
onClick={() => setActiveTab("trips")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "trips" ? "rgba(236, 72, 153, 0.15)" : "transparent",
color: activeTab === "trips" ? "var(--accent-pink, #ec4899)" : "var(--text-secondary)",
fontWeight: activeTab === "trips" ? "600" : "500",
transition: "all 0.2s"
}}
>
<Compass size={18} />
Trips & Briefings
</button>
<button
onClick={() => setActiveTab("logs")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "logs" ? "rgba(255, 255, 255, 0.05)" : "transparent",
color: activeTab === "logs" ? "var(--text-primary)" : "var(--text-secondary)",
fontWeight: activeTab === "logs" ? "600" : "500",
transition: "all 0.2s"
}}
>
<Layers size={18} />
Workflows & Logs
</button>
<div style={{ margin: "8px 0", borderTop: "1px solid rgba(255,255,255,0.1)" }}></div>
<button
onClick={() => setActiveTab("whatsapp")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "whatsapp" ? "rgba(37, 211, 102, 0.15)" : "transparent",
color: activeTab === "whatsapp" ? "#25D366" : "var(--text-secondary)",
fontWeight: activeTab === "whatsapp" ? "600" : "500",
transition: "all 0.2s"
}}
>
<MessageSquare size={18} />
WhatsApp Gateway
</button>
<button
onClick={() => setActiveTab("gmail")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "gmail" ? "rgba(234, 67, 53, 0.15)" : "transparent",
color: activeTab === "gmail" ? "#EA4335" : "var(--text-secondary)",
fontWeight: activeTab === "gmail" ? "600" : "500",
transition: "all 0.2s"
}}
>
<Mail size={18} />
Gmail Agent
</button>
<button
onClick={() => setActiveTab("config")}
style={{
display: "flex", alignItems: "center", gap: "12px", width: "100%", padding: "12px 16px", border: "none", borderRadius: "8px", cursor: "pointer",
background: activeTab === "config" ? "rgba(168, 85, 247, 0.15)" : "transparent",
color: activeTab === "config" ? "var(--accent-purple, #a855f7)" : "var(--text-secondary)",
fontWeight: activeTab === "config" ? "600" : "500",
transition: "all 0.2s"
}}
>
<Settings size={18} />
Settings & Config
</button>
</nav>
</div>
{/* Footer info */}
<div style={{ display: "flex", flexDirection: "column", gap: "8px", borderTop: "1px solid var(--border-color)", paddingTop: "16px" }}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span style={{ width: "8px", height: "8px", borderRadius: "50%" }} className="pulse-emerald"></span>
<span style={{ fontSize: "12px", color: "var(--text-secondary)" }}>Temporal Worker Connected</span>
</div>
<span style={{ fontSize: "11px", color: "var(--text-muted)" }}>v1.2.0-Production</span>
</div>
</aside>
{/* Main Content Area */}
<main className="main-content">
<header className="header">
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
<h1 style={{ fontSize: "18px", textTransform: "capitalize" }}>{activeTab.replace("curio", "Clinical Curio").replace("ops", "OpsAgent Hub")}</h1>
<div style={{ fontSize: "12px", color: "var(--text-muted)", padding: "2px 8px", background: "rgba(255,255,255,0.05)", borderRadius: "4px" }}>
Namespace: ai-agents
</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "16px" }}>
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end" }}>
<span style={{ fontSize: "12px", color: "var(--text-primary)" }}>Sysadmin Console</span>
<span style={{ fontSize: "10px", color: "var(--accent-cyan)" }}>192.168.8.250</span>
</div>
<div style={{ width: "36px", height: "36px", borderRadius: "50%", background: "#1f293d", display: "flex", alignItems: "center", justifyContent: "center" }}>
<User size={18} style={{ color: "var(--text-secondary)" }} />
</div>
</div>
</header>
<div className="content-body">
{/* TAB 1: DASHBOARD HOME */}
{activeTab === "dashboard" && (
<div className="fade-in" style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
{/* Stat Cards */}
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))", gap: "20px" }}>
<div className="glass glass-interactive" style={{ padding: "20px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "12px" }}>
<span style={{ color: "var(--text-secondary)", fontSize: "14px" }}>Total Active Agents</span>
<Cpu style={{ color: "var(--accent-violet)" }} size={20} />
</div>
<h2 style={{ fontSize: "28px" }}>5 / 8</h2>
<div style={{ fontSize: "12px", color: "var(--text-muted)", marginTop: "6px" }}>Active in namespace</div>
</div>
<div className="glass glass-interactive" style={{ padding: "20px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "12px" }}>
<span style={{ color: "var(--text-secondary)", fontSize: "14px" }}>Temporal Workflows</span>
<RefreshCw style={{ color: "var(--accent-cyan)" }} size={20} />
</div>
<h2 style={{ fontSize: "28px" }}>12 Active</h2>
<div style={{ fontSize: "12px", color: "var(--text-emerald)", marginTop: "6px" }}>98.8% success SLA</div>
</div>
<div className="glass glass-interactive" style={{ padding: "20px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "12px" }}>
<span style={{ color: "var(--text-secondary)", fontSize: "14px" }}>Diagnostic Logs</span>
<Database style={{ color: "var(--accent-emerald)" }} size={20} />
</div>
<h2 style={{ fontSize: "28px" }}>142 MB</h2>
<div style={{ fontSize: "12px", color: "var(--text-muted)", marginTop: "6px" }}>Persisted in PostgreSQL</div>
</div>
<div className="glass glass-interactive" style={{ padding: "20px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "12px" }}>
<span style={{ color: "var(--text-secondary)", fontSize: "14px" }}>Action Approvals</span>
<ShieldAlert style={{ color: "var(--accent-amber)" }} size={20} />
</div>
<h2 style={{ fontSize: "28px", color: "var(--accent-amber)" }}>{pendingApprovals.length} Awaiting</h2>
<div style={{ fontSize: "12px", color: "var(--accent-rose)", marginTop: "6px" }}>Require administrator override</div>
</div>
</div>
{/* Agent Status Panel */}
<div className="glass" style={{ padding: "24px" }}>
<h3 style={{ fontSize: "16px", marginBottom: "16px" }}>Core Agent Ecosystem Status</h3>
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
{[
{ name: "Bernard", role: "GitOps Reviewer & Workflow Monitor", status: "Idle", desc: "Awaiting PR push events on Gitea hooks.", type: "system" },
{ name: "Gumbo", role: "Documentation Summarizer", status: "Active", desc: "Indexing recent technical wiki docs via MCP filesystem.", type: "assistant" },
{ name: "OpsAgent", role: "SRE Diagnostics & Self-Healing", status: "Pending approval", desc: "Awaiting administrator decision for remediation action.", type: "remediator" },
{ name: "Claw", role: "Local Assistant CLI (OpenClaw clone)", status: "Active", desc: "Awaiting operator prompt input.", type: "assistant" },
{ name: "Curio Billing/Triage", role: "HMS AI Assist", status: "Idle", desc: "Standing by for clinical intake recording inputs.", type: "clinical" },
].map((agent, i) => (
<div key={i} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 16px", border: "1px solid var(--border-color)", borderRadius: "8px", background: "rgba(255,255,255,0.01)" }}>
<div>
<h4 style={{ fontSize: "14px", fontWeight: "600" }}>{agent.name}</h4>
<p style={{ fontSize: "12px", color: "var(--text-secondary)" }}>{agent.role} — <span style={{ color: "var(--text-muted)" }}>{agent.desc}</span></p>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span style={{ width: "8px", height: "8px", borderRadius: "50%" }} className={agent.status.includes("Pending") ? "pulse-amber" : "pulse-emerald"}></span>
<span style={{ fontSize: "12px", fontWeight: "600", color: agent.status.includes("Pending") ? "var(--accent-amber)" : "var(--text-secondary)" }}>{agent.status}</span>
</div>
</div>
))}
</div>
</div>
{/* Quick Trigger Block */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "20px" }}>
<div className="glass" style={{ padding: "20px" }}>
<h3 style={{ fontSize: "14px", marginBottom: "12px" }}>Diagnostics & Ops Triggers</h3>
<div style={{ display: "flex", gap: "10px" }}>
<button
onClick={() => {
setActiveTab("ops");
}}
style={{ padding: "10px 16px", border: "none", borderRadius: "6px", background: "linear-gradient(135deg, var(--accent-amber), #d97706)", color: "#000", fontWeight: "600", cursor: "pointer", display: "flex", alignItems: "center", gap: "8px" }}
>
<ShieldAlert size={16} />
Go to Approval Center
</button>
<button
onClick={() => {
setActiveTab("claw");
setMessages(prev => [...prev, { role: "assistant", content: "I am ready. Tell me to `check k8s pods` to test system diagnostic logic.", timestamp: "Just now" }]);
}}
style={{ padding: "10px 16px", border: "1px solid var(--border-color)", borderRadius: "6px", background: "rgba(255,255,255,0.05)", color: "var(--text-primary)", fontWeight: "600", cursor: "pointer" }}
>
Diagnose Pods
</button>
</div>
</div>
<div className="glass" style={{ padding: "20px" }}>
<h3 style={{ fontSize: "14px", marginBottom: "12px" }}>Knowledge Intake Trigger</h3>
<div style={{ display: "flex", gap: "10px" }}>
<button
onClick={() => {
alert("Triggered GumboSummarizeWorkflow for api-spec.md");
}}
style={{ padding: "10px 16px", border: "none", borderRadius: "6px", background: "linear-gradient(135deg, var(--accent-violet), var(--accent-cyan))", color: "#fff", fontWeight: "600", cursor: "pointer", display: "flex", alignItems: "center", gap: "8px" }}
>
<Sparkles size={16} />
Summarize Documentation (Gumbo)
</button>
</div>
</div>
</div>
</div>
)}
{/* TAB 2: CLAW PERSONAL ASSISTANT */}
{activeTab === "claw" && (
<div className="fade-in" style={{ display: "grid", gridTemplateColumns: "1fr 340px", gap: "24px", height: "calc(100vh - 140px)", overflow: "hidden" }}>
{/* Chat Panel */}
<div className="glass" style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
{/* Chat Header */}
<div style={{ padding: "16px 24px", borderBottom: "1px solid var(--border-color)", display: "flex", alignItems: "center", gap: "12px", background: "rgba(255,255,255,0.01)" }}>
<div style={{ width: "10px", height: "10px", borderRadius: "50%" }} className="pulse-emerald"></div>
<div>
<h3 style={{ fontSize: "15px" }}>Claw Personal Assistant</h3>
<span style={{ fontSize: "11px", color: "var(--text-muted)" }}>Runs locally. Accesses file tools & system terminal.</span>
</div>
</div>
{/* Chat Messages */}
<div style={{ flex: 1, padding: "24px", overflowY: "auto", display: "flex", flexDirection: "column", gap: "16px" }}>
{messages.map((msg, i) => (
<div
key={i}
style={{
display: "flex",
flexDirection: "column",
alignItems: msg.role === "user" ? "flex-end" : "flex-start",
maxWidth: "85%",
alignSelf: msg.role === "user" ? "flex-end" : "flex-start"
}}
>
<div
style={{
padding: "12px 16px",
borderRadius: "12px",
fontSize: "14px",
lineHeight: "1.5",
background: msg.role === "user" ? "var(--accent-cyan)" : "rgba(255, 255, 255, 0.04)",
color: msg.role === "user" ? "#000" : "var(--text-primary)",
border: msg.role === "user" ? "none" : "1px solid var(--border-color)"
}}
>
{msg.content}
</div>
<span style={{ fontSize: "10px", color: "var(--text-muted)", marginTop: "4px", padding: "0 4px" }}>{msg.timestamp}</span>
</div>
))}
{isClawTyping && (
<div style={{ alignSelf: "flex-start", display: "flex", alignItems: "center", gap: "4px", padding: "12px 16px", background: "rgba(255, 255, 255, 0.04)", borderRadius: "12px", border: "1px solid var(--border-color)" }}>
<span style={{ width: "6px", height: "6px", background: "var(--text-secondary)", borderRadius: "50%", display: "inline-block", animation: "bounce 1.4s infinite ease-in-out both" }}></span>
<span style={{ width: "6px", height: "6px", background: "var(--text-secondary)", borderRadius: "50%", display: "inline-block", animation: "bounce 1.4s infinite ease-in-out both 0.2s" }}></span>
<span style={{ width: "6px", height: "6px", background: "var(--text-secondary)", borderRadius: "50%", display: "inline-block", animation: "bounce 1.4s infinite ease-in-out both 0.4s" }}></span>
</div>
)}
{/* Weather Card */}
{weatherCard && (
<div style={{ alignSelf: "flex-start", width: "100%", maxWidth: "480px" }} className="fade-in">
<div style={{
borderRadius: "16px",
overflow: "hidden",
border: "1px solid rgba(6,182,212,0.3)",
background: "linear-gradient(135deg, rgba(6,182,212,0.08) 0%, rgba(139,92,246,0.08) 100%)",
backdropFilter: "blur(12px)"
}}>
{/* Card Header */}
<div style={{
padding: "16px 20px",
background: "linear-gradient(135deg, rgba(6,182,212,0.15), rgba(139,92,246,0.1))",
borderBottom: "1px solid rgba(6,182,212,0.2)",
display: "flex", alignItems: "center", justifyContent: "space-between"
}}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<span style={{ fontSize: "28px" }}>{getWeatherEmoji(weatherCard.current.desc)}</span>
<div>
<div style={{ fontSize: "13px", fontWeight: "600", color: "var(--accent-cyan)" }}>📍 {weatherCard.location}</div>
<div style={{ fontSize: "11px", color: "var(--text-muted)" }}>Live conditions • ZIP {weatherCard.zip}</div>
</div>
</div>
<div style={{ textAlign: "right" }}>
<div style={{ fontSize: "32px", fontWeight: "700", color: "var(--text-primary)", lineHeight: 1 }}>{weatherCard.current.tempF}°F</div>
<div style={{ fontSize: "11px", color: "var(--text-secondary)", marginTop: "2px" }}>feels like {weatherCard.current.feelsLikeF}°F</div>
</div>
</div>
{/* Condition */}
<div style={{ padding: "10px 20px", borderBottom: "1px solid rgba(255,255,255,0.05)", fontSize: "13px", color: "var(--text-secondary)" }}>
{weatherCard.current.desc}
</div>
{/* Stats Grid */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "0", padding: "0" }}>
{[
{ icon: "💧", label: "Humidity", value: `${weatherCard.current.humidity}%` },
{ icon: "💨", label: "Wind", value: `${weatherCard.current.windMph} mph ${weatherCard.current.windDir}` },
{ icon: "", label: `UV Index`, value: `${weatherCard.current.uvIndex}` },
{ icon: "👁", label: "Visibility", value: `${weatherCard.current.visibility} mi` },
{ icon: "🔺", label: "High", value: `${weatherCard.today.maxF}°F` },
{ icon: "🔻", label: "Low", value: `${weatherCard.today.minF}°F` },
].map((stat, i) => (
<div key={i} style={{
padding: "12px 16px",
borderRight: i % 3 !== 2 ? "1px solid rgba(255,255,255,0.05)" : "none",
borderBottom: i < 3 ? "1px solid rgba(255,255,255,0.05)" : "none"
}}>
<div style={{ fontSize: "16px", marginBottom: "2px" }}>{stat.icon}</div>
<div style={{ fontSize: "13px", fontWeight: "600", color: "var(--text-primary)" }}>{stat.value}</div>
<div style={{ fontSize: "10px", color: "var(--text-muted)" }}>{stat.label}</div>
</div>
))}
</div>
{/* Sunrise/Sunset & Tomorrow */}
<div style={{ padding: "12px 20px", borderTop: "1px solid rgba(255,255,255,0.05)", display: "flex", justifyContent: "space-between", fontSize: "12px", color: "var(--text-secondary)" }}>
<span>🌅 {weatherCard.today.sunrise}</span>
<span>🌇 {weatherCard.today.sunset}</span>
{weatherCard.tomorrow && (
<span>{getWeatherEmoji(weatherCard.tomorrow.desc)} Tomorrow: {weatherCard.tomorrow.minF}{weatherCard.tomorrow.maxF}°F</span>
)}
</div>
{/* Send to WhatsApp CTA */}
<div style={{ padding: "14px 20px", borderTop: "1px solid rgba(6,182,212,0.2)", display: "flex", gap: "10px", alignItems: "center" }}>
<button
id="send-weather-whatsapp"
onClick={handleSendWeatherToWhatsApp}
disabled={weatherSending || weatherSent}
style={{
flex: 1,
padding: "10px 16px",
border: "none",
borderRadius: "8px",
background: weatherSent
? "linear-gradient(135deg, #10b981, #059669)"
: "linear-gradient(135deg, #25D366, #128C7E)",
color: "#fff",
fontWeight: "600",
cursor: weatherSending || weatherSent ? "default" : "pointer",
display: "flex", alignItems: "center", justifyContent: "center", gap: "8px",
fontSize: "13px",
opacity: weatherSending ? 0.7 : 1,
transition: "all 0.3s"
}}
>
{weatherSent ? " Sent to WhatsApp!" : weatherSending ? " Sending..." : "📱 Send to WhatsApp Group"}
</button>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Chat Input */}
<form onSubmit={handleSendMessage} style={{ padding: "20px", borderTop: "1px solid var(--border-color)", display: "flex", gap: "10px", background: "rgba(8,10,16,0.3)" }}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="e.g. 'weather', 'whatsapp me: call doctor at 3pm', 'check k8s pods'..."
style={{ flex: 1, padding: "12px 16px", border: "1px solid var(--border-color)", borderRadius: "8px", background: "var(--bg-secondary)", color: "var(--text-primary)", outline: "none", fontSize: "14px" }}
/>
<button
type="submit"
style={{ padding: "12px 20px", border: "none", borderRadius: "8px", background: "var(--accent-cyan)", color: "#000", fontWeight: "600", cursor: "pointer", display: "flex", alignItems: "center", gap: "8px" }}
>
<Send size={16} />
Send
</button>
</form>
</div>
{/* Claw Tool & Console Log Panel */}
<div className="glass" style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden", borderLeft: "1px solid var(--border-color)" }}>
{/* Terminal Console */}
<div style={{ padding: "16px 20px", borderBottom: "1px solid var(--border-color)", background: "rgba(255,255,255,0.01)" }}>
<h3 style={{ fontSize: "14px", display: "flex", alignItems: "center", gap: "8px" }}>
<Terminal size={16} style={{ color: "var(--accent-cyan)" }} />
Claw Tool Console
</h3>
</div>
<div style={{ flex: 1, minHeight: 0, padding: "16px", background: "#05070a", fontFamily: "var(--font-mono)", fontSize: "11px", color: "#a5f3fc", overflowY: "auto", display: "flex", flexDirection: "column", gap: "8px" }}>
{clawTerminalLogs.length === 0 ? (
<div style={{ color: "var(--text-muted)", fontStyle: "italic" }}>Console logs will stream here as Claw calls CLI and API tools...</div>
) : (
clawTerminalLogs.map((log, i) => (
<div key={i} style={{ whiteSpace: "pre-wrap", wordBreak: "break-all" }}>
<span style={{ color: "var(--text-muted)" }}>&gt;</span> {log}
</div>
))
)}
<div ref={terminalEndRef} />
</div>
{/* Scheduled Reminders Panel */}
<div style={{ borderTop: "1px solid var(--border-color)", display: "flex", flexDirection: "column", maxHeight: "300px" }}>
<div style={{ padding: "12px 16px", borderBottom: "1px solid var(--border-color)", display: "flex", alignItems: "center", justifyContent: "space-between", background: "rgba(139,92,246,0.05)" }}>
<h3 style={{ fontSize: "13px", display: "flex", alignItems: "center", gap: "8px", color: "var(--text-primary)" }}>
<span>⏰</span>
Scheduled Reminders
{scheduledReminders.length > 0 && (
<span style={{ fontSize: "10px", padding: "1px 6px", borderRadius: "10px", background: "rgba(139,92,246,0.3)", color: "#c4b5fd", fontWeight: "700" }}>
{scheduledReminders.length}
</span>
)}
</h3>
<span style={{ fontSize: "10px", color: "var(--text-muted)" }}>auto-refreshes</span>
</div>
<div style={{ overflowY: "auto", flex: 1 }}>
{scheduledReminders.length === 0 ? (
<div style={{ padding: "20px 16px", textAlign: "center", color: "var(--text-muted)", fontSize: "12px" }}>
No reminders scheduled.<br />
<span style={{ color: "var(--accent-violet)", fontSize: "11px" }}>Try: "remind me at 3pm: call doctor"</span>
</div>
) : (
scheduledReminders.map((r) => (
<div key={r.id} style={{
display: "flex", alignItems: "flex-start", justifyContent: "space-between",
padding: "10px 14px",
borderBottom: "1px solid rgba(255,255,255,0.04)",
gap: "8px"
}}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: "12px", fontWeight: "600", color: "var(--text-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
{r.label}
</div>
<div style={{ fontSize: "10px", color: "var(--accent-violet)", marginTop: "2px" }}>
🕐 {formatReminderTime(r.sendAt)} ET
</div>
</div>
<button
onClick={() => handleCancelReminder(r.id)}
title="Cancel reminder"
style={{
flexShrink: 0,
padding: "3px 8px",
border: "1px solid rgba(239,68,68,0.3)",
borderRadius: "4px",
background: "rgba(239,68,68,0.1)",
color: "#f87171",
fontSize: "10px",
cursor: "pointer"
}}
>
</button>
</div>
))
)}
</div>
</div>
</div>
</div>
)}
{/* TAB 3: OPSAGENT HUB */}
{activeTab === "ops" && (
<div className="fade-in" style={{ display: "grid", gridTemplateColumns: "1fr 400px", gap: "24px", height: "calc(100vh - 140px)" }}>
{/* Left Column: Diagnostics and Approvals */}
<div style={{ display: "flex", flexDirection: "column", gap: "20px", overflowY: "auto" }}>
{/* Pending Actions */}
<div className="glass" style={{ padding: "24px" }}>
<h2 style={{ fontSize: "16px", marginBottom: "16px", display: "flex", alignItems: "center", gap: "8px" }}>
<ShieldAlert size={20} style={{ color: "var(--accent-amber)" }} />
Awaiting System Approvals (Approval Gate)
</h2>
{pendingApprovals.length === 0 ? (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "40px", gap: "12px", border: "1px dashed var(--border-color)", borderRadius: "8px" }}>
<CheckCircle size={32} style={{ color: "var(--accent-emerald)" }} />
<h4 style={{ color: "var(--text-primary)" }}>No approvals pending</h4>
<p style={{ fontSize: "12px", color: "var(--text-muted)" }}>All systems are running clean and within normal parameters.</p>
</div>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
{pendingApprovals.map((req) => (
<div key={req.id} style={{ border: "1px solid var(--border-color)", borderRadius: "8px", background: "rgba(255,255,255,0.02)", overflow: "hidden" }}>
<div style={{ padding: "16px", borderBottom: "1px solid var(--border-color)", display: "flex", justifyContent: "space-between", alignItems: "center", background: "rgba(245,158,11,0.03)" }}>
<div>
<span style={{ fontSize: "11px", fontWeight: "700", textTransform: "uppercase", background: "rgba(245,158,11,0.15)", color: "var(--accent-amber)", padding: "2px 6px", borderRadius: "4px", marginRight: "8px" }}>
{req.action.replace("_", " ")}
</span>
<span style={{ fontSize: "13px", color: "var(--text-primary)", fontWeight: "600" }}>Target: {req.targetType.toUpperCase()} ({req.targetId})</span>
</div>
<span style={{ fontSize: "11px", color: "var(--text-muted)" }}>{req.timestamp}</span>
</div>
<div style={{ padding: "16px" }}>
<p style={{ fontSize: "13px", color: "var(--text-secondary)", lineHeight: "1.5", marginBottom: "16px" }}>{req.details}</p>
<div style={{ display: "flex", gap: "10px", justifyContent: "flex-end" }}>
<button
onClick={() => handleReject(req.id)}
style={{ padding: "8px 16px", border: "1px solid var(--accent-rose)", borderRadius: "6px", background: "transparent", color: "var(--accent-rose)", fontWeight: "600", cursor: "pointer", display: "flex", alignItems: "center", gap: "6px" }}
>
<XCircle size={14} />
Reject
</button>
<button
onClick={() => handleApprove(req.id, req.action, req.targetId)}
style={{ padding: "8px 16px", border: "none", borderRadius: "6px", background: "linear-gradient(135deg, var(--accent-emerald), #059669)", color: "#fff", fontWeight: "600", cursor: "pointer", display: "flex", alignItems: "center", gap: "6px" }}
>
<CheckCircle size={14} />
Approve & Execute
</button>
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* Diagnostics Panel */}
<div className="glass" style={{ padding: "24px" }}>
<h3 style={{ fontSize: "15px", marginBottom: "12px" }}>OpsAgent Diagnostics Tools</h3>
<p style={{ fontSize: "13px", color: "var(--text-secondary)", marginBottom: "16px" }}>Manually run automated checks across your container nodes.</p>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px" }}>
<div style={{ padding: "12px", border: "1px solid var(--border-color)", borderRadius: "8px", background: "rgba(255,255,255,0.01)" }}>
<span style={{ fontSize: "12px", fontWeight: "600" }}>K8s Diagnostic</span>
<button
onClick={() => {
setOpsLogs(prev => [...prev, `[Manual Trigger] Initiated Kubernetes diagnostics check for namespace 'default'...`]);
setTimeout(() => {
setOpsLogs(prev => [...prev, `[OpsAgent] web-pod-xyz logs scanned. DB connection failed.`]);
}, 1000);
}}
style={{ width: "100%", padding: "8px", border: "none", borderRadius: "4px", background: "rgba(6, 182, 212, 0.15)", color: "var(--accent-cyan)", fontWeight: "600", fontSize: "11px", cursor: "pointer", marginTop: "8px" }}
>
Scan Namespace
</button>
</div>
<div style={{ padding: "12px", border: "1px solid var(--border-color)", borderRadius: "8px", background: "rgba(255,255,255,0.01)" }}>
<span style={{ fontSize: "12px", fontWeight: "600" }}>Proxmox Auditor</span>
<button
onClick={() => {
setOpsLogs(prev => [...prev, `[Manual Trigger] Querying Proxmox host 192.168.8.225 for LXC disk metrics...`]);
setTimeout(() => {
setOpsLogs(prev => [...prev, `[OpsAgent] VMID 102 checked: 94% storage load. VMID 105 checked: 52% load.`]);
}, 1000);
}}
style={{ width: "100%", padding: "8px", border: "none", borderRadius: "4px", background: "rgba(139, 92, 246, 0.15)", color: "var(--accent-violet)", fontWeight: "600", fontSize: "11px", cursor: "pointer", marginTop: "8px" }}
>
Audit Disk Usage
</button>
</div>
</div>
</div>
</div>
{/* Right Column: Execution Live Terminal */}
<div className="glass" style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
<div style={{ padding: "16px 20px", borderBottom: "1px solid var(--border-color)", background: "rgba(255,255,255,0.01)" }}>
<h3 style={{ fontSize: "14px" }}>OpsAgent Execution Terminal Logs</h3>
</div>
<div style={{ flex: 1, padding: "16px", background: "#05070a", fontFamily: "var(--font-mono)", fontSize: "11px", color: "#a3e635", overflowY: "auto", display: "flex", flexDirection: "column", gap: "8px" }}>
{opsLogs.map((log, i) => (
<div key={i} style={{ lineBreak: "anywhere" }}>{log}</div>
))}
</div>
</div>
</div>
)}
{/* TAB 4: CLINICAL WORKSPACE */}
{activeTab === "curio" && (
<div className="fade-in" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "24px", height: "calc(100vh - 140px)" }}>
{/* Doctor Dictation Section */}
<div className="glass" style={{ padding: "24px", display: "flex", flexDirection: "column", gap: "16px", height: "100%", overflowY: "auto" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<HeartPulse style={{ color: "var(--accent-emerald)" }} size={24} />
<div>
<h2 style={{ fontSize: "16px" }}>Clinical Scribe Workspace</h2>
<span style={{ fontSize: "11px", color: "var(--text-muted)" }}>Uses ClinicalIntakeWorkflow (Temporal Task Queue)</span>
</div>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<label style={{ fontSize: "12px", color: "var(--text-secondary)", fontWeight: "600" }}>Raw Dictation Input</label>
<textarea
value={dictationText}
onChange={(e) => setDictationText(e.target.value)}
style={{ width: "100%", height: "200px", padding: "16px", border: "1px solid var(--border-color)", borderRadius: "8px", background: "var(--bg-secondary)", color: "var(--text-primary)", outline: "none", resize: "none", fontSize: "14px", lineHeight: "1.5" }}
/>
</div>
<button
onClick={handleProcessClinical}
disabled={isProcessingClinical || !dictationText}
style={{ width: "100%", padding: "12px", border: "none", borderRadius: "8px", background: "var(--accent-emerald)", color: "#000", fontWeight: "600", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px" }}
>
{isProcessingClinical ? (
<>
<RefreshCw size={16} className="spin" style={{ animation: "spin 2s linear infinite" }} />
Structuring Dictation via LLM...
</>
) : (
<>
<Play size={16} />
Submit to ClinicalIntake Workflow
</>
)}
</button>
</div>
{/* Structuring Result Output */}
<div className="glass" style={{ padding: "24px", display: "flex", flexDirection: "column", gap: "16px", height: "100%", overflowY: "auto" }}>
<h3 style={{ fontSize: "15px", borderBottom: "1px solid var(--border-color)", paddingBottom: "12px" }}>Structured Output (SOAP Note)</h3>
{clinicalNote ? (
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr", gap: "12px" }}>
<div>
<span style={{ fontSize: "11px", fontWeight: "700", color: "var(--accent-emerald)" }}>SUBJECTIVE</span>
<p style={{ fontSize: "13px", color: "var(--text-secondary)", marginTop: "4px" }}>{clinicalNote.subjective}</p>
</div>
<div>
<span style={{ fontSize: "11px", fontWeight: "700", color: "var(--accent-emerald)" }}>OBJECTIVE</span>
<p style={{ fontSize: "13px", color: "var(--text-secondary)", marginTop: "4px" }}>{clinicalNote.objective}</p>
</div>
<div>
<span style={{ fontSize: "11px", fontWeight: "700", color: "var(--accent-emerald)" }}>ASSESSMENT</span>
<p style={{ fontSize: "13px", color: "var(--text-secondary)", marginTop: "4px" }}>{clinicalNote.assessment}</p>
</div>
<div>
<span style={{ fontSize: "11px", fontWeight: "700", color: "var(--accent-emerald)" }}>PLAN</span>
<p style={{ fontSize: "13px", color: "var(--text-secondary)", marginTop: "4px" }}>{clinicalNote.plan}</p>
</div>
</div>
<div style={{ borderTop: "1px solid var(--border-color)", paddingTop: "16px" }}>
<h4 style={{ fontSize: "12px", color: "var(--text-primary)", marginBottom: "8px" }}>Generated Billing Codes (ICD-10 / CPT)</h4>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "12px" }}>
<thead>
<tr style={{ textAlign: "left", color: "var(--text-muted)", borderBottom: "1px solid var(--border-color)" }}>
<th style={{ padding: "8px" }}>Code</th>
<th style={{ padding: "8px" }}>Description</th>
</tr>
</thead>
<tbody>
{billingCodes.map((bc, idx) => (
<tr key={idx} style={{ borderBottom: "1px solid rgba(255,255,255,0.02)" }}>
<td style={{ padding: "8px", fontWeight: "600", color: "var(--accent-cyan)" }}>{bc.code}</td>
<td style={{ padding: "8px", color: "var(--text-secondary)" }}>{bc.desc}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
) : (
<div style={{ display: "flex", flex: 1, alignItems: "center", justifyContent: "center", color: "var(--text-muted)", fontSize: "13px", fontStyle: "italic", border: "1px dashed var(--border-color)", borderRadius: "8px" }}>
No workflow submitted yet. Enter dictation and click submit.
</div>
)}
</div>
</div>
)}
{/* TAB: CONFIG */}
{activeTab === "config" && (
<div className="fade-in" style={{ display: "flex", flexDirection: "column", gap: "24px", maxWidth: "800px" }}>
<div className="glass" style={{ padding: "24px" }}>
<div style={{ display: "flex", alignItems: "center", gap: "12px", marginBottom: "20px" }}>
<Settings size={24} style={{ color: "var(--accent-purple, #a855f7)" }} />
<h2 style={{ fontSize: "20px", fontWeight: "600" }}>WhatsApp Routing Configuration</h2>
</div>
<p style={{ color: "var(--text-secondary)", fontSize: "14px", marginBottom: "24px", lineHeight: "1.6" }}>
Manage the WhatsApp group invite links used by the various Agentic OS microservices.
Changes here are written directly to the Kubernetes <code>whatsapp-config.yaml</code> ConfigMap.
</p>
<div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
{/* Default Group */}
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<label style={{ fontSize: "14px", fontWeight: "600", color: "var(--text-primary)" }}>Default Group URL</label>
<input
type="text"
value={configData.defaultGroupUrl}
onChange={(e) => setConfigData({...configData, defaultGroupUrl: e.target.value})}
placeholder="https://chat.whatsapp.com/..."
style={{
width: "100%", padding: "12px", borderRadius: "8px",
background: "rgba(0,0,0,0.2)", border: "1px solid var(--border-color)",
color: "var(--text-primary)", fontSize: "14px", fontFamily: "monospace"
}}
/>
<span style={{ fontSize: "12px", color: "var(--text-muted)" }}>
Used by <strong>Reminder Service</strong>, <strong>Weather CronJob</strong>, and general notifications.
</span>
</div>
{/* Trip Group */}
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<label style={{ fontSize: "14px", fontWeight: "600", color: "var(--text-primary)" }}>Trip & Briefings Group URL</label>
<input
type="text"
value={configData.tripGroupUrl}
onChange={(e) => setConfigData({...configData, tripGroupUrl: e.target.value})}
placeholder="https://chat.whatsapp.com/..."
style={{
width: "100%", padding: "12px", borderRadius: "8px",
background: "rgba(0,0,0,0.2)", border: "1px solid var(--border-color)",
color: "var(--text-primary)", fontSize: "14px", fontFamily: "monospace"
}}
/>
<span style={{ fontSize: "12px", color: "var(--text-muted)" }}>
Used exclusively by the <strong>Trip Service</strong> to isolate travel insights.
</span>
</div>
{/* Save Button */}
<div style={{ marginTop: "16px", display: "flex", justifyContent: "flex-end" }}>
<button
onClick={handleSaveConfig}
disabled={isSavingConfig}
style={{
padding: "10px 24px", borderRadius: "8px", border: "none",
background: "var(--accent-purple, #a855f7)", color: "#fff",
fontWeight: "600", cursor: isSavingConfig ? "not-allowed" : "pointer",
opacity: isSavingConfig ? 0.7 : 1, transition: "opacity 0.2s"
}}
>
{isSavingConfig ? "Saving to K8s..." : "Save & Apply"}
</button>
</div>
</div>
</div>
</div>
)}
{/* TAB 5: WORKFLOW MONITOR */}
{activeTab === "logs" && (
<div className="fade-in" style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
<div className="glass" style={{ padding: "24px" }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }}>
<div>
<h3 style={{ fontSize: "16px" }}>Durable Execution Logs</h3>
<p style={{ fontSize: "12px", color: "var(--text-muted)" }}>Temporal workflows audit records inside the local postgres cluster.</p>
</div>
<button
onClick={() => {
setWorkflows(prev => [
...prev,
{ id: `wf-manual-${Math.floor(Math.random()*900+100)}`, name: "GumboSummarizeWorkflow", agent: "Gumbo", target: "wiki-summary.txt", status: "completed", result: "Completed" }
]);
}}
style={{ padding: "8px 14px", border: "none", borderRadius: "6px", background: "rgba(255,255,255,0.05)", color: "var(--text-primary)", fontWeight: "600", cursor: "pointer", fontSize: "12px" }}
>
Refresh List
</button>
</div>
<div style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "13px" }}>
<thead>
<tr style={{ borderBottom: "1px solid var(--border-color)", color: "var(--text-muted)", textAlign: "left" }}>
<th style={{ padding: "12px 16px" }}>Workflow ID</th>
<th style={{ padding: "12px 16px" }}>Workflow Name</th>
<th style={{ padding: "12px 16px" }}>Assigned Agent</th>
<th style={{ padding: "12px 16px" }}>Target</th>
<th style={{ padding: "12px 16px" }}>Status</th>
<th style={{ padding: "12px 16px" }}>Result</th>
</tr>
</thead>
<tbody>
{workflows.map((wf) => (
<tr key={wf.id} style={{ borderBottom: "1px solid var(--border-color)", background: "rgba(255,255,255,0.005)" }}>
<td style={{ padding: "12px 16px", fontFamily: "var(--font-mono)", fontSize: "12px" }}>{wf.id}</td>
<td style={{ padding: "12px 16px", fontWeight: "600" }}>{wf.name}</td>
<td style={{ padding: "12px 16px" }}>{wf.agent}</td>
<td style={{ padding: "12px 16px", color: "var(--text-secondary)" }}>{wf.target}</td>
<td style={{ padding: "12px 16px" }}>
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
<span style={{ width: "6px", height: "6px", borderRadius: "50%" }} className={wf.status === "running" ? "pulse-amber" : "pulse-emerald"}></span>
<span style={{ textTransform: "capitalize", fontWeight: "600", color: wf.status === "running" ? "var(--accent-amber)" : "var(--text-secondary)" }}>
{wf.status}
</span>
</div>
</td>
<td style={{ padding: "12px 16px", color: wf.status === "running" ? "var(--text-muted)" : "var(--text-primary)" }}>{wf.result}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
{/* TAB 6: TRIPS & CONFIG */}
{activeTab === "trips" && (
<div className="fade-in" style={{ display: "grid", gridTemplateColumns: "300px 1fr", gap: "24px", height: "calc(100vh - 140px)" }}>
{/* Trip List Panel */}
<div className="glass" style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
<div style={{ padding: "16px 20px", borderBottom: "1px solid var(--border-color)", background: "rgba(255,255,255,0.01)" }}>
<h3 style={{ fontSize: "14px", display: "flex", alignItems: "center", gap: "8px" }}>
<Compass size={16} style={{ color: "var(--accent-pink)" }} />
Managed Trips
</h3>
</div>
<div style={{ flex: 1, overflowY: "auto", padding: "16px", display: "flex", flexDirection: "column", gap: "12px" }}>
<button
onClick={() => {
setSelectedTripId(null);
setTripEditorJson("{\n \"name\": \"New Trip\",\n \"briefingTime\": \"07:00\",\n \"sendToGroup\": true,\n \"groupUrls\": [],\n \"llmModel\": \"qwen2.5:0.5b\",\n \"days\": []\n}");
}}
style={{ padding: "12px", border: "1px dashed var(--border-color)", borderRadius: "8px", background: "rgba(255,255,255,0.02)", color: "var(--text-primary)", cursor: "pointer", fontSize: "13px" }}
>
+ Add New Trip
</button>
{tripsList.map(t => (
<div
key={t.id}
onClick={() => {
setSelectedTripId(t.id);
setTripEditorJson(JSON.stringify(t, null, 2));
}}
style={{ padding: "12px", border: `1px solid ${selectedTripId === t.id ? 'var(--accent-pink)' : 'var(--border-color)'}`, borderRadius: "8px", background: selectedTripId === t.id ? "rgba(236,72,153,0.05)" : "var(--bg-secondary)", cursor: "pointer" }}
>
<h4 style={{ fontSize: "14px", fontWeight: "600", marginBottom: "4px" }}>{t.name}</h4>
<div style={{ fontSize: "11px", color: "var(--text-muted)", display: "flex", justifyContent: "space-between" }}>
<span> {t.briefingTime} ET</span>
<span>{t.days?.length} Days</span>
</div>
</div>
))}
</div>
</div>
{/* Editor Panel */}
<div className="glass" style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
<div style={{ padding: "16px 20px", borderBottom: "1px solid var(--border-color)", background: "rgba(255,255,255,0.01)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<h3 style={{ fontSize: "14px" }}>{selectedTripId ? "Edit Trip Configuration" : "New Trip Configuration"}</h3>
<div style={{ display: "flex", gap: "8px" }}>
{selectedTripId && (
<button
onClick={() => handleTestTrip(selectedTripId)}
disabled={isTestingTrip}
style={{ padding: "8px 14px", border: "1px solid var(--border-color)", borderRadius: "6px", background: "rgba(255,255,255,0.05)", color: "var(--text-primary)", fontSize: "12px", cursor: "pointer" }}
>
{isTestingTrip ? "Firing..." : "Test Briefing"}
</button>
)}
<button
onClick={handleSaveTrip}
disabled={isSavingTrip}
style={{ padding: "8px 14px", border: "none", borderRadius: "6px", background: "linear-gradient(135deg, #ec4899, #be185d)", color: "#fff", fontWeight: "600", fontSize: "12px", cursor: "pointer" }}
>
{isSavingTrip ? "Saving..." : "Save Trip Config"}
</button>
</div>
</div>
<div style={{ flex: 1, display: "flex", flexDirection: "column", padding: "16px", background: "#05070a" }}>
<textarea
value={tripEditorJson}
onChange={(e) => setTripEditorJson(e.target.value)}
placeholder="Paste JSON config here..."
style={{ flex: 1, width: "100%", padding: "16px", border: "none", background: "transparent", color: "#a5f3fc", fontFamily: "var(--font-mono)", fontSize: "12px", resize: "none", outline: "none" }}
spellCheck={false}
/>
</div>
</div>
</div>
)}
{/* TAB: WHATSAPP */}
{activeTab === "whatsapp" && <WhatsAppAgentUI />}
{/* TAB: GMAIL */}
{activeTab === "gmail" && <GmailAgentUI />}
</div>
</main>
</div>
);
}