Sk0lovek commited on
Commit
ef50209
·
verified ·
1 Parent(s): af98494

Upload 13 files

Browse files
Files changed (13) hide show
  1. .env.example +9 -0
  2. .gitignore +8 -0
  3. README.md +20 -10
  4. index.html +13 -0
  5. metadata.json +5 -0
  6. package-lock.json +0 -0
  7. package.json +39 -0
  8. server.ts +258 -0
  9. src/App.tsx +358 -0
  10. src/index.css +1 -0
  11. src/main.tsx +10 -0
  12. tsconfig.json +26 -0
  13. vite.config.ts +24 -0
.env.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # GEMINI_API_KEY: Required for Gemini AI API calls.
2
+ # AI Studio automatically injects this at runtime from user secrets.
3
+ # Users configure this via the Secrets panel in the AI Studio UI.
4
+ GEMINI_API_KEY="MY_GEMINI_API_KEY"
5
+
6
+ # APP_URL: The URL where this applet is hosted.
7
+ # AI Studio automatically injects this at runtime with the Cloud Run service URL.
8
+ # Used for self-referential links, OAuth callbacks, and API endpoints.
9
+ APP_URL="MY_APP_URL"
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+ build/
3
+ dist/
4
+ coverage/
5
+ .DS_Store
6
+ *.log
7
+ .env*
8
+ !.env.example
README.md CHANGED
@@ -1,10 +1,20 @@
1
- ---
2
- title: AIBuilder
3
- emoji: 📉
4
- colorFrom: pink
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+ <img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
3
+ </div>
4
+
5
+ # Run and deploy your AI Studio app
6
+
7
+ This contains everything you need to run your app locally.
8
+
9
+ View your app in AI Studio: https://ai.studio/apps/e637c9a3-5d3d-4b65-a91f-769d105c53be
10
+
11
+ ## Run Locally
12
+
13
+ **Prerequisites:** Node.js
14
+
15
+
16
+ 1. Install dependencies:
17
+ `npm install`
18
+ 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
19
+ 3. Run the app:
20
+ `npm run dev`
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>My Google AI Studio App</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
13
+
metadata.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "name": "Minecraft AI Architect",
3
+ "description": "Web dashboard to manage an AI-powered Minecraft bot that builds structures from chat prompts using Gemini.",
4
+ "requestFramePermissions": []
5
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "react-example",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx server.ts",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "clean": "rm -rf dist",
11
+ "lint": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@google/genai": "^1.46.0",
15
+ "@tailwindcss/vite": "^4.1.14",
16
+ "@vitejs/plugin-react": "^5.0.4",
17
+ "cors": "^2.8.6",
18
+ "dotenv": "^17.2.3",
19
+ "express": "^4.22.1",
20
+ "lucide-react": "^0.546.0",
21
+ "mineflayer": "^4.35.0",
22
+ "mineflayer-pathfinder": "^2.4.5",
23
+ "motion": "^12.23.24",
24
+ "react": "^19.0.0",
25
+ "react-dom": "^19.0.0",
26
+ "socket.io": "^4.8.3",
27
+ "socket.io-client": "^4.8.3",
28
+ "vite": "^6.2.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/express": "^4.17.21",
32
+ "@types/node": "^22.14.0",
33
+ "autoprefixer": "^10.4.21",
34
+ "tailwindcss": "^4.1.14",
35
+ "tsx": "^4.21.0",
36
+ "typescript": "~5.8.2",
37
+ "vite": "^6.2.0"
38
+ }
39
+ }
server.ts ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from "express";
2
+ import { createServer } from "http";
3
+ import { Server } from "socket.io";
4
+ import { createServer as createViteServer } from "vite";
5
+ import path from "path";
6
+ import mineflayer from "mineflayer";
7
+ import { pathfinder, Movements, goals } from "mineflayer-pathfinder";
8
+ import { GoogleGenAI, Type } from "@google/genai";
9
+
10
+ const app = express();
11
+ const httpServer = createServer(app);
12
+ const io = new Server(httpServer, {
13
+ cors: {
14
+ origin: "*",
15
+ },
16
+ });
17
+
18
+ const PORT = 3000;
19
+
20
+ let bot: mineflayer.Bot | null = null;
21
+ let ai: GoogleGenAI | null = null;
22
+ let selectedModel: string = "gemini-3-flash-preview";
23
+ let apiType: string = "google";
24
+ let apiBaseUrl: string = "";
25
+ let customModel: string = "";
26
+ let maxBlocks: number = 10000;
27
+ let globalApiKey: string = "";
28
+
29
+ // Helper to log to frontend
30
+ const log = (message: string, type: "info" | "success" | "error" | "chat" = "info") => {
31
+ io.emit("bot-log", { message, type, timestamp: new Date().toISOString() });
32
+ console.log(`[${type.toUpperCase()}] ${message}`);
33
+ };
34
+
35
+ async function startServer() {
36
+ // API Routes
37
+ app.get("/api/status", (req, res) => {
38
+ res.json({ connected: !!bot, username: bot?.username });
39
+ });
40
+
41
+ // Socket.io for Bot Control
42
+ io.on("connection", (socket) => {
43
+ log("Frontend connected to control panel");
44
+
45
+ socket.on("connect-bot", (config) => {
46
+ const { host, port, version, username, apiKey, model } = config;
47
+
48
+ if (bot) {
49
+ bot.quit();
50
+ log("Existing bot disconnected");
51
+ }
52
+
53
+ try {
54
+ globalApiKey = apiKey;
55
+ apiType = config.apiType || "google";
56
+ apiBaseUrl = config.apiBaseUrl || "";
57
+ customModel = config.customModel || "";
58
+ maxBlocks = parseInt(config.maxBlocks) || 10000;
59
+ selectedModel = model || "gemini-3-flash-preview";
60
+
61
+ if (apiType === "google") {
62
+ ai = new GoogleGenAI({ apiKey });
63
+ } else {
64
+ ai = null;
65
+ }
66
+
67
+ bot = mineflayer.createBot({
68
+ host,
69
+ port: parseInt(port) || 25565,
70
+ username,
71
+ version: version || false,
72
+ });
73
+
74
+ bot.loadPlugin(pathfinder);
75
+
76
+ bot.on("spawn", () => {
77
+ log(`Bot ${bot?.username} spawned on ${host}:${port}`, "success");
78
+ io.emit("bot-status", { connected: true, username: bot?.username });
79
+ });
80
+
81
+ bot.on("chat", async (username, message) => {
82
+ if (username === bot?.username) return;
83
+ log(`${username}: ${message}`, "chat");
84
+
85
+ if (message.startsWith("!build ")) {
86
+ const prompt = message.replace("!build ", "");
87
+ await handleBuildRequest(prompt, username);
88
+ }
89
+ });
90
+
91
+ bot.on("error", (err) => log(`Bot error: ${err.message}`, "error"));
92
+ bot.on("kicked", (reason) => {
93
+ log(`Bot kicked: ${reason}`, "error");
94
+ bot = null;
95
+ io.emit("bot-status", { connected: false });
96
+ });
97
+
98
+ } catch (error: any) {
99
+ log(`Failed to initialize: ${error.message}`, "error");
100
+ }
101
+ });
102
+
103
+ socket.on("disconnect-bot", () => {
104
+ if (bot) {
105
+ bot.quit();
106
+ bot = null;
107
+ log("Bot disconnected manually");
108
+ io.emit("bot-status", { connected: false });
109
+ }
110
+ });
111
+ });
112
+
113
+ async function handleBuildRequest(prompt: string, requestingUser: string) {
114
+ if (!bot) return;
115
+
116
+ log(`AI is thinking about: "${prompt}"...`);
117
+ bot.chat(`Request received from ${requestingUser}, beginning generation...`);
118
+
119
+ try {
120
+ const promptText = `You are a Minecraft architect and admin. The user "${requestingUser}" wants: "${prompt}".
121
+ You can build structures or execute commands (like giving items, summoning entities, etc.).
122
+ Coordinates (x, y, z) for blocks are relative to the bot's current position (0,0,0).
123
+ For commands (like /summon), use relative coordinates (~ ~ ~) which are relative to the bot!
124
+ Use standard Minecraft 1.21.1 block names and command syntax.
125
+
126
+ CRITICAL 1.21.1 COMMAND SYNTAX:
127
+ - Items with custom names/enchantments: give ${requestingUser} cobblestone[custom_name=[{"text":"Name","italic":false}],enchantments={channeling:1}]
128
+ - Summoning entities with visible names: summon armor_stand ~2 ~ ~ {CustomName:'{"text":"Palace"}',CustomNameVisible:1b,ArmorItems:[{},{},{},{id:"minecraft:shield",count:1}]}
129
+
130
+ You can build large structures up to ${maxBlocks} blocks.
131
+ To save tokens, output a JSON object containing an "actions" array.
132
+ Action type 1 (Place Block): [x, y, z, "block_name"]
133
+ Action type 2 (Execute Command): ["cmd", "command_string"]
134
+ Example:
135
+ {
136
+ "actions": [
137
+ [0, 0, 0, "stone"],
138
+ [0, 1, 0, "oak_planks"],
139
+ ["cmd", "give ${requestingUser} diamond_sword[custom_name=[{\\"text\\":\\"Epic Sword\\",\\"italic\\":false}]] 1"],
140
+ ["cmd", "summon armor_stand ~2 ~ ~ {CustomName:'{\\"text\\":\\"Guard\\"}',CustomNameVisible:1b}"]
141
+ ]
142
+ }
143
+ Output ONLY the JSON object.`;
144
+
145
+ let responseText = "";
146
+
147
+ if (apiType === "openai") {
148
+ const url = apiBaseUrl.replace(/\/+$/, '') + '/chat/completions';
149
+ const res = await fetch(url, {
150
+ method: "POST",
151
+ headers: {
152
+ "Content-Type": "application/json",
153
+ "Authorization": `Bearer ${globalApiKey}`
154
+ },
155
+ body: JSON.stringify({
156
+ model: customModel || "gpt-3.5-turbo",
157
+ messages: [{ role: "user", content: promptText }]
158
+ })
159
+ });
160
+
161
+ if (!res.ok) {
162
+ const errText = await res.text();
163
+ throw new Error(`Custom API Error: ${res.status} ${errText}`);
164
+ }
165
+
166
+ const data = await res.json();
167
+ responseText = data.choices[0].message.content;
168
+ } else {
169
+ if (!ai) throw new Error("Google GenAI not initialized");
170
+ const response = await ai.models.generateContent({
171
+ model: selectedModel,
172
+ contents: promptText,
173
+ config: {
174
+ responseMimeType: "application/json",
175
+ }
176
+ });
177
+ responseText = response.text;
178
+ }
179
+
180
+ // Clean up markdown formatting if present
181
+ responseText = responseText.replace(/```json/g, '').replace(/```/g, '').trim();
182
+
183
+ // Fix bad control characters (like literal newlines or tabs inside strings) that break JSON.parse
184
+ responseText = responseText.replace(/[\n\r\t]/g, ' ').replace(/[\x00-\x1F\x7F-\x9F]/g, '');
185
+
186
+ const parsed = JSON.parse(responseText);
187
+ const actions = parsed.actions;
188
+
189
+ if (!Array.isArray(actions)) throw new Error("Invalid AI response format: missing 'actions' array");
190
+
191
+ log(`AI generated ${actions.length} actions. Muting command feedback and starting execution...`, "success");
192
+
193
+ // Disable command feedback in Minecraft to prevent chat spam
194
+ bot.chat('/gamerule sendCommandFeedback false');
195
+ bot.chat('/gamerule logAdminCommands false');
196
+
197
+ const startPos = bot.entity.position.clone();
198
+
199
+ for (const action of actions) {
200
+ if (!bot) break;
201
+ if (!Array.isArray(action)) continue;
202
+
203
+ try {
204
+ // 100ms delay to speed up building while avoiding anti-spam kicks (10 blocks/sec)
205
+ await new Promise(r => setTimeout(r, 100));
206
+
207
+ if (action[0] === "cmd" && typeof action[1] === "string") {
208
+ let cmd = action[1];
209
+ if (cmd.startsWith("/")) cmd = cmd.substring(1);
210
+ log(`Executing command: /${cmd}`, "info");
211
+ bot.chat(`/${cmd}`);
212
+ } else if (action.length >= 4) {
213
+ const [x, y, z, rawBlock] = action;
214
+ const targetPos = startPos.offset(x, y, z);
215
+
216
+ // Ensure block name has minecraft: prefix if it doesn't already
217
+ let blockName = String(rawBlock).toLowerCase();
218
+ if (!blockName.startsWith('minecraft:')) {
219
+ blockName = `minecraft:${blockName}`;
220
+ }
221
+
222
+ // @ts-ignore
223
+ bot.chat(`/setblock ${Math.floor(targetPos.x)} ${Math.floor(targetPos.y)} ${Math.floor(targetPos.z)} ${blockName}`);
224
+ }
225
+ } catch (e) {
226
+ log(`Failed to execute action: ${JSON.stringify(action)}`, "error");
227
+ }
228
+ }
229
+
230
+ bot.chat("Generation complete.");
231
+ log("Generation complete!", "success");
232
+ } catch (error: any) {
233
+ log(`AI Error: ${error.message}`, "error");
234
+ bot.chat(`Error during generation: ${error.message}`);
235
+ }
236
+ }
237
+
238
+ // Vite middleware for development
239
+ if (process.env.NODE_ENV !== "production") {
240
+ const vite = await createViteServer({
241
+ server: { middlewareMode: true },
242
+ appType: "spa",
243
+ });
244
+ app.use(vite.middlewares);
245
+ } else {
246
+ const distPath = path.join(process.cwd(), "dist");
247
+ app.use(express.static(distPath));
248
+ app.get("*", (req, res) => {
249
+ res.sendFile(path.join(distPath, "index.html"));
250
+ });
251
+ }
252
+
253
+ httpServer.listen(PORT, "0.0.0.0", () => {
254
+ log(`Server running on http://localhost:${PORT}`, "success");
255
+ });
256
+ }
257
+
258
+ startServer();
src/App.tsx ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { io, Socket } from "socket.io-client";
3
+ import {
4
+ Terminal,
5
+ Settings,
6
+ Play,
7
+ Square,
8
+ Cpu,
9
+ Globe,
10
+ User,
11
+ Key,
12
+ MessageSquare,
13
+ Activity
14
+ } from "lucide-react";
15
+ import { motion, AnimatePresence } from "motion/react";
16
+
17
+ interface LogEntry {
18
+ message: string;
19
+ type: "info" | "success" | "error" | "chat";
20
+ timestamp: string;
21
+ }
22
+
23
+ export default function App() {
24
+ const [socket, setSocket] = useState<Socket | null>(null);
25
+ const [config, setConfig] = useState(() => {
26
+ const saved = localStorage.getItem("ai_architect_config");
27
+ if (saved) {
28
+ try {
29
+ return JSON.parse(saved);
30
+ } catch (e) {}
31
+ }
32
+ return {
33
+ host: "localhost",
34
+ port: "25565",
35
+ version: "1.21.11",
36
+ username: "AI_Architect",
37
+ apiKey: "",
38
+ model: "gemini-3-flash-preview",
39
+ apiType: "google",
40
+ apiBaseUrl: "https://api.openai.com/v1",
41
+ customModel: "gpt-4o",
42
+ maxBlocks: 10000
43
+ };
44
+ });
45
+
46
+ useEffect(() => {
47
+ localStorage.setItem("ai_architect_config", JSON.stringify(config));
48
+ }, [config]);
49
+
50
+ const [status, setStatus] = useState({ connected: false, username: "" });
51
+ const [logs, setLogs] = useState<LogEntry[]>([]);
52
+ const logEndRef = useRef<HTMLDivElement>(null);
53
+
54
+ useEffect(() => {
55
+ const newSocket = io();
56
+ setSocket(newSocket);
57
+
58
+ newSocket.on("bot-log", (log: LogEntry) => {
59
+ setLogs((prev) => [...prev.slice(-100), log]);
60
+ });
61
+
62
+ newSocket.on("bot-status", (status) => {
63
+ setStatus(status);
64
+ });
65
+
66
+ return () => {
67
+ newSocket.disconnect();
68
+ };
69
+ }, []);
70
+
71
+ useEffect(() => {
72
+ logEndRef.current?.scrollIntoView({ behavior: "smooth" });
73
+ }, [logs]);
74
+
75
+ const handleConnect = () => {
76
+ if (!socket) return;
77
+ if (!config.apiKey) {
78
+ alert("Please enter a Gemini API Key");
79
+ return;
80
+ }
81
+ socket.emit("connect-bot", config);
82
+ };
83
+
84
+ const handleDisconnect = () => {
85
+ if (!socket) return;
86
+ socket.emit("disconnect-bot");
87
+ };
88
+
89
+ return (
90
+ <div className="min-h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono p-4 md:p-8 flex flex-col gap-6">
91
+ {/* Header */}
92
+ <header className="flex items-center justify-between border-b border-[#333] pb-4">
93
+ <div className="flex items-center gap-3">
94
+ <div className="p-2 bg-[#1a1a1a] border border-[#333] rounded">
95
+ <Cpu className="w-6 h-6 text-[#00ff00]" />
96
+ </div>
97
+ <div>
98
+ <h1 className="text-xl font-bold tracking-tighter uppercase">Minecraft AI Architect</h1>
99
+ <p className="text-xs text-[#666]">v1.0.0 // SPIGOT 1.21.1 INTEGRATION</p>
100
+ </div>
101
+ </div>
102
+ <div className="flex items-center gap-4">
103
+ <div className="flex items-center gap-2 px-3 py-1 bg-[#1a1a1a] border border-[#333] rounded-full">
104
+ <div className={`w-2 h-2 rounded-full ${status.connected ? "bg-[#00ff00] animate-pulse" : "bg-[#ff0000]"}`} />
105
+ <span className="text-[10px] uppercase tracking-widest">
106
+ {status.connected ? `ONLINE: ${status.username}` : "OFFLINE"}
107
+ </span>
108
+ </div>
109
+ </div>
110
+ </header>
111
+
112
+ <main className="grid grid-cols-1 lg:grid-cols-12 gap-6 flex-grow">
113
+ {/* Configuration Panel */}
114
+ <section className="lg:col-span-4 flex flex-col gap-4">
115
+ <div className="bg-[#111] border border-[#333] rounded-lg p-5 flex flex-col gap-4">
116
+ <div className="flex items-center gap-2 mb-2">
117
+ <Settings className="w-4 h-4 text-[#666]" />
118
+ <h2 className="text-xs uppercase tracking-widest font-bold">Bot Configuration</h2>
119
+ </div>
120
+
121
+ <div className="space-y-4">
122
+ <div className="space-y-1">
123
+ <label className="text-[10px] text-[#666] uppercase">Server Host</label>
124
+ <div className="relative">
125
+ <Globe className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-[#444]" />
126
+ <input
127
+ type="text"
128
+ value={config.host}
129
+ onChange={(e) => setConfig({...config, host: e.target.value})}
130
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 pl-9 text-sm focus:border-[#00ff00] outline-none transition-colors"
131
+ placeholder="localhost"
132
+ />
133
+ </div>
134
+ </div>
135
+
136
+ <div className="grid grid-cols-3 gap-4">
137
+ <div className="space-y-1">
138
+ <label className="text-[10px] text-[#666] uppercase">Port</label>
139
+ <input
140
+ type="text"
141
+ value={config.port}
142
+ onChange={(e) => setConfig({...config, port: e.target.value})}
143
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
144
+ placeholder="25565"
145
+ />
146
+ </div>
147
+ <div className="space-y-1">
148
+ <label className="text-[10px] text-[#666] uppercase">Version</label>
149
+ <input
150
+ type="text"
151
+ value={config.version}
152
+ onChange={(e) => setConfig({...config, version: e.target.value})}
153
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
154
+ placeholder="Auto"
155
+ />
156
+ </div>
157
+ <div className="space-y-1">
158
+ <label className="text-[10px] text-[#666] uppercase">Bot Name</label>
159
+ <div className="relative">
160
+ <User className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-[#444]" />
161
+ <input
162
+ type="text"
163
+ value={config.username}
164
+ onChange={(e) => setConfig({...config, username: e.target.value})}
165
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 pl-9 text-sm focus:border-[#00ff00] outline-none"
166
+ />
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ <div className="space-y-1">
172
+ <label className="text-[10px] text-[#666] uppercase">API Key (Gemini or Custom)</label>
173
+ <div className="relative">
174
+ <Key className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-[#444]" />
175
+ <input
176
+ type="password"
177
+ value={config.apiKey}
178
+ onChange={(e) => setConfig({...config, apiKey: e.target.value})}
179
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 pl-9 text-sm focus:border-[#00ff00] outline-none transition-colors"
180
+ placeholder="Enter your API Key..."
181
+ />
182
+ </div>
183
+ </div>
184
+
185
+ <div className="grid grid-cols-2 gap-4">
186
+ <div className="space-y-1">
187
+ <label className="text-[10px] text-[#666] uppercase">API Provider</label>
188
+ <select
189
+ value={config.apiType}
190
+ onChange={(e) => setConfig({...config, apiType: e.target.value})}
191
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none appearance-none cursor-pointer"
192
+ >
193
+ <option value="google">Google Gemini</option>
194
+ <option value="openai">OpenAI / Custom API</option>
195
+ </select>
196
+ </div>
197
+ <div className="space-y-1">
198
+ <label className="text-[10px] text-[#666] uppercase">Max Blocks Limit</label>
199
+ <input
200
+ type="number"
201
+ value={config.maxBlocks}
202
+ onChange={(e) => setConfig({...config, maxBlocks: parseInt(e.target.value) || 1000})}
203
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
204
+ />
205
+ </div>
206
+ </div>
207
+
208
+ {config.apiType === 'google' ? (
209
+ <div className="space-y-1">
210
+ <label className="text-[10px] text-[#666] uppercase">Gemini Model</label>
211
+ <select
212
+ value={config.model}
213
+ onChange={(e) => setConfig({...config, model: e.target.value})}
214
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none appearance-none cursor-pointer"
215
+ >
216
+ <option value="gemini-pro-latest">🌌 Gemini Pro Latest (Latest Pro)</option>
217
+ <option value="gemini-flash-latest">🌌 Gemini Flash Latest (Latest Flash)</option>
218
+ <option value="gemini-3-pro-preview">🌌 Gemini 3 Pro (Gen 3 Pro)</option>
219
+ <option value="gemini-3-flash-preview">🌌 Gemini 3 Flash (Gen 3 Flash)</option>
220
+ <option value="gemini-2.5-pro">💎 Gemini 2.5 Pro (Powerful)</option>
221
+ <option value="gemini-2.5-flash">⚡ Gemini 2.5 Flash (Fast)</option>
222
+ <option value="gemini-2.5-flash-lite">🔦 Gemini 2.5 Lite (Lite)</option>
223
+ </select>
224
+ </div>
225
+ ) : (
226
+ <div className="grid grid-cols-2 gap-4">
227
+ <div className="space-y-1">
228
+ <label className="text-[10px] text-[#666] uppercase">API Base URL</label>
229
+ <input
230
+ type="text"
231
+ value={config.apiBaseUrl}
232
+ onChange={(e) => setConfig({...config, apiBaseUrl: e.target.value})}
233
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
234
+ placeholder="https://api.openai.com/v1"
235
+ />
236
+ </div>
237
+ <div className="space-y-1">
238
+ <label className="text-[10px] text-[#666] uppercase">Model Name</label>
239
+ <input
240
+ type="text"
241
+ value={config.customModel}
242
+ onChange={(e) => setConfig({...config, customModel: e.target.value})}
243
+ className="w-full bg-[#050505] border border-[#222] rounded p-2 text-sm focus:border-[#00ff00] outline-none"
244
+ placeholder="gpt-4o"
245
+ />
246
+ </div>
247
+ </div>
248
+ )}
249
+ </div>
250
+
251
+ <div className="mt-4 flex gap-2">
252
+ {!status.connected ? (
253
+ <button
254
+ onClick={handleConnect}
255
+ 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"
256
+ >
257
+ <Play className="w-4 h-4 fill-current" /> Initialize Bot
258
+ </button>
259
+ ) : (
260
+ <button
261
+ onClick={handleDisconnect}
262
+ 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"
263
+ >
264
+ <Square className="w-4 h-4 fill-current" /> Terminate Session
265
+ </button>
266
+ )}
267
+ </div>
268
+ </div>
269
+
270
+ <div className="bg-[#111] border border-[#333] rounded-lg p-5">
271
+ <div className="flex items-center gap-2 mb-4">
272
+ <Activity className="w-4 h-4 text-[#666]" />
273
+ <h2 className="text-xs uppercase tracking-widest font-bold">Quick Commands</h2>
274
+ </div>
275
+ <div className="grid grid-cols-1 gap-2">
276
+ <p className="text-[10px] text-[#666] mb-2 leading-relaxed">
277
+ Use these commands in Minecraft chat to interact with the AI Architect:
278
+ </p>
279
+ <div className="bg-[#050505] border border-[#222] p-2 rounded flex flex-col gap-1">
280
+ <code className="text-[#00ff00] text-xs">!build [prompt]</code>
281
+ <span className="text-[9px] text-[#444]">Example: !build a small stone tower</span>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ </section>
286
+
287
+ {/* Console / Logs */}
288
+ <section className="lg:col-span-8 flex flex-col">
289
+ <div className="bg-[#050505] border border-[#333] rounded-lg flex flex-col h-[600px]">
290
+ <div className="flex items-center justify-between px-4 py-2 border-b border-[#333] bg-[#111] rounded-t-lg">
291
+ <div className="flex items-center gap-2">
292
+ <Terminal className="w-3 h-3 text-[#666]" />
293
+ <span className="text-[10px] uppercase tracking-widest font-bold">System Terminal</span>
294
+ </div>
295
+ <div className="flex gap-1">
296
+ <div className="w-2 h-2 rounded-full bg-[#333]" />
297
+ <div className="w-2 h-2 rounded-full bg-[#333]" />
298
+ <div className="w-2 h-2 rounded-full bg-[#333]" />
299
+ </div>
300
+ </div>
301
+
302
+ <div className="flex-grow overflow-y-auto p-4 space-y-2 font-mono text-xs custom-scrollbar">
303
+ <AnimatePresence initial={false}>
304
+ {logs.length === 0 && (
305
+ <div className="text-[#333] italic">Waiting for connection...</div>
306
+ )}
307
+ {logs.map((log, i) => (
308
+ <motion.div
309
+ key={i}
310
+ initial={{ opacity: 0, x: -5 }}
311
+ animate={{ opacity: 1, x: 0 }}
312
+ className="flex gap-3 border-l border-[#222] pl-3 py-1"
313
+ >
314
+ <span className="text-[#444] shrink-0">[{new Date(log.timestamp).toLocaleTimeString()}]</span>
315
+ <span className={`
316
+ ${log.type === "error" ? "text-[#ff0000]" : ""}
317
+ ${log.type === "success" ? "text-[#00ff00]" : ""}
318
+ ${log.type === "chat" ? "text-[#00ffff]" : ""}
319
+ ${log.type === "info" ? "text-[#888]" : ""}
320
+ `}>
321
+ {log.type === "chat" && <MessageSquare className="inline w-3 h-3 mr-2 opacity-50" />}
322
+ {log.message}
323
+ </span>
324
+ </motion.div>
325
+ ))}
326
+ </AnimatePresence>
327
+ <div ref={logEndRef} />
328
+ </div>
329
+ </div>
330
+ </section>
331
+ </main>
332
+
333
+ <footer className="mt-auto pt-4 border-t border-[#333] flex justify-between items-center text-[10px] text-[#444] uppercase tracking-widest">
334
+ <span>© 2026 AI ARCHITECT PROTOCOL</span>
335
+ <div className="flex gap-4">
336
+ <span>LATENCY: 42MS</span>
337
+ <span>UPTIME: 99.9%</span>
338
+ </div>
339
+ </footer>
340
+
341
+ <style>{`
342
+ .custom-scrollbar::-webkit-scrollbar {
343
+ width: 4px;
344
+ }
345
+ .custom-scrollbar::-webkit-scrollbar-track {
346
+ background: #050505;
347
+ }
348
+ .custom-scrollbar::-webkit-scrollbar-thumb {
349
+ background: #222;
350
+ border-radius: 10px;
351
+ }
352
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
353
+ background: #333;
354
+ }
355
+ `}</style>
356
+ </div>
357
+ );
358
+ }
src/index.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "tailwindcss";
src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import {StrictMode} from 'react';
2
+ import {createRoot} from 'react-dom/client';
3
+ import App from './App.tsx';
4
+ import './index.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": [
8
+ "ES2022",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "skipLibCheck": true,
13
+ "moduleResolution": "bundler",
14
+ "isolatedModules": true,
15
+ "moduleDetection": "force",
16
+ "allowJs": true,
17
+ "jsx": "react-jsx",
18
+ "paths": {
19
+ "@/*": [
20
+ "./*"
21
+ ]
22
+ },
23
+ "allowImportingTsExtensions": true,
24
+ "noEmit": true
25
+ }
26
+ }
vite.config.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tailwindcss from '@tailwindcss/vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+ import {defineConfig, loadEnv} from 'vite';
5
+
6
+ export default defineConfig(({mode}) => {
7
+ const env = loadEnv(mode, '.', '');
8
+ return {
9
+ plugins: [react(), tailwindcss()],
10
+ define: {
11
+ 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
12
+ },
13
+ resolve: {
14
+ alias: {
15
+ '@': path.resolve(__dirname, '.'),
16
+ },
17
+ },
18
+ server: {
19
+ // HMR is disabled in AI Studio via DISABLE_HMR env var.
20
+ // Do not modify—file watching is disabled to prevent flickering during agent edits.
21
+ hmr: process.env.DISABLE_HMR !== 'true',
22
+ },
23
+ };
24
+ });