812 lines
46 KiB
JavaScript
812 lines
46 KiB
JavaScript
"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
|
|
} from "lucide-react";
|
|
|
|
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. I have access to your Kubernetes namespace and Proxmox cluster nodes. How can I help you today?",
|
|
timestamp: "Just now"
|
|
}
|
|
]);
|
|
const [inputValue, setInputValue] = useState("");
|
|
const [isClawTyping, setIsClawTyping] = useState(false);
|
|
const [clawTerminalLogs, setClawTerminalLogs] = 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" }
|
|
]);
|
|
|
|
const messagesEndRef = useRef(null);
|
|
const terminalEndRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [messages, isClawTyping]);
|
|
|
|
useEffect(() => {
|
|
terminalEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [clawTerminalLogs]);
|
|
|
|
// 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);
|
|
setClawTerminalLogs(prev => [...prev, `[Claw] Received instruction: "${userText}"`]);
|
|
|
|
// Parse commands for visual interactions
|
|
let responseText = "";
|
|
let logs = [];
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
|
|
const cleanInput = userText.toLowerCase();
|
|
if (cleanInput.includes("pod") || cleanInput.includes("k8s") || cleanInput.includes("kubectl")) {
|
|
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")) {
|
|
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")) {
|
|
responseText = "Hi there! I am Claw, your local personal assistant. Try asking me to `check k8s pods` or `diagnose proxmox container 102`.";
|
|
} else {
|
|
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);
|
|
};
|
|
|
|
// Approval Handlers
|
|
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);
|
|
};
|
|
|
|
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("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>
|
|
</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>
|
|
)}
|
|
<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="Ask Claw: e.g. 'check pods' or 'check disk VMID 102'..."
|
|
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)" }}>
|
|
<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, 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)" }}>></span> {log}
|
|
</div>
|
|
))
|
|
)}
|
|
<div ref={terminalEndRef} />
|
|
</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 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>
|
|
)}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|