tahamajs/IE / Projects /front /src /components /tools /ToolExecutionWindow.tsx
tahamajs's picture
download
raw
5.82 kB
import { useState, useRef, useEffect } from "react";
import { X, Minus, Square } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import type { Tool } from "@/types/tool";
import api from "@/lib/api";
import { toast } from "react-toastify";
interface ToolExecutionWindowProps {
tool: Tool;
onClose: () => void;
}
export function ToolExecutionWindow({ tool, onClose }: ToolExecutionWindowProps) {
const [isMaximized, setIsMaximized] = useState(false);
const [isMinimized, setIsMinimized] = useState(false);
const windowRef = useRef<HTMLDivElement>(null);
// Listen to tool sandbox notifications
useEffect(() => {
const handleMessage = async (e: MessageEvent) => {
if (e.data && e.data.type === "SEND_NOTIFICATION") {
const { title, body, priority } = e.data.payload || {};
try {
await api.post("/notifications", {
title: title || `${tool.name} Alert`,
body: body || "",
priority: priority || "medium",
type: "system",
source: tool.name,
});
toast.success(`Notification sent from ${tool.name}`);
} catch (err) {
console.error("Failed to send notification from custom tool:", err);
toast.error("Failed to dispatch application notification");
}
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [tool]);
const injectedCode = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { margin: 0; padding: 16px; font-family: system-ui, -apple-system, sans-serif; }
.tool-container { max-width: 100%; overflow-x: auto; }
</style>
</head>
<body>
<script>
window.sendNotification = function(title, body, priority = 'medium') {
window.parent.postMessage({
type: "SEND_NOTIFICATION",
payload: { title, body, priority }
}, "*");
};
</script>
${tool.code}
</body>
</html>
`;
if (isMinimized) {
return (
<div className="fixed bottom-4 left-4 z-50">
<button
onClick={() => setIsMinimized(false)}
className="flex items-center gap-3 premium-glass px-4 py-2 rounded-full border border-white/20 shadow-xl hover:bg-white/10"
>
<span className="text-xl">{tool.icon || "🔧"}</span>
<span className="font-bold text-sm text-white">{tool.name}</span>
</button>
</div>
);
}
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className={`fixed z-50 flex flex-col overflow-hidden shadow-2xl premium-glass border border-white/20 dark:border-white/5 backdrop-blur-xl ${
isMaximized
? "inset-0 rounded-none"
: "top-[10%] left-[10%] right-[10%] bottom-[10%] rounded-[1.5rem]"
}`}
ref={windowRef}
>
{/* macOS-style Window Title Bar */}
<div
className="flex items-center justify-between px-4 py-3 bg-gradient-to-b from-slate-100 to-slate-200 dark:from-slate-800 dark:to-slate-900 border-b border-slate-300 dark:border-slate-700/50 select-none"
>
<div className="flex flex-1 items-center gap-2">
{/* Traffic Lights */}
<div className="flex items-center gap-1.5 group">
<button
onClick={onClose}
className="w-3.5 h-3.5 rounded-full bg-rose-500 flex items-center justify-center hover:bg-rose-600 transition"
>
<X className="w-2.5 h-2.5 text-black/50 opacity-0 group-hover:opacity-100" />
</button>
<button
onClick={() => setIsMinimized(true)}
className="w-3.5 h-3.5 rounded-full bg-amber-500 flex items-center justify-center hover:bg-amber-600 transition"
>
<Minus className="w-2.5 h-2.5 text-black/50 opacity-0 group-hover:opacity-100" />
</button>
<button
onClick={() => setIsMaximized(!isMaximized)}
className="w-3.5 h-3.5 rounded-full bg-emerald-500 flex items-center justify-center hover:bg-emerald-600 transition"
>
<Square className="w-2.5 h-2.5 text-black/50 opacity-0 group-hover:opacity-100" />
</button>
</div>
</div>
<div className="flex-1 text-center flex items-center justify-center gap-2">
<span className="text-sm">{tool.icon || "🔧"}</span>
<span className="font-semibold text-xs text-slate-800 dark:text-slate-200 tracking-wide">{tool.name}</span>
</div>
<div className="flex-1" />
</div>
{/* Window Content */}
<div className="flex-1 bg-white relative">
<iframe
srcDoc={injectedCode}
title={tool.name}
className="absolute inset-0 w-full h-full border-none"
sandbox="allow-scripts allow-modals"
/>
</div>
</motion.div>
{/* Dimmed Background Overlay */}
{!isMaximized && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-40 bg-slate-950/20 backdrop-blur-sm pointer-events-none"
/>
)}
</AnimatePresence>
);
}

Xet Storage Details

Size:
5.82 kB
·
Xet hash:
b60dba5d229c7a5c60df032b94be0ae6a263e4cd5642f60cf8214ae9e4b63371

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.