everydaytok commited on
Commit
3446746
·
verified ·
1 Parent(s): 48725d9

Update stateManager.js

Browse files
Files changed (1) hide show
  1. stateManager.js +98 -31
stateManager.js CHANGED
@@ -4,9 +4,10 @@ let supabase = null;
4
  const activeProjects = new Map();
5
  const initializationLocks = new Set();
6
 
7
- const streamBuffers = new Map();
8
- const statusBuffers = new Map();
9
- const snapshotBuffers = new Map();
 
10
 
11
  export const initDB = () => {
12
  if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_KEY) {
@@ -24,7 +25,9 @@ export const StateManager = {
24
  getProject: async (projectId) => {
25
  if (activeProjects.has(projectId)) {
26
  const cached = activeProjects.get(projectId);
27
- if (cached.workerHistory && cached.pmHistory) return cached;
 
 
28
  }
29
 
30
  const { data: proj, error } = await supabase.from('projects').select('*').eq('id', projectId).single();
@@ -38,9 +41,14 @@ export const StateManager = {
38
  ...proj.info,
39
  id: proj.id,
40
  userId: proj.user_id,
 
 
 
41
  workerHistory: (chunks || []).filter(c => c.type === 'worker').reverse().flatMap(c => c.payload || []),
42
  pmHistory: (chunks || []).filter(c => c.type === 'pm').reverse().flatMap(c => c.payload || []),
 
43
  commandQueue: [],
 
44
  lastActive: Date.now()
45
  };
46
 
@@ -58,48 +66,87 @@ export const StateManager = {
58
  }
59
 
60
  try {
61
- const { data: chunks } = await supabase.from('message_chunks')
62
- .select('id, chunk_index, payload').eq('project_id', projectId).eq('type', type)
63
- .order('chunk_index', { ascending: false }).limit(1);
 
 
 
 
 
 
 
 
64
 
65
  const latest = chunks?.[0];
66
  const currentPayload = (latest && Array.isArray(latest.payload)) ? latest.payload : [];
 
67
 
68
  if (latest && currentPayload.length < 20) {
69
- await supabase.from('message_chunks').update({ payload: [...currentPayload, newMessage] }).eq('id', latest.id);
70
- } else {
71
- await supabase.from('message_chunks').insert({
72
- project_id: projectId, type, chunk_index: (latest?.chunk_index ?? -1) + 1, payload: [newMessage]
 
 
 
 
 
 
 
 
 
73
  });
 
74
  }
75
- } catch (e) { console.error("[StateManager] addHistory failed:", e.message); }
 
 
76
  },
77
 
78
- setStatus: (projectId, status) => { statusBuffers.set(projectId, status); },
79
- getStatus: (projectId) => statusBuffers.get(projectId) || "Idle",
 
 
 
 
 
80
 
81
  appendStream: (projectId, chunk) => {
82
- streamBuffers.set(projectId, (streamBuffers.get(projectId) || "") + chunk);
83
- snapshotBuffers.set(projectId, (snapshotBuffers.get(projectId) || "") + chunk);
 
 
 
84
  },
 
85
  appendSnapshotOnly: (projectId, chunk) => {
86
- snapshotBuffers.set(projectId, (snapshotBuffers.get(projectId) || "") + chunk);
 
87
  },
 
88
  popStream: (projectId) => {
89
  const content = streamBuffers.get(projectId) || "";
90
  streamBuffers.set(projectId, "");
91
  return content;
92
  },
93
- getSnapshot: (projectId) => snapshotBuffers.get(projectId) || "",
 
 
 
 
94
  clearSnapshot: (projectId) => {
95
  snapshotBuffers.delete(projectId);
96
  statusBuffers.delete(projectId);
97
  },
98
 
99
- // --- RESTORED FULL REGEX LOGIC ---
100
  queueCommand: async (projectId, input) => {
101
  let project = activeProjects.get(projectId);
102
- if (!project) project = await StateManager.getProject(projectId);
 
 
 
 
103
  if (!project) return;
104
 
105
  let command = null;
@@ -110,12 +157,10 @@ export const StateManager = {
110
  else if (typeof input === 'string') {
111
  const rawResponse = input;
112
 
113
- // Loop Prevention
114
  if (rawResponse.includes("[ASK_PM:")) return;
115
  if (rawResponse.includes("[ROUTE_TO_PM:")) return;
116
  if (rawResponse.includes("[GENERATE_IMAGE:") && !rawResponse.includes("```")) return;
117
 
118
- // REGEX Parsing (RESTORED)
119
  const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i);
120
  const readScriptMatch = rawResponse.match(/\[READ_SCRIPT:\s*(.*?)\]/);
121
  const readHierarchyMatch = rawResponse.match(/\[READ_HIERARCHY:\s*(.*?)\]/);
@@ -130,7 +175,7 @@ export const StateManager = {
130
  if (command) {
131
  if (!project.commandQueue) project.commandQueue = [];
132
  project.commandQueue.push(command);
133
- console.log(`[Memory] Queued command: ${command.type}`);
134
  }
135
  },
136
 
@@ -143,16 +188,30 @@ export const StateManager = {
143
  updateProject: async (projectId, data) => {
144
  if (activeProjects.has(projectId)) {
145
  const current = activeProjects.get(projectId);
146
- activeProjects.set(projectId, { ...current, ...data, lastActive: Date.now() });
 
147
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  const { data: currentDb } = await supabase.from('projects').select('info').eq('id', projectId).single();
 
149
  if (currentDb) {
150
- const mergedInfo = { ...currentDb.info, ...data };
151
  delete mergedInfo.commandQueue;
152
- await supabase.from('projects').update({
153
- info: mergedInfo,
154
- updated_at: new Date().toISOString() // RESTORED TIMESTAMP
155
- }).eq('id', projectId);
156
  }
157
  },
158
 
@@ -160,13 +219,21 @@ export const StateManager = {
160
  const now = Date.now();
161
  const FOUR_HOURS = 4 * 60 * 60 * 1000;
162
  let count = 0;
 
163
  for (const [id, data] of activeProjects.entries()) {
164
- if (now - (data.lastActive || now) > FOUR_HOURS) {
165
- activeProjects.delete(id); streamBuffers.delete(id); snapshotBuffers.delete(id); statusBuffers.delete(id);
 
 
 
 
 
 
166
  count++;
167
  }
168
  }
169
  return count;
170
  },
 
171
  getSupabaseClient: () => supabase
172
  };
 
4
  const activeProjects = new Map();
5
  const initializationLocks = new Set();
6
 
7
+ // --- REALTIME BUFFERS ---
8
+ const streamBuffers = new Map(); // Destructive (Plugin)
9
+ const statusBuffers = new Map(); // Status (Frontend)
10
+ const snapshotBuffers = new Map(); // Non-Destructive (Frontend)
11
 
12
  export const initDB = () => {
13
  if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_KEY) {
 
25
  getProject: async (projectId) => {
26
  if (activeProjects.has(projectId)) {
27
  const cached = activeProjects.get(projectId);
28
+ if (cached.workerHistory && cached.pmHistory) {
29
+ return cached;
30
+ }
31
  }
32
 
33
  const { data: proj, error } = await supabase.from('projects').select('*').eq('id', projectId).single();
 
41
  ...proj.info,
42
  id: proj.id,
43
  userId: proj.user_id,
44
+ thumbnail: proj.info?.thumbnail || null,
45
+ gdd: proj.info?.gdd || null,
46
+
47
  workerHistory: (chunks || []).filter(c => c.type === 'worker').reverse().flatMap(c => c.payload || []),
48
  pmHistory: (chunks || []).filter(c => c.type === 'pm').reverse().flatMap(c => c.payload || []),
49
+
50
  commandQueue: [],
51
+ failureCount: proj.info?.failureCount || 0,
52
  lastActive: Date.now()
53
  };
54
 
 
66
  }
67
 
68
  try {
69
+ const { data: chunks, error: fetchError } = await supabase.from('message_chunks')
70
+ .select('id, chunk_index, payload')
71
+ .eq('project_id', projectId)
72
+ .eq('type', type)
73
+ .order('chunk_index', { ascending: false })
74
+ .limit(10);
75
+
76
+ if (fetchError) {
77
+ console.error(`[DB Error] Failed to fetch history for ${projectId}:`, fetchError.message);
78
+ return;
79
+ }
80
 
81
  const latest = chunks?.[0];
82
  const currentPayload = (latest && Array.isArray(latest.payload)) ? latest.payload : [];
83
+ const latestIndex = (latest && typeof latest.chunk_index === 'number') ? latest.chunk_index : -1;
84
 
85
  if (latest && currentPayload.length < 20) {
86
+ const updatedPayload = [...currentPayload, newMessage];
87
+ const { error: updateError } = await supabase.from('message_chunks')
88
+ .update({ payload: updatedPayload })
89
+ .eq('id', latest.id);
90
+ if (updateError) console.error(`[DB Error] Update chunk failed:`, updateError.message);
91
+ }
92
+ else {
93
+ const nextIndex = latestIndex + 1;
94
+ const { error: insertError } = await supabase.from('message_chunks').insert({
95
+ project_id: projectId,
96
+ type,
97
+ chunk_index: nextIndex,
98
+ payload: [newMessage]
99
  });
100
+ if (insertError) console.error(`[DB Error] Insert new chunk failed:`, insertError.message);
101
  }
102
+ } catch (e) {
103
+ console.error("[StateManager] Unexpected error in addHistory:", e);
104
+ }
105
  },
106
 
107
+ setStatus: (projectId, status) => {
108
+ statusBuffers.set(projectId, status);
109
+ },
110
+
111
+ getStatus: (projectId) => {
112
+ return statusBuffers.get(projectId) || "Working...";
113
+ },
114
 
115
  appendStream: (projectId, chunk) => {
116
+ const currentDestructive = streamBuffers.get(projectId) || "";
117
+ streamBuffers.set(projectId, currentDestructive + chunk);
118
+
119
+ const currentSnapshot = snapshotBuffers.get(projectId) || "";
120
+ snapshotBuffers.set(projectId, currentSnapshot + chunk);
121
  },
122
+
123
  appendSnapshotOnly: (projectId, chunk) => {
124
+ const currentSnapshot = snapshotBuffers.get(projectId) || "";
125
+ snapshotBuffers.set(projectId, currentSnapshot + chunk);
126
  },
127
+
128
  popStream: (projectId) => {
129
  const content = streamBuffers.get(projectId) || "";
130
  streamBuffers.set(projectId, "");
131
  return content;
132
  },
133
+
134
+ getSnapshot: (projectId) => {
135
+ return snapshotBuffers.get(projectId) || "";
136
+ },
137
+
138
  clearSnapshot: (projectId) => {
139
  snapshotBuffers.delete(projectId);
140
  statusBuffers.delete(projectId);
141
  },
142
 
 
143
  queueCommand: async (projectId, input) => {
144
  let project = activeProjects.get(projectId);
145
+
146
+ if (!project) {
147
+ project = await StateManager.getProject(projectId);
148
+ }
149
+
150
  if (!project) return;
151
 
152
  let command = null;
 
157
  else if (typeof input === 'string') {
158
  const rawResponse = input;
159
 
 
160
  if (rawResponse.includes("[ASK_PM:")) return;
161
  if (rawResponse.includes("[ROUTE_TO_PM:")) return;
162
  if (rawResponse.includes("[GENERATE_IMAGE:") && !rawResponse.includes("```")) return;
163
 
 
164
  const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i);
165
  const readScriptMatch = rawResponse.match(/\[READ_SCRIPT:\s*(.*?)\]/);
166
  const readHierarchyMatch = rawResponse.match(/\[READ_HIERARCHY:\s*(.*?)\]/);
 
175
  if (command) {
176
  if (!project.commandQueue) project.commandQueue = [];
177
  project.commandQueue.push(command);
178
+ console.log(`[Memory] Queued command for ${projectId}: ${command.type}`);
179
  }
180
  },
181
 
 
188
  updateProject: async (projectId, data) => {
189
  if (activeProjects.has(projectId)) {
190
  const current = activeProjects.get(projectId);
191
+ const newData = { ...current, ...data, lastActive: Date.now() };
192
+ activeProjects.set(projectId, newData);
193
  }
194
+
195
+ const payload = {
196
+ info: {
197
+ title: data.title,
198
+ status: data.status,
199
+ stats: data.stats,
200
+ description: data.description,
201
+ failureCount: data.failureCount
202
+ }
203
+ };
204
+
205
+ Object.keys(payload.info).forEach(key => payload.info[key] === undefined && delete payload.info[key]);
206
+
207
  const { data: currentDb } = await supabase.from('projects').select('info').eq('id', projectId).single();
208
+
209
  if (currentDb) {
210
+ const mergedInfo = { ...currentDb.info, ...payload.info };
211
  delete mergedInfo.commandQueue;
212
+
213
+ const { error } = await supabase.from('projects').update({ info: mergedInfo }).eq('id', projectId);
214
+ if (error) console.error("[DB ERROR] Update Project failed:", error.message);
 
215
  }
216
  },
217
 
 
219
  const now = Date.now();
220
  const FOUR_HOURS = 4 * 60 * 60 * 1000;
221
  let count = 0;
222
+
223
  for (const [id, data] of activeProjects.entries()) {
224
+ if (!data.lastActive) data.lastActive = now;
225
+
226
+ if (now - data.lastActive > FOUR_HOURS) {
227
+ console.log(`[StateManager] 🧹 Removing expired project: ${id}`);
228
+ activeProjects.delete(id);
229
+ streamBuffers.delete(id);
230
+ snapshotBuffers.delete(id);
231
+ statusBuffers.delete(id);
232
  count++;
233
  }
234
  }
235
  return count;
236
  },
237
+
238
  getSupabaseClient: () => supabase
239
  };