everydaytok commited on
Commit
2e4ad05
·
verified ·
1 Parent(s): 14e000f

Update stateManager.js

Browse files
Files changed (1) hide show
  1. stateManager.js +31 -123
stateManager.js CHANGED
@@ -4,10 +4,9 @@ let supabase = null;
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,9 +24,7 @@ export const StateManager = {
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,14 +38,9 @@ export const StateManager = {
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,75 +58,39 @@ export const StateManager = {
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);
@@ -142,43 +98,23 @@ export const StateManager = {
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;
153
-
154
- if (typeof input === 'object' && input.type && input.payload) {
155
- command = input;
156
- }
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*(.*?)\]/);
167
- const readLogsMatch = rawResponse.includes("[READ_LOGS]");
168
-
169
  if (codeMatch) command = { type: "EXECUTE", payload: codeMatch[1].trim() };
170
  else if (readScriptMatch) command = { type: "READ_SCRIPT", payload: readScriptMatch[1].trim() };
171
  else if (readHierarchyMatch) command = { type: "READ_HIERARCHY", payload: readHierarchyMatch[1].trim() };
172
- else if (readLogsMatch) command = { type: "READ_LOGS", payload: null };
173
  }
174
-
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
-
182
  popCommand: async (projectId) => {
183
  const project = activeProjects.get(projectId);
184
  if (!project || !project.commandQueue || project.commandQueue.length === 0) return null;
@@ -188,36 +124,16 @@ export const StateManager = {
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')
214
- .update({
215
- info: mergedInfo,
216
- updated_at: new Date().toISOString() // Force timestamp update
217
- })
218
- .eq('id', projectId);
219
-
220
- if (error) console.error("[DB ERROR] Update Project failed:", error.message);
221
  }
222
  },
223
 
@@ -225,21 +141,13 @@ export const StateManager = {
225
  const now = Date.now();
226
  const FOUR_HOURS = 4 * 60 * 60 * 1000;
227
  let count = 0;
228
-
229
  for (const [id, data] of activeProjects.entries()) {
230
- if (!data.lastActive) data.lastActive = now;
231
-
232
- if (now - data.lastActive > FOUR_HOURS) {
233
- console.log(`[StateManager] 🧹 Removing expired project: ${id}`);
234
- activeProjects.delete(id);
235
- streamBuffers.delete(id);
236
- snapshotBuffers.delete(id);
237
- statusBuffers.delete(id);
238
  count++;
239
  }
240
  }
241
  return count;
242
  },
243
-
244
  getSupabaseClient: () => supabase
245
  };
 
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
  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
  ...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
  }
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);
 
98
 
99
  queueCommand: async (projectId, input) => {
100
  let project = activeProjects.get(projectId);
101
+ if (!project) project = await StateManager.getProject(projectId);
 
 
 
 
102
  if (!project) return;
 
103
  let command = null;
104
+ if (typeof input === 'object' && input.type) command = input;
 
 
 
105
  else if (typeof input === 'string') {
106
+ const codeMatch = input.match(/```(?:lua|luau)?([\s\S]*?)```/i);
107
+ const readScriptMatch = input.match(/\[READ_SCRIPT:\s*(.*?)\]/);
108
+ const readHierarchyMatch = input.match(/\[READ_HIERARCHY:\s*(.*?)\]/);
 
 
 
 
 
 
 
 
109
  if (codeMatch) command = { type: "EXECUTE", payload: codeMatch[1].trim() };
110
  else if (readScriptMatch) command = { type: "READ_SCRIPT", payload: readScriptMatch[1].trim() };
111
  else if (readHierarchyMatch) command = { type: "READ_HIERARCHY", payload: readHierarchyMatch[1].trim() };
 
112
  }
 
113
  if (command) {
114
  if (!project.commandQueue) project.commandQueue = [];
115
  project.commandQueue.push(command);
 
116
  }
117
  },
 
118
  popCommand: async (projectId) => {
119
  const project = activeProjects.get(projectId);
120
  if (!project || !project.commandQueue || project.commandQueue.length === 0) return null;
 
124
  updateProject: async (projectId, data) => {
125
  if (activeProjects.has(projectId)) {
126
  const current = activeProjects.get(projectId);
127
+ activeProjects.set(projectId, { ...current, ...data, lastActive: Date.now() });
 
128
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  const { data: currentDb } = await supabase.from('projects').select('info').eq('id', projectId).single();
 
130
  if (currentDb) {
131
+ const mergedInfo = { ...currentDb.info, ...data };
132
  delete mergedInfo.commandQueue;
133
+ await supabase.from('projects').update({
134
+ info: mergedInfo,
135
+ updated_at: new Date().toISOString()
136
+ }).eq('id', projectId);
 
 
 
 
 
137
  }
138
  },
139
 
 
141
  const now = Date.now();
142
  const FOUR_HOURS = 4 * 60 * 60 * 1000;
143
  let count = 0;
 
144
  for (const [id, data] of activeProjects.entries()) {
145
+ if (now - (data.lastActive || now) > FOUR_HOURS) {
146
+ activeProjects.delete(id); streamBuffers.delete(id); snapshotBuffers.delete(id); statusBuffers.delete(id);
 
 
 
 
 
 
147
  count++;
148
  }
149
  }
150
  return count;
151
  },
 
152
  getSupabaseClient: () => supabase
153
  };