everydaytok commited on
Commit
2dd2827
·
verified ·
1 Parent(s): 7afc828

Update stateManager.js

Browse files
Files changed (1) hide show
  1. stateManager.js +75 -104
stateManager.js CHANGED
@@ -1,55 +1,57 @@
1
- // stateManager.js
2
- let db = null;
3
-
4
- export const initDB = (firebaseDB) => {
5
- db = firebaseDB;
 
 
 
 
 
 
 
 
 
 
6
  };
7
 
8
  const activeProjects = new Map();
9
 
10
-
11
-
12
  export const StateManager = {
13
- /**
14
- * GET PROJECT
15
- * Hydrates from DB if not in memory.
16
- * Flattens { info, thumbnail, state } into a usable Memory Object.
17
- */
18
  getProject: async (projectId) => {
19
  // 1. Try Memory
20
  if (activeProjects.has(projectId)) {
21
  return activeProjects.get(projectId);
22
  }
23
 
24
- // 2. Try Firebase (Hydration)
25
- if (db) {
26
  try {
27
- console.log(`[StateManager] 🟡 Hydrating project ${projectId} from DB...`);
28
- // We fetch the root because the Server AI needs EVERYTHING (context, history) to function.
29
- // If this was a client-side Dashboard, we would only fetch /info.
30
- const snapshot = await db.ref(`projects/${projectId}`).once('value');
 
31
 
32
- if (snapshot.exists()) {
33
- const dbData = snapshot.val();
34
-
35
- // FLATTEN DB STRUCTURE -> MEMORY STRUCTURE
36
  const memoryObject = {
37
- ...dbData.info, // Title, stats, description
38
- thumbnail: dbData.thumbnail?.url || null,
39
- // State might be undefined if just created, so default it
40
- commandQueue: dbData.state?.commandQueue || [],
41
- workerHistory: dbData.state?.workerHistory || [],
42
- pmHistory: dbData.state?.pmHistory || [],
43
- failureCount: dbData.state?.failureCount || 0,
44
- gdd: dbData.state?.gdd || null,
45
- // CRITICAL: Initialize lastActive to NOW so it doesn't expire immediately
46
- lastActive: Date.now(),
47
- lastUpdated: Date.now()
48
  };
49
-
50
- // Load into Memory
 
 
 
 
 
51
  activeProjects.set(projectId, memoryObject);
52
- console.log(`[StateManager] 🟢 Project ${projectId} hydrated.`);
53
  return memoryObject;
54
  }
55
  } catch (err) {
@@ -60,11 +62,6 @@ export const StateManager = {
60
  return null;
61
  },
62
 
63
- /**
64
- * UPDATE PROJECT
65
- * Updates Memory immediately.
66
- * Performs granular updates to DB (separating Info vs State).
67
- */
68
  updateProject: async (projectId, data) => {
69
  let current = activeProjects.get(projectId);
70
 
@@ -74,53 +71,46 @@ export const StateManager = {
74
  commandQueue: [], workerHistory: [], pmHistory: [], failureCount: 0
75
  };
76
  }
77
-
78
- const timestamp = Date.now();
79
 
80
  // 1. Update Memory (Flat Merge)
81
  const newData = {
82
  ...current,
83
  ...data,
84
- // CRITICAL: Initialize lastActive to NOW so it doesn't expire immediately
85
- lastActive: Date.now(),
86
- lastUpdated: Date.now()
87
  };
88
  activeProjects.set(projectId, newData);
89
 
90
- // 2. Update Firebase (Nested Split)
91
- if (db) {
92
- const updates = {};
93
-
94
- // We assume 'data' contains the specific fields that changed.
95
- // However, to be safe and robust, we map the current state to the correct buckets.
96
-
97
- // Bucket: Info
98
- if (data.status || data.stats || data.title) {
99
- updates[`projects/${projectId}/info/status`] = newData.status;
100
- if (newData.lastUpdated) updates[`projects/${projectId}/info/lastUpdated`] = newData.lastUpdated;
101
- // Add other info fields if they are mutable
102
- }
103
 
104
- // Bucket: State (History, Queue, GDD) - This is the most frequent update
105
- if (data.workerHistory || data.pmHistory || data.commandQueue || data.failureCount || data.gdd) {
106
- updates[`projects/${projectId}/state/workerHistory`] = newData.workerHistory;
107
- updates[`projects/${projectId}/state/pmHistory`] = newData.pmHistory;
108
- updates[`projects/${projectId}/state/commandQueue`] = newData.commandQueue;
109
- updates[`projects/${projectId}/state/failureCount`] = newData.failureCount;
110
- if (newData.gdd) updates[`projects/${projectId}/state/gdd`] = newData.gdd;
111
- }
112
 
113
- // Bucket: Thumbnail (Only update if explicitly passed in 'data', avoiding re-upload of massive string)
114
- if (data.thumbnail) {
115
- updates[`projects/${projectId}/thumbnail`] = {url: data.thumbnail};
116
- }
117
 
118
- // Perform the update
119
- if (Object.keys(updates).length > 0) {
120
- db.ref().update(updates).catch(err => {
121
- console.error(`[StateManager] 🔴 DB Write Failed for ${projectId}:`, err);
122
- });
123
- }
124
  }
125
 
126
  return newData;
@@ -137,10 +127,13 @@ export const StateManager = {
137
  }
138
  else if (typeof input === 'string') {
139
  const rawResponse = input;
 
 
140
  if (rawResponse.includes("[ASK_PM:")) return;
141
- if (rawResponse.includes("[ROUTE_TO_PM:")) return;
142
  if (rawResponse.includes("[GENERATE_IMAGE:") && !rawResponse.includes("```")) return;
143
 
 
144
  const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i);
145
  const readScriptMatch = rawResponse.match(/\[READ_SCRIPT:\s*(.*?)\]/);
146
  const readHierarchyMatch = rawResponse.match(/\[READ_HIERARCHY:\s*(.*?)\]/);
@@ -155,7 +148,6 @@ export const StateManager = {
155
  if (command) {
156
  project.commandQueue.push(command);
157
  console.log(`[${projectId}] Queued Action: ${command.type}`);
158
- // Sync queue to DB
159
  await StateManager.updateProject(projectId, { commandQueue: project.commandQueue });
160
  }
161
  },
@@ -165,7 +157,6 @@ export const StateManager = {
165
  if (!project || !project.commandQueue.length) return null;
166
 
167
  const command = project.commandQueue.shift();
168
- // Sync queue to DB
169
  await StateManager.updateProject(projectId, { commandQueue: project.commandQueue });
170
 
171
  return command;
@@ -177,39 +168,19 @@ export const StateManager = {
177
  let count = 0;
178
 
179
  for (const [id, data] of activeProjects.entries()) {
180
- // FIX: If lastActive is missing/undefined/0, reset it to NOW.
181
- // This prevents the "54 year old project" bug where (now - 0) > 4 hours.
182
  if (!data.lastActive) {
183
- console.warn(`[StateManager] ⚠️ Project ${id} missing timestamp. Healing data...`);
184
  data.lastActive = now;
185
- continue; // Skip deleting this round
186
  }
187
 
188
  if (now - data.lastActive > FOUR_HOURS) {
189
- console.log(`[StateManager] 🧹 Removing expired project: ${id}`);
190
  activeProjects.delete(id);
191
  count++;
192
  }
193
  }
194
-
195
- if (count > 0) {
196
- console.log(`[StateManager] 🗑️ Cleaned ${count} projects from memory.`);
197
- }
198
  return count;
199
- }
200
-
201
- /* cleanupMemory: () => {
202
- const now = Date.now();
203
- const FOUR_HOURS = 4 * 60 * 60 * 1000;
204
- let count = 0;
205
 
206
- for (const [id, data] of activeProjects.entries()) {
207
- if (now - (data.lastActive || 0) > FOUR_HOURS) {
208
- activeProjects.delete(id);
209
- count++;
210
- }
211
- }
212
- console.log(`[StateManager] 🧹 Memory Cleanup: Removed ${count} inactive projects.`);
213
- return count;
214
- } */
215
  };
 
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ // --- SUPABASE SETUP ---
4
+ let supabase = null;
5
+
6
+ export const initDB = () => {
7
+ if (process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_ROLE_KEY) {
8
+ supabase = createClient(
9
+ process.env.SUPABASE_URL,
10
+ process.env.SUPABASE_SERVICE_ROLE_KEY
11
+ );
12
+ console.log("⚡ Supabase Client Initialized");
13
+ } else {
14
+ console.warn("⚠️ Supabase Credentials Missing - Memory Mode Only");
15
+ }
16
  };
17
 
18
  const activeProjects = new Map();
19
 
 
 
20
  export const StateManager = {
 
 
 
 
 
21
  getProject: async (projectId) => {
22
  // 1. Try Memory
23
  if (activeProjects.has(projectId)) {
24
  return activeProjects.get(projectId);
25
  }
26
 
27
+ // 2. Try Supabase
28
+ if (supabase) {
29
  try {
30
+ const { data, error } = await supabase
31
+ .from('projects')
32
+ .select('*')
33
+ .eq('id', projectId)
34
+ .single();
35
 
36
+ if (data) {
37
+ // Flatten DB structure (info + state) to Memory Object
 
 
38
  const memoryObject = {
39
+ ...data.info, // Title, stats, description, status
40
+ ...data.state, // workerHistory, pmHistory, queue
41
+ id: data.id,
42
+ userId: data.user_id,
43
+ thumbnail: data.thumbnail,
44
+ lastActive: Date.now(),
45
+ lastUpdated: Date.now()
 
 
 
 
46
  };
47
+
48
+ // Ensure arrays exist
49
+ if (!memoryObject.commandQueue) memoryObject.commandQueue = [];
50
+ if (!memoryObject.workerHistory) memoryObject.workerHistory = [];
51
+ if (!memoryObject.pmHistory) memoryObject.pmHistory = [];
52
+ if (!memoryObject.failureCount) memoryObject.failureCount = 0;
53
+
54
  activeProjects.set(projectId, memoryObject);
 
55
  return memoryObject;
56
  }
57
  } catch (err) {
 
62
  return null;
63
  },
64
 
 
 
 
 
 
65
  updateProject: async (projectId, data) => {
66
  let current = activeProjects.get(projectId);
67
 
 
71
  commandQueue: [], workerHistory: [], pmHistory: [], failureCount: 0
72
  };
73
  }
 
 
74
 
75
  // 1. Update Memory (Flat Merge)
76
  const newData = {
77
  ...current,
78
  ...data,
79
+ lastActive: Date.now(),
80
+ lastUpdated: Date.now()
 
81
  };
82
  activeProjects.set(projectId, newData);
83
 
84
+ // 2. Update Supabase
85
+ if (supabase) {
86
+ // We separate data into columns: 'info' and 'state'
87
+ const infoUpdate = {
88
+ title: newData.title,
89
+ stats: newData.stats,
90
+ description: newData.description,
91
+ status: newData.status,
92
+ lastUpdated: newData.lastUpdated
93
+ };
 
 
 
94
 
95
+ const stateUpdate = {
96
+ workerHistory: newData.workerHistory,
97
+ pmHistory: newData.pmHistory,
98
+ commandQueue: newData.commandQueue,
99
+ failureCount: newData.failureCount,
100
+ gdd: newData.gdd
101
+ };
 
102
 
103
+ const updatePayload = {
104
+ info: infoUpdate,
105
+ state: stateUpdate,
106
+ };
107
 
108
+ if (data.thumbnail) updatePayload.thumbnail = data.thumbnail;
109
+
110
+ await supabase
111
+ .from('projects')
112
+ .update(updatePayload)
113
+ .eq('id', projectId);
114
  }
115
 
116
  return newData;
 
127
  }
128
  else if (typeof input === 'string') {
129
  const rawResponse = input;
130
+
131
+ // Filters
132
  if (rawResponse.includes("[ASK_PM:")) return;
133
+ if (rawResponse.includes("[ROUTE_TO_PM:")) return;
134
  if (rawResponse.includes("[GENERATE_IMAGE:") && !rawResponse.includes("```")) return;
135
 
136
+ // Extractors
137
  const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i);
138
  const readScriptMatch = rawResponse.match(/\[READ_SCRIPT:\s*(.*?)\]/);
139
  const readHierarchyMatch = rawResponse.match(/\[READ_HIERARCHY:\s*(.*?)\]/);
 
148
  if (command) {
149
  project.commandQueue.push(command);
150
  console.log(`[${projectId}] Queued Action: ${command.type}`);
 
151
  await StateManager.updateProject(projectId, { commandQueue: project.commandQueue });
152
  }
153
  },
 
157
  if (!project || !project.commandQueue.length) return null;
158
 
159
  const command = project.commandQueue.shift();
 
160
  await StateManager.updateProject(projectId, { commandQueue: project.commandQueue });
161
 
162
  return command;
 
168
  let count = 0;
169
 
170
  for (const [id, data] of activeProjects.entries()) {
 
 
171
  if (!data.lastActive) {
 
172
  data.lastActive = now;
173
+ continue;
174
  }
175
 
176
  if (now - data.lastActive > FOUR_HOURS) {
 
177
  activeProjects.delete(id);
178
  count++;
179
  }
180
  }
 
 
 
 
181
  return count;
182
+ },
 
 
 
 
 
183
 
184
+ // Exporting raw client for credits check in App.js
185
+ getSupabaseClient: () => supabase
 
 
 
 
 
 
 
186
  };