Files changed (1) hide show
  1. app.js +51 -72
app.js CHANGED
@@ -1,100 +1,79 @@
1
  // server.js
2
- // Node + Express server to accept Snapshotter payloads from the Roblox plugin.
3
- // Save as server.js and run: node server.js
4
- // Installs: npm install express body-parser cors node-fetch
5
-
6
  import express from "express";
7
  import bodyParser from "body-parser";
8
  import cors from "cors";
9
  import fs from "fs";
10
  import path from "path";
11
  import { fileURLToPath } from "url";
12
- import fetch from "node-fetch";
13
 
14
  const __filename = fileURLToPath(import.meta.url);
15
  const __dirname = path.dirname(__filename);
16
-
17
- const PORT = process.env.PORT ? Number(process.env.PORT) : 7860;
18
- const STORAGE_DIR = path.join(__dirname, "data");
19
-
20
- if (!fs.existsSync(STORAGE_DIR)) fs.mkdirSync(STORAGE_DIR, { recursive: true });
21
 
22
  const app = express();
23
  app.use(cors());
24
- // Increase limit to 50mb to handle large complex workspaces
25
  app.use(bodyParser.json({ limit: "50mb" }));
26
 
27
- // Logging middleware
28
- app.use((req, res, next) => {
29
- console.log(new Date().toISOString(), req.method, req.url);
30
- next();
31
- });
32
-
33
- // Health check
34
- app.get("/ping", (req, res) => res.status(200).send("pong"));
35
 
36
- // Main endpoint for the Plugin
37
  app.post("/api/ai-build", async (req, res) => {
38
  try {
39
- const payload = req.body;
40
- if (!payload) return res.status(400).json({ success: false, error: "empty payload" });
41
-
42
- // Generate ID
43
- const id = "asset_" + Math.random().toString(36).slice(2, 10);
44
- const filename = path.join(STORAGE_DIR, `${id}.json`);
45
-
46
- // Log what we received
47
- console.log(`πŸ“₯ Received snapshot from: ${payload.sender || "Unknown"}`);
48
- console.log(`πŸ“ Instruction: "${payload.instruction || "No instruction provided"}"`);
49
- console.log(`Target Root: ${payload.selectionName}`);
50
 
51
- // Save payload to disk (Game State + Instruction)
52
- fs.writeFileSync(filename, JSON.stringify({ receivedAt: Date.now(), payload }, null, 2), "utf8");
 
 
 
 
 
53
 
54
- // FUTURE: Here is where you would trigger the Gemini API call using 'payload.instruction' and 'payload.state'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- // If client provided a callback URL, fire-and-forget
57
- if (payload.callbackUrl && typeof payload.callbackUrl === "string") {
58
- const cbBody = { assetId: id, status: "processed", ts: Date.now() };
59
- (async () => {
60
- try {
61
- if (typeof fetch === "function") {
62
- await fetch(payload.callbackUrl, {
63
- method: "POST",
64
- headers: { "Content-Type": "application/json" },
65
- body: JSON.stringify(cbBody)
66
- });
67
- console.log("Callback sent to", payload.callbackUrl);
68
- }
69
- } catch (err) {
70
- console.warn("Callback failed:", err.message);
71
- }
72
- })();
73
  }
74
 
75
- return res.json({ success: true, assetId: id, message: "Snapshot received successfully" });
76
-
77
- } catch (err) {
78
- console.error("/api/ai-build error:", err);
79
- return res.status(500).json({ success: false, error: String(err) });
80
- }
81
- });
82
 
83
- // Retrieve stored assets
84
- app.get("/asset/:id", (req, res) => {
85
- try {
86
- const id = req.params.id;
87
- const filename = path.join(STORAGE_DIR, `${id}.json`);
88
- if (!fs.existsSync(filename)) return res.status(404).json({ found: false });
89
- const content = fs.readFileSync(filename, "utf8");
90
- return res.json({ found: true, data: JSON.parse(content) });
91
  } catch (err) {
92
- console.error("/asset error", err);
93
- return res.status(500).json({ success: false, error: String(err) });
94
  }
95
  });
96
 
97
- app.listen(PORT, () => {
98
- console.log(`πŸš€ Plugin backend listening on http://localhost:${PORT}`);
99
- console.log(`πŸ“‚ Storage: ${STORAGE_DIR}`);
100
- });
 
1
  // server.js
 
 
 
 
2
  import express from "express";
3
  import bodyParser from "body-parser";
4
  import cors from "cors";
5
  import fs from "fs";
6
  import path from "path";
7
  import { fileURLToPath } from "url";
 
8
 
9
  const __filename = fileURLToPath(import.meta.url);
10
  const __dirname = path.dirname(__filename);
11
+ const PORT = process.env.PORT || 7860;
 
 
 
 
12
 
13
  const app = express();
14
  app.use(cors());
 
15
  app.use(bodyParser.json({ limit: "50mb" }));
16
 
17
+ // === DEMO CONTROLLER ===
18
+ // CHANGE THIS TO TEST DIFFERENT INTERACTIONS
19
+ // Options: 'NONE' (Just save), 'BUILD_PART' (Send Lua Code), 'FETCH_SCRIPT' (Ask for Source)
20
+ const DEMO_MODE = 'BUILD_PART';
 
 
 
 
21
 
 
22
  app.post("/api/ai-build", async (req, res) => {
23
  try {
24
+ const { instruction, state, scriptContext } = req.body;
25
+
26
+ console.log("πŸ“₯ Received Request");
27
+ console.log(" Instruction:", instruction);
28
+
29
+ // 1. Log Payload Size (Check token efficiency)
30
+ if(state && state.flatList) {
31
+ console.log(` Hierarchy Size: ${state.flatList.length} items (Flat Format)`);
32
+ }
 
 
33
 
34
+ // 2. IF WE RECEIVED SCRIPT SOURCE (From a previous fetch request)
35
+ if (scriptContext) {
36
+ console.log(" πŸ“˜ Received Script Source for:", scriptContext.name);
37
+ console.log(" --- START SOURCE ---\n", scriptContext.source.substring(0, 100) + "...", "\n --- END SOURCE ---");
38
+ // In a real app, you would now feed this to the AI to edit.
39
+ return res.json({ success: true, message: "Source received, analyzing..." });
40
+ }
41
 
42
+ // 3. DEMO RESPONSES
43
+ if (DEMO_MODE === 'BUILD_PART') {
44
+ console.log(" πŸ‘‰ Sending Code Demo");
45
+ const luaCode = `
46
+ ```lua
47
+ -- AI Generated Demo
48
+ local p = Instance.new("Part")
49
+ p.Name = "AI_Generated_Cube"
50
+ p.Color = Color3.fromRGB(0, 255, 100)
51
+ p.Size = Vector3.new(4, 4, 4)
52
+ p.Position = Vector3.new(0, 10, 0)
53
+ p.Anchored = true
54
+ p.Parent = workspace
55
+ print("Hello from the Server! I made a part.")
56
+ ```
57
+ `;
58
+ return res.send(luaCode); // Sending raw markdown/text
59
+ }
60
 
61
+ else if (DEMO_MODE === 'FETCH_SCRIPT') {
62
+ console.log(" πŸ‘‰ Sending Fetch Request Demo");
63
+ // This tells the plugin to find a script named "BikeLogic" and send it back
64
+ return res.json({
65
+ action: "read_script",
66
+ targetName: "BikeLogic" // CHANGE THIS to a script name that actually exists in your studio
67
+ });
 
 
 
 
 
 
 
 
 
 
68
  }
69
 
70
+ // Default: Just acknowledge
71
+ return res.json({ success: true, assetId: "req_" + Date.now() });
 
 
 
 
 
72
 
 
 
 
 
 
 
 
 
73
  } catch (err) {
74
+ console.error("Server Error:", err);
75
+ res.status(500).send("Server Error");
76
  }
77
  });
78
 
79
+ app.listen(PORT, () => console.log(`πŸš€ Server running on port ${PORT}. Demo Mode: ${DEMO_MODE}`));