everydaycats commited on
Commit
b8d04a8
·
verified ·
1 Parent(s): be18b86

Update stateManager.js

Browse files
Files changed (1) hide show
  1. stateManager.js +119 -18
stateManager.js CHANGED
@@ -1,31 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
1
  const activeProjects = new Map();
2
 
3
  export const StateManager = {
 
 
 
 
4
  getProject: async (projectId) => {
5
- if (activeProjects.has(projectId)) return activeProjects.get(projectId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  return null;
7
  },
8
 
 
 
 
 
9
  updateProject: async (projectId, data) => {
10
- const current = activeProjects.get(projectId) || {
11
- commandQueue: [],
12
- workerHistory: [],
13
- pmHistory: [],
14
- failureCount: 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  };
16
- const newData = { ...current, ...data, lastActive: Date.now() };
 
17
  activeProjects.set(projectId, newData);
 
 
 
 
 
 
 
 
18
  return newData;
19
  },
20
 
21
- // PARSE AI RESPONSE FOR COMMANDS
 
 
 
22
  queueCommand: async (projectId, input) => {
23
- const project = activeProjects.get(projectId);
 
24
  if (!project) return;
25
 
26
  let command = null;
27
 
28
- // A. HANDLE PRE-FORMED OBJECTS (e.g. Image Assets)
29
  if (typeof input === 'object' && input.type && input.payload) {
30
  command = input;
31
  }
@@ -33,19 +108,15 @@ export const StateManager = {
33
  else if (typeof input === 'string') {
34
  const rawResponse = input;
35
 
36
- // 1. Internal Consultation Check (Do NOT queue for Plugin)
37
  if (rawResponse.includes("[ASK_PM:")) {
38
  console.log(`[${projectId}] Skipped Plugin Queue (Internal PM Consultation)`);
39
  return;
40
  }
41
 
42
- // 2. Image Generation Check (Handled by Server Controller, not Plugin directly usually)
43
  if (rawResponse.includes("[GENERATE_IMAGE:") && !rawResponse.includes("```")) {
44
- // If it's ONLY an image request and no code, we return early as server handles it.
45
  return;
46
  }
47
 
48
- // 3. Regex Parsers
49
  const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i);
50
  const readScriptMatch = rawResponse.match(/\[READ_SCRIPT:\s*(.*?)\]/);
51
  const readHierarchyMatch = rawResponse.match(/\[READ_HIERARCHY:\s*(.*?)\]/);
@@ -65,18 +136,48 @@ export const StateManager = {
65
  if (command) {
66
  project.commandQueue.push(command);
67
  console.log(`[${projectId}] Queued Action: ${command.type}`);
 
 
 
68
  } else {
69
  if (typeof input === 'string' && input.length < 500) {
70
  console.log(`[${projectId}] Chatter (No Command):`, input);
71
  }
72
  }
73
-
74
- activeProjects.set(projectId, project);
75
  },
76
 
 
 
 
 
77
  popCommand: async (projectId) => {
78
- const project = activeProjects.get(projectId);
79
  if (!project || !project.commandQueue.length) return null;
80
- return project.commandQueue.shift();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
82
  };
 
1
+ import admin from 'firebase-admin';
2
+
3
+ // Initialize Firebase (Assuming it's initialized in server.js, but we need the ref here)
4
+ // We will rely on server.js to initialize the app, or we can lazy-load the db ref.
5
+ let db = null;
6
+
7
+ // This function allows server.js to pass the initialized DB instance
8
+ export const initDB = (firebaseDB) => {
9
+ db = firebaseDB;
10
+ };
11
+
12
  const activeProjects = new Map();
13
 
14
  export const StateManager = {
15
+ /**
16
+ * GET PROJECT
17
+ * Strategy: Memory First -> DB Fallback -> Null
18
+ */
19
  getProject: async (projectId) => {
20
+ // 1. Try Memory
21
+ if (activeProjects.has(projectId)) {
22
+ return activeProjects.get(projectId);
23
+ }
24
+
25
+ // 2. Try Firebase (Hydration)
26
+ if (db) {
27
+ try {
28
+ console.log(`[StateManager] 🟡 Hydrating project ${projectId} from DB...`);
29
+ const snapshot = await db.ref(`projects/${projectId}`).once('value');
30
+ if (snapshot.exists()) {
31
+ const projectData = snapshot.val();
32
+
33
+ // Ensure arrays exist (Firebase doesn't store empty arrays)
34
+ if (!projectData.commandQueue) projectData.commandQueue = [];
35
+ if (!projectData.workerHistory) projectData.workerHistory = [];
36
+ if (!projectData.pmHistory) projectData.pmHistory = [];
37
+
38
+ // Load into Memory
39
+ activeProjects.set(projectId, projectData);
40
+ console.log(`[StateManager] 🟢 Project ${projectId} hydrated.`);
41
+ return projectData;
42
+ }
43
+ } catch (err) {
44
+ console.error(`[StateManager] Error reading DB for ${projectId}:`, err);
45
+ }
46
+ }
47
+
48
  return null;
49
  },
50
 
51
+ /**
52
+ * UPDATE PROJECT
53
+ * Strategy: Update Memory -> Async Write to DB
54
+ */
55
  updateProject: async (projectId, data) => {
56
+ // We get the current state from memory (or hydrate it if missing, though usually called after get)
57
+ let current = activeProjects.get(projectId);
58
+
59
+ // If it wasn't in memory, try to get it first to avoid overwriting with partial data
60
+ if (!current) {
61
+ current = await StateManager.getProject(projectId) || {
62
+ commandQueue: [],
63
+ workerHistory: [],
64
+ pmHistory: [],
65
+ failureCount: 0
66
+ };
67
+ }
68
+
69
+ const timestamp = Date.now();
70
+
71
+ // Merge Data
72
+ const newData = {
73
+ ...current,
74
+ ...data,
75
+ lastUpdated: timestamp, // UCT timestamp for sync
76
+ lastActive: timestamp
77
  };
78
+
79
+ // 1. Write to Memory
80
  activeProjects.set(projectId, newData);
81
+
82
+ // 2. Write to Firebase (Fire and Forget)
83
+ if (db) {
84
+ db.ref(`projects/${projectId}`).update(data).catch(err => {
85
+ console.error(`[StateManager] 🔴 DB Write Failed for ${projectId}:`, err);
86
+ });
87
+ }
88
+
89
  return newData;
90
  },
91
 
92
+ /**
93
+ * QUEUE COMMAND
94
+ * Parses input, updates command list in Memory & DB
95
+ */
96
  queueCommand: async (projectId, input) => {
97
+ // Ensure project is loaded
98
+ const project = await StateManager.getProject(projectId);
99
  if (!project) return;
100
 
101
  let command = null;
102
 
103
+ // A. HANDLE PRE-FORMED OBJECTS
104
  if (typeof input === 'object' && input.type && input.payload) {
105
  command = input;
106
  }
 
108
  else if (typeof input === 'string') {
109
  const rawResponse = input;
110
 
 
111
  if (rawResponse.includes("[ASK_PM:")) {
112
  console.log(`[${projectId}] Skipped Plugin Queue (Internal PM Consultation)`);
113
  return;
114
  }
115
 
 
116
  if (rawResponse.includes("[GENERATE_IMAGE:") && !rawResponse.includes("```")) {
 
117
  return;
118
  }
119
 
 
120
  const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i);
121
  const readScriptMatch = rawResponse.match(/\[READ_SCRIPT:\s*(.*?)\]/);
122
  const readHierarchyMatch = rawResponse.match(/\[READ_HIERARCHY:\s*(.*?)\]/);
 
136
  if (command) {
137
  project.commandQueue.push(command);
138
  console.log(`[${projectId}] Queued Action: ${command.type}`);
139
+
140
+ // Persist the updated queue to both
141
+ await StateManager.updateProject(projectId, { commandQueue: project.commandQueue });
142
  } else {
143
  if (typeof input === 'string' && input.length < 500) {
144
  console.log(`[${projectId}] Chatter (No Command):`, input);
145
  }
146
  }
 
 
147
  },
148
 
149
+ /**
150
+ * POP COMMAND
151
+ * Removes from Memory & DB
152
+ */
153
  popCommand: async (projectId) => {
154
+ const project = await StateManager.getProject(projectId);
155
  if (!project || !project.commandQueue.length) return null;
156
+
157
+ const command = project.commandQueue.shift();
158
+
159
+ // Sync the removal to DB
160
+ await StateManager.updateProject(projectId, { commandQueue: project.commandQueue });
161
+
162
+ return command;
163
+ },
164
+
165
+ /**
166
+ * CLEANUP
167
+ * Removes projects from Memory if inactive for > 4 hours
168
+ */
169
+ cleanupMemory: () => {
170
+ const now = Date.now();
171
+ const FOUR_HOURS = 4 * 60 * 60 * 1000;
172
+ let count = 0;
173
+
174
+ for (const [id, data] of activeProjects.entries()) {
175
+ if (now - (data.lastActive || 0) > FOUR_HOURS) {
176
+ activeProjects.delete(id);
177
+ count++;
178
+ }
179
+ }
180
+ console.log(`[StateManager] 🧹 Memory Cleanup: Removed ${count} inactive projects.`);
181
+ return count;
182
  }
183
  };