shinmentakezo07
feat(hf): add dashboard HF controls and align runtime with Spaces port defaults
5242538 | import { getConsistentMachineId } from "@/shared/utils/machineId"; | |
| import { isCloudEnabled } from "@/lib/localDb"; | |
| const INTERNAL_BASE_URL = | |
| process.env.BASE_URL || | |
| process.env.NEXT_PUBLIC_BASE_URL || | |
| process.env.NEXT_PUBLIC_APP_URL || | |
| `http://localhost:${process.env.PORT || 7860}`; | |
| /** | |
| * Cloud sync scheduler | |
| */ | |
| export class CloudSyncScheduler { | |
| machineId: string | null; | |
| intervalMinutes: number; | |
| intervalId: ReturnType<typeof setInterval> | null; | |
| constructor(machineId: string | null = null, intervalMinutes = 15) { | |
| this.machineId = machineId; | |
| this.intervalMinutes = intervalMinutes; | |
| this.intervalId = null; | |
| } | |
| /** | |
| * Initialize machine ID if not provided | |
| */ | |
| async initializeMachineId() { | |
| if (!this.machineId) { | |
| this.machineId = await getConsistentMachineId(); | |
| } | |
| } | |
| /** | |
| * Start periodic sync (delays first sync to allow server to be ready) | |
| */ | |
| async start() { | |
| if (this.intervalId) { | |
| return; | |
| } | |
| await this.initializeMachineId(); | |
| // Delay first sync by 30 seconds to ensure server is ready | |
| setTimeout(() => { | |
| this.syncWithRetry().catch(() => {}); | |
| }, 30000); | |
| // Then sync periodically | |
| this.intervalId = setInterval( | |
| () => { | |
| this.syncWithRetry().catch(() => {}); | |
| }, | |
| this.intervalMinutes * 60 * 1000 | |
| ); | |
| } | |
| /** | |
| * Stop periodic sync | |
| */ | |
| stop() { | |
| if (this.intervalId) { | |
| clearInterval(this.intervalId); | |
| this.intervalId = null; | |
| } | |
| } | |
| /** | |
| * Sync with retry logic (exponential backoff) | |
| */ | |
| async syncWithRetry(maxRetries = 1) { | |
| for (let attempt = 1; attempt <= maxRetries; attempt++) { | |
| try { | |
| const result = await this.sync(); | |
| return result; | |
| } catch (error) { | |
| if (attempt === maxRetries) { | |
| return null; | |
| } | |
| const delay = Math.min(1000 * Math.pow(2, attempt), 10000); // Max 10s | |
| await new Promise((resolve) => setTimeout(resolve, delay)); | |
| } | |
| } | |
| } | |
| /** | |
| * Perform sync via internal API route (handles token update to db.json) | |
| */ | |
| async sync() { | |
| // Check if cloud is enabled | |
| const enabled = await isCloudEnabled(); | |
| if (!enabled) { | |
| return null; | |
| } | |
| await this.initializeMachineId(); | |
| // Call internal API route which handles both sync and token update | |
| const response = await fetch(`${INTERNAL_BASE_URL}/api/sync/cloud`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ machineId: this.machineId, action: "sync" }), | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json().catch(() => ({})); | |
| throw new Error(errorData.error || "Sync failed"); | |
| } | |
| const result = await response.json(); | |
| return result; | |
| } | |
| /** | |
| * Check if scheduler is running | |
| */ | |
| isRunning() { | |
| return this.intervalId !== null; | |
| } | |
| } | |
| // Export a singleton instance if needed | |
| let cloudSyncScheduler: CloudSyncScheduler | null = null; | |
| export async function getCloudSyncScheduler(machineId: string | null = null, intervalMinutes = 15) { | |
| if (!cloudSyncScheduler) { | |
| cloudSyncScheduler = new CloudSyncScheduler(machineId, intervalMinutes); | |
| } | |
| return cloudSyncScheduler; | |
| } | |