| | |
| | let db = null; |
| |
|
| | export const initDB = (firebaseDB) => { |
| | db = firebaseDB; |
| | }; |
| |
|
| | const activeProjects = new Map(); |
| |
|
| |
|
| |
|
| | export const StateManager = { |
| | |
| | |
| | |
| | |
| | |
| | getProject: async (projectId) => { |
| | |
| | if (activeProjects.has(projectId)) { |
| | return activeProjects.get(projectId); |
| | } |
| |
|
| | |
| | if (db) { |
| | try { |
| | console.log(`[StateManager] 🟡 Hydrating project ${projectId} from DB...`); |
| | |
| | |
| | const snapshot = await db.ref(`projects/${projectId}`).once('value'); |
| | |
| | if (snapshot.exists()) { |
| | const dbData = snapshot.val(); |
| | |
| | |
| | const memoryObject = { |
| | ...dbData.info, |
| | thumbnail: dbData.thumbnail?.url || null, |
| | |
| | commandQueue: dbData.state?.commandQueue || [], |
| | workerHistory: dbData.state?.workerHistory || [], |
| | pmHistory: dbData.state?.pmHistory || [], |
| | failureCount: dbData.state?.failureCount || 0, |
| | gdd: dbData.state?.gdd || null, |
| | |
| | lastActive: Date.now(), |
| | lastUpdated: Date.now() |
| | }; |
| | |
| | |
| | activeProjects.set(projectId, memoryObject); |
| | console.log(`[StateManager] 🟢 Project ${projectId} hydrated.`); |
| | return memoryObject; |
| | } |
| | } catch (err) { |
| | console.error(`[StateManager] Error reading DB for ${projectId}:`, err); |
| | } |
| | } |
| |
|
| | return null; |
| | }, |
| |
|
| | |
| | |
| | |
| | |
| | |
| | updateProject: async (projectId, data) => { |
| | let current = activeProjects.get(projectId); |
| | |
| | |
| | if (!current) { |
| | current = await StateManager.getProject(projectId) || { |
| | commandQueue: [], workerHistory: [], pmHistory: [], failureCount: 0 |
| | }; |
| | } |
| |
|
| | const timestamp = Date.now(); |
| | |
| | |
| | const newData = { |
| | ...current, |
| | ...data, |
| | |
| | lastActive: Date.now(), |
| | lastUpdated: Date.now() |
| | }; |
| | activeProjects.set(projectId, newData); |
| |
|
| | |
| | if (db) { |
| | const updates = {}; |
| | |
| | |
| | |
| | |
| | |
| | if (data.status || data.stats || data.title) { |
| | updates[`projects/${projectId}/info/status`] = newData.status; |
| | if (newData.lastUpdated) updates[`projects/${projectId}/info/lastUpdated`] = newData.lastUpdated; |
| | |
| | } |
| |
|
| | |
| | if (data.workerHistory || data.pmHistory || data.commandQueue || data.failureCount || data.gdd) { |
| | updates[`projects/${projectId}/state/workerHistory`] = newData.workerHistory; |
| | updates[`projects/${projectId}/state/pmHistory`] = newData.pmHistory; |
| | updates[`projects/${projectId}/state/commandQueue`] = newData.commandQueue; |
| | updates[`projects/${projectId}/state/failureCount`] = newData.failureCount; |
| | if (newData.gdd) updates[`projects/${projectId}/state/gdd`] = newData.gdd; |
| | } |
| |
|
| | |
| | if (data.thumbnail) { |
| | updates[`projects/${projectId}/thumbnail`] = {url: data.thumbnail}; |
| | } |
| |
|
| | |
| | if (Object.keys(updates).length > 0) { |
| | db.ref().update(updates).catch(err => { |
| | console.error(`[StateManager] 🔴 DB Write Failed for ${projectId}:`, err); |
| | }); |
| | } |
| | } |
| |
|
| | return newData; |
| | }, |
| |
|
| | queueCommand: async (projectId, input) => { |
| | const project = await StateManager.getProject(projectId); |
| | if (!project) return; |
| | |
| | let command = null; |
| |
|
| | if (typeof input === 'object' && input.type && input.payload) { |
| | command = input; |
| | } |
| | else if (typeof input === 'string') { |
| | const rawResponse = input; |
| | if (rawResponse.includes("[ASK_PM:")) return; |
| | if (rawResponse.includes("[GENERATE_IMAGE:") && !rawResponse.includes("```")) return; |
| |
|
| | const codeMatch = rawResponse.match(/```(?:lua|luau)?([\s\S]*?)```/i); |
| | const readScriptMatch = rawResponse.match(/\[READ_SCRIPT:\s*(.*?)\]/); |
| | const readHierarchyMatch = rawResponse.match(/\[READ_HIERARCHY:\s*(.*?)\]/); |
| | const readLogsMatch = rawResponse.includes("[READ_LOGS]"); |
| |
|
| | if (codeMatch) command = { type: "EXECUTE", payload: codeMatch[1].trim() }; |
| | else if (readScriptMatch) command = { type: "READ_SCRIPT", payload: readScriptMatch[1].trim() }; |
| | else if (readHierarchyMatch) command = { type: "READ_HIERARCHY", payload: readHierarchyMatch[1].trim() }; |
| | else if (readLogsMatch) command = { type: "READ_LOGS", payload: null }; |
| | } |
| | |
| | if (command) { |
| | project.commandQueue.push(command); |
| | console.log(`[${projectId}] Queued Action: ${command.type}`); |
| | |
| | await StateManager.updateProject(projectId, { commandQueue: project.commandQueue }); |
| | } |
| | }, |
| | |
| | popCommand: async (projectId) => { |
| | const project = await StateManager.getProject(projectId); |
| | if (!project || !project.commandQueue.length) return null; |
| | |
| | const command = project.commandQueue.shift(); |
| | |
| | await StateManager.updateProject(projectId, { commandQueue: project.commandQueue }); |
| | |
| | return command; |
| | }, |
| |
|
| | cleanupMemory: () => { |
| | const now = Date.now(); |
| | const FOUR_HOURS = 4 * 60 * 60 * 1000; |
| | let count = 0; |
| |
|
| | for (const [id, data] of activeProjects.entries()) { |
| | |
| | |
| | if (!data.lastActive) { |
| | console.warn(`[StateManager] ⚠️ Project ${id} missing timestamp. Healing data...`); |
| | data.lastActive = now; |
| | continue; |
| | } |
| |
|
| | if (now - data.lastActive > FOUR_HOURS) { |
| | console.log(`[StateManager] 🧹 Removing expired project: ${id}`); |
| | activeProjects.delete(id); |
| | count++; |
| | } |
| | } |
| | |
| | if (count > 0) { |
| | console.log(`[StateManager] 🗑️ Cleaned ${count} projects from memory.`); |
| | } |
| | return count; |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | }; |