Pepguy commited on
Commit
43cc6fc
·
verified ·
1 Parent(s): 6789c80

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +78 -9
app.js CHANGED
@@ -29,6 +29,7 @@ const simpleId = (prefix = "") => prefix + Math.random().toString(36).slice(2, 9
29
 
30
  /**
31
  * Action vocabulary — keep plugin and server in sync.
 
32
  */
33
  const VALID_ACTIONS = [
34
  "create_part",
@@ -55,17 +56,65 @@ const VALID_ACTIONS = [
55
  ];
56
 
57
  /**
58
- * Validate/prepare model output tasks (ensure allowed actions, add ids)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  */
60
  function validateAndPrepareTasks(arr) {
61
  if (!Array.isArray(arr)) throw new Error("Model output must be a JSON array of tasks.");
62
  return arr.map((t) => {
63
  if (!t || typeof t !== "object") throw new Error("Each task must be an object.");
64
  if (!t.action) throw new Error("Each task must have an action field.");
65
- if (!VALID_ACTIONS.includes(t.action)) throw new Error(`Invalid action from model: ${t.action}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  const id = t.id || simpleId("cmd_");
67
  const params = t.params && typeof t.params === "object" ? t.params : {};
68
- return { id, action: t.action, params, target_ref: t.target_ref || null };
 
69
  });
70
  }
71
 
@@ -79,6 +128,7 @@ async function generateTasks(project) {
79
  commandId: h.commandId,
80
  status: h.status,
81
  message: h.message ? (typeof h.message === "string" ? (h.message.length > 120 ? h.message.slice(0, 117) + "..." : h.message) : null) : null,
 
82
  target_ref: h.target_ref || null,
83
  ts: h.ts
84
  }));
@@ -99,7 +149,7 @@ async function generateTasks(project) {
99
  const systemPrompt = `
100
  You are a precise assistant that converts a high-level Roblox project description into an ordered JSON array of low-level editor tasks. Be spatially aware: reason about coordinates (x,y,z), sizes, rotations (euler degrees as {x,y,z}), orientations, and parent/child relationships. For every object you create, assign a stable "target_ref" string (short alphanumeric) that later tasks can reference.
101
 
102
- Return ONLY valid JSON — a single array, no commentary.
103
 
104
  Allowed actions (use exactly these): ${VALID_ACTIONS.join(", ")}.
105
 
@@ -112,9 +162,9 @@ Task object shape:
112
 
113
  Important rules:
114
  - Spatial parameters use explicit numeric fields: position: { x, y, z }, size: { x, y, z }, rotation: { x, y, z } (degrees).
115
- - When referencing an earlier-created object, use params.target_ref exactly as the ref string returned earlier.
116
  - Do not reference Roblox-specific paths (like Workspace.Model.Part).
117
- - Use only the allowed actions listed above.
118
  - If you need the plugin to evaluate the scene and return properties (for feedback), use actions "feedback_request" or "list_workspace".
119
  - End the sequence with an action "finish" when the project is complete.
120
 
@@ -171,7 +221,7 @@ app.post("/projects", async (req, res) => {
171
  commandQueue: [],
172
  pendingResults: new Map(),
173
  running: false,
174
- history: [], // { commandId, status, message, extra, target_ref, ts }
175
  _refs: {} // mapping: target_ref -> { commandId, extra, ts }
176
  };
177
 
@@ -191,7 +241,7 @@ app.post("/projects", async (req, res) => {
191
  });
192
 
193
  /**
194
- * Tweak / prompt endpoint — now includes full project context so the model knows what has been done.
195
  * Body: { prompt: string }
196
  */
197
  app.post("/projects/:projectId/prompt", async (req, res) => {
@@ -206,7 +256,7 @@ app.post("/projects/:projectId/prompt", async (req, res) => {
206
  status: project.status,
207
  queuedCount: project.commandQueue.length,
208
  tasksCount: project.tasks.length,
209
- recentHistory: (project.history || []).slice(-50).map(h => ({ commandId: h.commandId, status: h.status, target_ref: h.target_ref })),
210
  refsKeys: Object.keys(project._refs || {}).slice(0, 200)
211
  };
212
 
@@ -282,8 +332,15 @@ app.post("/projects/:projectId/result", (req, res) => {
282
  message: message || null,
283
  extra: extra || null,
284
  target_ref: target_ref || null,
 
285
  ts: Date.now()
286
  };
 
 
 
 
 
 
287
  project.history = project.history || [];
288
  project.history.push(entry);
289
 
@@ -356,6 +413,18 @@ async function runProject(project) {
356
  // push to queue
357
  project.commandQueue.push(task);
358
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  // Wait for plugin result for this task
360
  const result = await new Promise((resolve, reject) => {
361
  const timeoutMs = 3 * 60 * 1000; // 3 minutes
 
29
 
30
  /**
31
  * Action vocabulary — keep plugin and server in sync.
32
+ * We also accept some common synonyms from the model and map them to canonical allowed actions.
33
  */
34
  const VALID_ACTIONS = [
35
  "create_part",
 
56
  ];
57
 
58
  /**
59
+ * Map of alternate action names the model might output -> canonical action.
60
+ * Add more mappings here if you see similar synonyms from the model.
61
+ */
62
+ const ACTION_SYNONYMS = {
63
+ // common variants
64
+ "add_bone": "create_rig",
65
+ "add_bones": "create_rig",
66
+ "create_bone": "create_rig",
67
+ "attach_script_to": "attach_script",
68
+ "modify_script": "edit_script",
69
+ "move": "move_to",
70
+ "rotate": "rotate_to",
71
+ "set_parent_to": "set_parent",
72
+ // geometry synonyms
73
+ "union": "create_union",
74
+ "make_union": "create_union",
75
+ "mesh_part": "create_meshpart",
76
+ "add_script": "create_script",
77
+ "insert_script": "create_script",
78
+ "append_script": "attach_script",
79
+ "add_animation": "apply_animation"
80
+ };
81
+
82
+ /**
83
+ * Validate/prepare model output tasks (ensure allowed actions, add ids).
84
+ * Also maps synonyms to canonical actions (records the originalAction in the returned object).
85
  */
86
  function validateAndPrepareTasks(arr) {
87
  if (!Array.isArray(arr)) throw new Error("Model output must be a JSON array of tasks.");
88
  return arr.map((t) => {
89
  if (!t || typeof t !== "object") throw new Error("Each task must be an object.");
90
  if (!t.action) throw new Error("Each task must have an action field.");
91
+
92
+ // Normalize action: trim and coerce to string
93
+ let actionRaw = String(t.action).trim();
94
+ const actionLower = actionRaw.toLowerCase();
95
+
96
+ // Map synonyms (case-insensitive)
97
+ let canonicalAction = null;
98
+ // First try exact match against VALID_ACTIONS (case-sensitive)
99
+ if (VALID_ACTIONS.includes(actionRaw)) canonicalAction = actionRaw;
100
+ // Then try case-insensitive match
101
+ if (!canonicalAction) {
102
+ canonicalAction = VALID_ACTIONS.find(a => a.toLowerCase() === actionLower) || null;
103
+ }
104
+ // Then try synonyms map
105
+ if (!canonicalAction && ACTION_SYNONYMS[actionLower]) {
106
+ canonicalAction = ACTION_SYNONYMS[actionLower];
107
+ }
108
+
109
+ if (!canonicalAction) {
110
+ // Not recognized — include the offending action in the error to help debugging
111
+ throw new Error(`Invalid action from model: ${actionRaw}`);
112
+ }
113
+
114
  const id = t.id || simpleId("cmd_");
115
  const params = t.params && typeof t.params === "object" ? t.params : {};
116
+ // Attach originalAction for traceability in history/logs
117
+ return { id, action: canonicalAction, originalAction: actionRaw, params, target_ref: t.target_ref || null };
118
  });
119
  }
120
 
 
128
  commandId: h.commandId,
129
  status: h.status,
130
  message: h.message ? (typeof h.message === "string" ? (h.message.length > 120 ? h.message.slice(0, 117) + "..." : h.message) : null) : null,
131
+ originalAction: h.originalAction || null,
132
  target_ref: h.target_ref || null,
133
  ts: h.ts
134
  }));
 
149
  const systemPrompt = `
150
  You are a precise assistant that converts a high-level Roblox project description into an ordered JSON array of low-level editor tasks. Be spatially aware: reason about coordinates (x,y,z), sizes, rotations (euler degrees as {x,y,z}), orientations, and parent/child relationships. For every object you create, assign a stable "target_ref" string (short alphanumeric) that later tasks can reference.
151
 
152
+ Return ONLY valid JSON — a single array, no commentary, no extra text.
153
 
154
  Allowed actions (use exactly these): ${VALID_ACTIONS.join(", ")}.
155
 
 
162
 
163
  Important rules:
164
  - Spatial parameters use explicit numeric fields: position: { x, y, z }, size: { x, y, z }, rotation: { x, y, z } (degrees).
165
+ - When referencing an earlier-created object, use params.target_ref exactly as the ref string you returned earlier.
166
  - Do not reference Roblox-specific paths (like Workspace.Model.Part).
167
+ - Use only the allowed actions listed above (exactly those names).
168
  - If you need the plugin to evaluate the scene and return properties (for feedback), use actions "feedback_request" or "list_workspace".
169
  - End the sequence with an action "finish" when the project is complete.
170
 
 
221
  commandQueue: [],
222
  pendingResults: new Map(),
223
  running: false,
224
+ history: [], // { commandId, status, message, extra, target_ref, originalAction, ts }
225
  _refs: {} // mapping: target_ref -> { commandId, extra, ts }
226
  };
227
 
 
241
  });
242
 
243
  /**
244
+ * Tweak / prompt endpoint — includes project context so the model knows what has been done.
245
  * Body: { prompt: string }
246
  */
247
  app.post("/projects/:projectId/prompt", async (req, res) => {
 
256
  status: project.status,
257
  queuedCount: project.commandQueue.length,
258
  tasksCount: project.tasks.length,
259
+ recentHistory: (project.history || []).slice(-50).map(h => ({ commandId: h.commandId, status: h.status, target_ref: h.target_ref, originalAction: h.originalAction })),
260
  refsKeys: Object.keys(project._refs || {}).slice(0, 200)
261
  };
262
 
 
332
  message: message || null,
333
  extra: extra || null,
334
  target_ref: target_ref || null,
335
+ originalAction: null, // we'll try to attach if we can find the task in history/tasks
336
  ts: Date.now()
337
  };
338
+
339
+ // Attempt to find the originalAction from project's tasks or prior mapping
340
+ // (task may have been dispatched earlier; search tasks and history)
341
+ const possibleTask = (project.tasks || []).find(t => t.id === commandId) || (project.commandQueue || []).find(t => t.id === commandId);
342
+ if (possibleTask && possibleTask.originalAction) entry.originalAction = possibleTask.originalAction;
343
+
344
  project.history = project.history || [];
345
  project.history.push(entry);
346
 
 
413
  // push to queue
414
  project.commandQueue.push(task);
415
 
416
+ // record that we dispatched this task (for easier traceability)
417
+ project.history = project.history || [];
418
+ project.history.push({
419
+ commandId: task.id,
420
+ status: "dispatched",
421
+ message: null,
422
+ extra: null,
423
+ target_ref: task.target_ref || null,
424
+ originalAction: task.originalAction || null,
425
+ ts: Date.now()
426
+ });
427
+
428
  // Wait for plugin result for this task
429
  const result = await new Promise((resolve, reject) => {
430
  const timeoutMs = 3 * 60 * 1000; // 3 minutes