Spaces:
Sleeping
Sleeping
Update app.js
Browse files
app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
// server.js - ALIGNED WITH PLUGIN
|
| 2 |
// Run: GEMINI_API_KEY=your_key node server.js
|
| 3 |
|
| 4 |
import express from "express";
|
|
@@ -25,7 +25,6 @@ const simpleId = (prefix = "") => prefix + Math.random().toString(36).slice(2, 9
|
|
| 25 |
/**
|
| 26 |
* STRICT VOCABULARY
|
| 27 |
* Only actions that exist in the Plugin's ACTION_HANDLERS are allowed here.
|
| 28 |
-
* Removed: create_union, create_rig, apply_animation, unweld, apply_texture
|
| 29 |
*/
|
| 30 |
const VALID_ACTIONS = [
|
| 31 |
"create_part",
|
|
@@ -40,7 +39,7 @@ const VALID_ACTIONS = [
|
|
| 40 |
"set_parent",
|
| 41 |
"delete",
|
| 42 |
"weld",
|
| 43 |
-
"list_workspace",
|
| 44 |
"finish"
|
| 45 |
];
|
| 46 |
|
|
@@ -69,12 +68,12 @@ const ACTION_SYNONYMS = {
|
|
| 69 |
"group": "create_model",
|
| 70 |
"parent": "set_parent",
|
| 71 |
"set_parent_to": "set_parent",
|
| 72 |
-
"join": "weld",
|
| 73 |
|
| 74 |
// Unsupported fallbacks (Safety)
|
| 75 |
-
"union": "create_model",
|
| 76 |
"create_union": "create_model",
|
| 77 |
-
"create_rig": "create_model"
|
| 78 |
};
|
| 79 |
|
| 80 |
function validateAndPrepareTasks(arr) {
|
|
@@ -100,8 +99,6 @@ function validateAndPrepareTasks(arr) {
|
|
| 100 |
}
|
| 101 |
|
| 102 |
if (!canonicalAction) {
|
| 103 |
-
// If AI hallucinates an action, we force it to a safe fallback or error
|
| 104 |
-
// Instead of crashing, let's log it and skip or error.
|
| 105 |
throw new Error(`Invalid action '${actionRaw}'. Supported: ${VALID_ACTIONS.join(", ")}`);
|
| 106 |
}
|
| 107 |
|
|
@@ -119,7 +116,6 @@ function validateAndPrepareTasks(arr) {
|
|
| 119 |
}
|
| 120 |
|
| 121 |
async function generateTasks(project) {
|
| 122 |
-
// Summarize project state
|
| 123 |
const historySample = (project.history || []).slice(-15).map(h => ({
|
| 124 |
status: h.status,
|
| 125 |
action: h.originalAction || "unknown",
|
|
@@ -128,7 +124,6 @@ async function generateTasks(project) {
|
|
| 128 |
|
| 129 |
const status = project.status || "unknown";
|
| 130 |
|
| 131 |
-
// We explicitly tell the AI about the Registry logic in the prompt
|
| 132 |
const systemPrompt = `
|
| 133 |
You are a Roblox Studio Assistant. Convert user descriptions into JSON tasks.
|
| 134 |
|
|
@@ -152,17 +147,18 @@ User Request:
|
|
| 152 |
${JSON.stringify({ description: project.description })}
|
| 153 |
`;
|
| 154 |
|
|
|
|
|
|
|
| 155 |
const resp = await ai.models.generateContent({
|
| 156 |
-
model: "gemini-
|
| 157 |
contents: systemPrompt,
|
| 158 |
-
generationConfig: { responseMimeType: "application/json" }
|
| 159 |
});
|
| 160 |
|
| 161 |
const text = resp?.text ? resp.text() : (resp?.response?.text() ?? "");
|
| 162 |
|
| 163 |
let arr;
|
| 164 |
try {
|
| 165 |
-
// Clean markdown code blocks if present
|
| 166 |
const cleanText = text.replace(/```json/g, "").replace(/```/g, "").trim();
|
| 167 |
arr = JSON.parse(cleanText);
|
| 168 |
} catch (err) {
|
|
@@ -173,7 +169,7 @@ ${JSON.stringify({ description: project.description })}
|
|
| 173 |
return validateAndPrepareTasks(arr);
|
| 174 |
}
|
| 175 |
|
| 176 |
-
// --- EXPRESS ROUTES
|
| 177 |
|
| 178 |
app.post("/projects", async (req, res) => {
|
| 179 |
try {
|
|
@@ -189,7 +185,7 @@ app.post("/projects", async (req, res) => {
|
|
| 189 |
_refs: {}
|
| 190 |
};
|
| 191 |
projects.set(projectId, project);
|
| 192 |
-
runProject(project);
|
| 193 |
return res.status(202).json({ projectId, status: "accepted" });
|
| 194 |
} catch (err) {
|
| 195 |
return res.status(500).json({ error: err.message });
|
|
@@ -201,14 +197,12 @@ app.post("/projects/:projectId/prompt", async (req, res) => {
|
|
| 201 |
if (!project) return res.status(404).json({ error: "project not found" });
|
| 202 |
|
| 203 |
const prompt = req.body.prompt;
|
| 204 |
-
// Append new prompt to description acts as "memory" for this simple prototype
|
| 205 |
project.description += `\nUser Update: ${prompt}`;
|
| 206 |
|
| 207 |
-
// Re-run generation logic for the new prompt
|
| 208 |
try {
|
| 209 |
-
const newTasks = await generateTasks({ ...project, description: prompt });
|
| 210 |
project.tasks.push(...newTasks);
|
| 211 |
-
project.commandQueue.push(...newTasks);
|
| 212 |
return res.json({ tasks: newTasks });
|
| 213 |
} catch(e) {
|
| 214 |
return res.status(500).json({error: e.message});
|
|
@@ -230,10 +224,8 @@ app.post("/projects/:projectId/result", (req, res) => {
|
|
| 230 |
|
| 231 |
const { commandId, status, message, target_ref } = req.body;
|
| 232 |
|
| 233 |
-
// Log history
|
| 234 |
project.history.push({ commandId, status, message, timestamp: Date.now() });
|
| 235 |
|
| 236 |
-
// Resolve waiting promise (if runProject is waiting)
|
| 237 |
if (project.pendingResults.has(commandId)) {
|
| 238 |
project.pendingResults.get(commandId).resolve({ status, message, target_ref });
|
| 239 |
project.pendingResults.delete(commandId);
|
|
@@ -252,9 +244,6 @@ async function runProject(project) {
|
|
| 252 |
return;
|
| 253 |
}
|
| 254 |
}
|
| 255 |
-
|
| 256 |
-
// In this simplified version, we just let the /next endpoint drain the queue.
|
| 257 |
-
// We don't strictly "block" here, making it more robust for connection drops.
|
| 258 |
}
|
| 259 |
|
| 260 |
app.listen(PORT, () => console.log(`BloxBuddy Brain running on ${PORT}`));
|
|
|
|
| 1 |
+
// server.js - ALIGNED WITH PLUGIN & FIXED SYNTAX
|
| 2 |
// Run: GEMINI_API_KEY=your_key node server.js
|
| 3 |
|
| 4 |
import express from "express";
|
|
|
|
| 25 |
/**
|
| 26 |
* STRICT VOCABULARY
|
| 27 |
* Only actions that exist in the Plugin's ACTION_HANDLERS are allowed here.
|
|
|
|
| 28 |
*/
|
| 29 |
const VALID_ACTIONS = [
|
| 30 |
"create_part",
|
|
|
|
| 39 |
"set_parent",
|
| 40 |
"delete",
|
| 41 |
"weld",
|
| 42 |
+
"list_workspace",
|
| 43 |
"finish"
|
| 44 |
];
|
| 45 |
|
|
|
|
| 68 |
"group": "create_model",
|
| 69 |
"parent": "set_parent",
|
| 70 |
"set_parent_to": "set_parent",
|
| 71 |
+
"join": "weld", // Fixed: JS comment syntax
|
| 72 |
|
| 73 |
// Unsupported fallbacks (Safety)
|
| 74 |
+
"union": "create_model", // Fixed: JS comment syntax
|
| 75 |
"create_union": "create_model",
|
| 76 |
+
"create_rig": "create_model" // Fixed: JS comment syntax
|
| 77 |
};
|
| 78 |
|
| 79 |
function validateAndPrepareTasks(arr) {
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
if (!canonicalAction) {
|
|
|
|
|
|
|
| 102 |
throw new Error(`Invalid action '${actionRaw}'. Supported: ${VALID_ACTIONS.join(", ")}`);
|
| 103 |
}
|
| 104 |
|
|
|
|
| 116 |
}
|
| 117 |
|
| 118 |
async function generateTasks(project) {
|
|
|
|
| 119 |
const historySample = (project.history || []).slice(-15).map(h => ({
|
| 120 |
status: h.status,
|
| 121 |
action: h.originalAction || "unknown",
|
|
|
|
| 124 |
|
| 125 |
const status = project.status || "unknown";
|
| 126 |
|
|
|
|
| 127 |
const systemPrompt = `
|
| 128 |
You are a Roblox Studio Assistant. Convert user descriptions into JSON tasks.
|
| 129 |
|
|
|
|
| 147 |
${JSON.stringify({ description: project.description })}
|
| 148 |
`;
|
| 149 |
|
| 150 |
+
// Use gemini-1.5-flash or 2.0-flash depending on what you have access to.
|
| 151 |
+
// 1.5-flash is very stable.
|
| 152 |
const resp = await ai.models.generateContent({
|
| 153 |
+
model: "gemini-1.5-flash",
|
| 154 |
contents: systemPrompt,
|
| 155 |
+
generationConfig: { responseMimeType: "application/json" }
|
| 156 |
});
|
| 157 |
|
| 158 |
const text = resp?.text ? resp.text() : (resp?.response?.text() ?? "");
|
| 159 |
|
| 160 |
let arr;
|
| 161 |
try {
|
|
|
|
| 162 |
const cleanText = text.replace(/```json/g, "").replace(/```/g, "").trim();
|
| 163 |
arr = JSON.parse(cleanText);
|
| 164 |
} catch (err) {
|
|
|
|
| 169 |
return validateAndPrepareTasks(arr);
|
| 170 |
}
|
| 171 |
|
| 172 |
+
// --- EXPRESS ROUTES ---
|
| 173 |
|
| 174 |
app.post("/projects", async (req, res) => {
|
| 175 |
try {
|
|
|
|
| 185 |
_refs: {}
|
| 186 |
};
|
| 187 |
projects.set(projectId, project);
|
| 188 |
+
runProject(project);
|
| 189 |
return res.status(202).json({ projectId, status: "accepted" });
|
| 190 |
} catch (err) {
|
| 191 |
return res.status(500).json({ error: err.message });
|
|
|
|
| 197 |
if (!project) return res.status(404).json({ error: "project not found" });
|
| 198 |
|
| 199 |
const prompt = req.body.prompt;
|
|
|
|
| 200 |
project.description += `\nUser Update: ${prompt}`;
|
| 201 |
|
|
|
|
| 202 |
try {
|
| 203 |
+
const newTasks = await generateTasks({ ...project, description: prompt });
|
| 204 |
project.tasks.push(...newTasks);
|
| 205 |
+
project.commandQueue.push(...newTasks);
|
| 206 |
return res.json({ tasks: newTasks });
|
| 207 |
} catch(e) {
|
| 208 |
return res.status(500).json({error: e.message});
|
|
|
|
| 224 |
|
| 225 |
const { commandId, status, message, target_ref } = req.body;
|
| 226 |
|
|
|
|
| 227 |
project.history.push({ commandId, status, message, timestamp: Date.now() });
|
| 228 |
|
|
|
|
| 229 |
if (project.pendingResults.has(commandId)) {
|
| 230 |
project.pendingResults.get(commandId).resolve({ status, message, target_ref });
|
| 231 |
project.pendingResults.delete(commandId);
|
|
|
|
| 244 |
return;
|
| 245 |
}
|
| 246 |
}
|
|
|
|
|
|
|
|
|
|
| 247 |
}
|
| 248 |
|
| 249 |
app.listen(PORT, () => console.log(`BloxBuddy Brain running on ${PORT}`));
|