AIBuilder / src /App.tsx
Sk0lovek's picture
Update src/App.tsx
5b1060f verified
Raw
History Blame Contribute Delete
18.6 kB
import React, { useState, useEffect, useRef } from "react";
import { io, Socket } from "socket.io-client";
import {
Terminal,
Settings,
Play,
Square,
Cpu,
Globe,
User,
Key,
MessageSquare,
Activity
} from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
interface LogEntry {
message: string;
type: "info" | "success" | "error" | "chat";
timestamp: string;
}
export default function App() {
const [socket, setSocket] = useState<Socket | null>(null);
const [config, setConfig] = useState(() => {
const saved = localStorage.getItem("ai_architect_config");
if (saved) {
try {
return JSON.parse(saved);
} catch (e) {}
}
return {
host: "localhost",
port: "25565",
version: "",
username: "AI_Architect",
apiKey: "",
model: "gemini-3-flash-preview",
apiType: "google",
apiBaseUrl: "https://api.openai.com/v1",
customModel: "gpt-4o",
maxBlocks: 10000,
edition: "java",
auth: "offline"
};
});
useEffect(() => {
localStorage.setItem("ai_architect_config", JSON.stringify(config));
}, [config]);
const [status, setStatus] = useState({ connected: false, username: "" });
const [logs, setLogs] = useState<LogEntry[]>([]);
const [msaAuth, setMsaAuth] = useState<{code: string, uri: string} | null>(null);
const logEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const newSocket = io();
setSocket(newSocket);
newSocket.on("bot-log", (log: LogEntry) => {
setLogs((prev) => [...prev.slice(-100), log]);
});
newSocket.on("bot-status", (status) => {
setStatus(status);
if (status.connected) {
setMsaAuth(null);
}
});
newSocket.on("bot-msa-code", (data) => {
setMsaAuth(data);
});
return () => {
newSocket.disconnect();
};
}, []);
useEffect(() => {
logEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [logs]);
const handleConnect = () => {
if (!socket) return;
if (!config.apiKey) {
alert("Please enter a Gemini API Key");
return;
}
socket.emit("connect-bot", config);
};
const handleDisconnect = () => {
if (!socket) return;
socket.emit("disconnect-bot");
};
return (
<div className="min-h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono p-4 md:p-8 flex flex-col gap-6">
{/* Header */}
<header className="flex items-center justify-between border-b border-[#333] pb-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-[#1a1a1a] border border-[#333] rounded">
<Cpu className="w-6 h-6 text-[#00ff00]" />
</div>
<div>
<h1 className="text-xl font-bold tracking-tighter uppercase">Minecraft AI Architect</h1>
<p className="text-xs text-[#666]">v1.0.0 // SPIGOT 1.21.1 INTEGRATION</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 px-3 py-1 bg-[#1a1a1a] border border-[#333] rounded-full">
<div className={`w-2 h-2 rounded-full ${status.connected ? "bg-[#00ff00] animate-pulse" : "bg-[#ff0000]"}`} />
<span className="text-[10px] uppercase tracking-widest">
{status.connected ? `ONLINE: ${status.username}` : "OFFLINE"}
</span>
</div>
</div>
</header>
<main className="grid grid-cols-1 lg:grid-cols-12 gap-6 flex-grow">
{/* Configuration Panel */}
<section className="lg:col-span-4 flex flex-col gap-4">
<div className="bg-[#111] border border-[#333] rounded-lg p-5 flex flex-col gap-4">
<div className="flex items-center gap-2 mb-2">
<Settings className="w-4 h-4 text-[#666]" />
<h2 className="text-xs uppercase tracking-widest font-bold">Bot Configuration</h2>
</div>
<div className="space-y-4">
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Server Host</label>
<div className="relative">
<Globe className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-[#444]" />
<input
type="text"
value={config.host}
onChange={(e) => setConfig({...config, host: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 pl-9 text-sm focus:border-[#00ff00] outline-none transition-colors"
placeholder="localhost"
/>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Port</label>
<input
type="text"
value={config.port}
onChange={(e) => setConfig({...config, port: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
placeholder="25565"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Edition</label>
<select
value={config.edition || "java"}
onChange={(e) => setConfig({...config, edition: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none appearance-none cursor-pointer"
>
<option value="java">Java Edition</option>
<option value="bedrock">Bedrock (via Geyser)</option>
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Version</label>
<input
type="text"
value={config.version}
onChange={(e) => setConfig({...config, version: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
placeholder="Auto (e.g. 1.21.1)"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Bot Name / Email</label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-[#444]" />
<input
type="text"
value={config.username}
onChange={(e) => setConfig({...config, username: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 pl-9 text-sm focus:border-[#00ff00] outline-none"
placeholder="Username or Email"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Authentication</label>
<select
value={config.auth || "offline"}
onChange={(e) => setConfig({...config, auth: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none appearance-none cursor-pointer"
>
<option value="offline">Offline (Cracked)</option>
<option value="microsoft">Microsoft (Premium)</option>
</select>
</div>
</div>
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">API Key (Gemini or Custom)</label>
<div className="relative">
<Key className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-[#444]" />
<input
type="password"
value={config.apiKey}
onChange={(e) => setConfig({...config, apiKey: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 pl-9 text-sm focus:border-[#00ff00] outline-none transition-colors"
placeholder="Enter your API Key..."
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">API Provider</label>
<select
value={config.apiType}
onChange={(e) => setConfig({...config, apiType: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none appearance-none cursor-pointer"
>
<option value="google">Google Gemini</option>
<option value="openai">OpenAI / Custom API</option>
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Max Blocks Limit</label>
<input
type="number"
value={config.maxBlocks}
onChange={(e) => setConfig({...config, maxBlocks: parseInt(e.target.value) || 1000})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
/>
</div>
</div>
{config.apiType === 'google' ? (
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Gemini Model</label>
<select
value={config.model}
onChange={(e) => setConfig({...config, model: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none appearance-none cursor-pointer"
>
<option value="gemini-pro-latest">🌌 Gemini Pro Latest (Latest Pro)</option>
<option value="gemini-flash-latest">🌌 Gemini Flash Latest (Latest Flash)</option>
<option value="gemini-3-pro-preview">🌌 Gemini 3 Pro (Gen 3 Pro)</option>
<option value="gemini-3-flash-preview">🌌 Gemini 3 Flash (Gen 3 Flash)</option>
<option value="gemini-2.5-pro">💎 Gemini 2.5 Pro (Powerful)</option>
<option value="gemini-2.5-flash">⚡ Gemini 2.5 Flash (Fast)</option>
<option value="gemini-2.5-flash-lite">🔦 Gemini 2.5 Lite (Lite)</option>
</select>
</div>
) : (
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">API Base URL</label>
<input
type="text"
value={config.apiBaseUrl}
onChange={(e) => setConfig({...config, apiBaseUrl: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
placeholder="https://api.openai.com/v1"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-[#666] uppercase">Model Name</label>
<input
type="text"
value={config.customModel}
onChange={(e) => setConfig({...config, customModel: e.target.value})}
className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
placeholder="gpt-4o"
/>
</div>
</div>
)}
</div>
<div className="mt-4 flex gap-2">
{!status.connected ? (
<button
onClick={handleConnect}
className="flex-grow flex items-center justify-center gap-2 bg-[#00ff00] text-black font-bold py-3 rounded hover:bg-[#00cc00] transition-colors uppercase text-xs"
>
<Play className="w-4 h-4 fill-current" /> Initialize Bot
</button>
) : (
<button
onClick={handleDisconnect}
className="flex-grow flex items-center justify-center gap-2 bg-[#ff0000] text-white font-bold py-3 rounded hover:bg-[#cc0000] transition-colors uppercase text-xs"
>
<Square className="w-4 h-4 fill-current" /> Terminate Session
</button>
)}
</div>
{msaAuth && (
<div className="mt-4 bg-[#1a1a1a] border border-[#00ff00] rounded p-4 flex flex-col items-center text-center gap-2 animate-pulse">
<h3 className="text-[#00ff00] font-bold uppercase text-xs tracking-widest">Microsoft Authentication Required</h3>
<p className="text-xs text-[#ccc]">Please sign in to your Microsoft account to continue.</p>
<div className="bg-[#050505] px-4 py-2 rounded border border-[#333] font-mono text-lg tracking-widest text-white my-2">
{msaAuth.code}
</div>
<a
href={msaAuth.uri}
target="_blank"
rel="noreferrer"
className="text-xs text-[#00ff00] hover:underline"
>
Click here to open login page ({msaAuth.uri})
</a>
</div>
)}
</div>
<div className="bg-[#111] border border-[#333] rounded-lg p-5">
<div className="flex items-center gap-2 mb-4">
<Activity className="w-4 h-4 text-[#666]" />
<h2 className="text-xs uppercase tracking-widest font-bold">Quick Commands</h2>
</div>
<div className="grid grid-cols-1 gap-2">
<p className="text-[10px] text-[#666] mb-2 leading-relaxed">
Use these commands in Minecraft chat to interact with the AI Architect:
</p>
<div className="bg-[#050505] border border-[#222] p-2 rounded flex flex-col gap-1">
<code className="text-[#00ff00] text-xs">!build [prompt]</code>
<span className="text-[9px] text-[#444]">Start a new build (or use !б)</span>
<div className="h-px bg-[#222] my-1" />
<code className="text-[#00ff00] text-xs">!next [prompt]</code>
<span className="text-[9px] text-[#444]">Modify previous build (or use !н)</span>
</div>
</div>
</div>
</section>
{/* Console / Logs */}
<section className="lg:col-span-8 flex flex-col">
<div className="bg-[#050505] border border-[#333] rounded-lg flex flex-col h-[600px]">
<div className="flex items-center justify-between px-4 py-2 border-b border-[#333] bg-[#111] rounded-t-lg">
<div className="flex items-center gap-2">
<Terminal className="w-3 h-3 text-[#666]" />
<span className="text-[10px] uppercase tracking-widest font-bold">System Terminal</span>
</div>
<div className="flex gap-1">
<div className="w-2 h-2 rounded-full bg-[#333]" />
<div className="w-2 h-2 rounded-full bg-[#333]" />
<div className="w-2 h-2 rounded-full bg-[#333]" />
</div>
</div>
<div className="flex-grow overflow-y-auto p-4 space-y-2 font-mono text-xs custom-scrollbar">
<AnimatePresence initial={false}>
{logs.length === 0 && (
<div className="text-[#333] italic">Waiting for connection...</div>
)}
{logs.map((log, i) => (
<motion.div
key={i}
initial={{ opacity: 0, x: -5 }}
animate={{ opacity: 1, x: 0 }}
className="flex gap-3 border-l border-[#222] pl-3 py-1"
>
<span className="text-[#444] shrink-0">[{new Date(log.timestamp).toLocaleTimeString()}]</span>
<span className={`
${log.type === "error" ? "text-[#ff0000]" : ""}
${log.type === "success" ? "text-[#00ff00]" : ""}
${log.type === "chat" ? "text-[#00ffff]" : ""}
${log.type === "info" ? "text-[#888]" : ""}
`}>
{log.type === "chat" && <MessageSquare className="inline w-3 h-3 mr-2 opacity-50" />}
{log.message}
</span>
</motion.div>
))}
</AnimatePresence>
<div ref={logEndRef} />
</div>
</div>
</section>
</main>
<footer className="mt-auto pt-4 border-t border-[#333] flex justify-between items-center text-[10px] text-[#444] uppercase tracking-widest">
<span>© 2026 AI ARCHITECT PROTOCOL</span>
<div className="flex gap-4">
<span>LATENCY: 42MS</span>
<span>UPTIME: 99.9%</span>
</div>
</footer>
<style>{`
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #050505;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #222;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #333;
}
`}</style>
</div>
);
}