import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import { useSettings } from '~/lib/hooks/useSettings'; import { logStore } from '~/lib/stores/logs'; import { toast } from 'react-toastify'; import { Dialog, DialogRoot, DialogTitle, DialogDescription, DialogButton } from '~/components/ui/Dialog'; import { classNames } from '~/utils/classNames'; interface UpdateProgress { stage: 'fetch' | 'pull' | 'install' | 'build' | 'complete'; message: string; progress?: number; error?: string; details?: { changedFiles?: string[]; additions?: number; deletions?: number; commitMessages?: string[]; totalSize?: string; currentCommit?: string; remoteCommit?: string; updateReady?: boolean; }; } interface UpdateSettings { autoUpdate: boolean; notifyInApp: boolean; checkInterval: number; } const ProgressBar = ({ progress }: { progress: number }) => (
); const UpdateProgressDisplay = ({ progress }: { progress: UpdateProgress }) => (
{progress.message} {progress.progress}%
{progress.details && (
{progress.details.changedFiles && progress.details.changedFiles.length > 0 && (
Changed Files:
    {progress.details.changedFiles.map((file, index) => (
  • {file}
  • ))}
)} {progress.details.totalSize &&
Total size: {progress.details.totalSize}
} {progress.details.additions !== undefined && progress.details.deletions !== undefined && (
Changes: +{progress.details.additions}{' '} -{progress.details.deletions}
)} {progress.details.currentCommit && progress.details.remoteCommit && (
Updating from {progress.details.currentCommit} to {progress.details.remoteCommit}
)}
)}
); const UpdateTab = () => { const { isLatestBranch } = useSettings(); const [isChecking, setIsChecking] = useState(false); const [error, setError] = useState(null); const [updateSettings, setUpdateSettings] = useState(() => { const stored = localStorage.getItem('update_settings'); return stored ? JSON.parse(stored) : { autoUpdate: false, notifyInApp: true, checkInterval: 24, }; }); const [showUpdateDialog, setShowUpdateDialog] = useState(false); const [updateProgress, setUpdateProgress] = useState(null); useEffect(() => { localStorage.setItem('update_settings', JSON.stringify(updateSettings)); }, [updateSettings]); const checkForUpdates = async () => { console.log('Starting update check...'); setIsChecking(true); setError(null); setUpdateProgress(null); try { const branchToCheck = isLatestBranch ? 'main' : 'stable'; // Start the update check with streaming progress const response = await fetch('/api/update', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ branch: branchToCheck, autoUpdate: updateSettings.autoUpdate, }), }); if (!response.ok) { throw new Error(`Update check failed: ${response.statusText}`); } const reader = response.body?.getReader(); if (!reader) { throw new Error('No response stream available'); } // Read the stream while (true) { const { done, value } = await reader.read(); if (done) { break; } // Convert the chunk to text and parse the JSON const chunk = new TextDecoder().decode(value); const lines = chunk.split('\n').filter(Boolean); for (const line of lines) { try { const progress = JSON.parse(line) as UpdateProgress; setUpdateProgress(progress); if (progress.error) { setError(progress.error); } // If we're done, update the UI accordingly if (progress.stage === 'complete') { setIsChecking(false); if (!progress.error) { // Update check completed toast.success('Update check completed'); // Show update dialog only if there are changes and auto-update is disabled if (progress.details?.changedFiles?.length && progress.details.updateReady) { setShowUpdateDialog(true); } } } } catch (e) { console.error('Error parsing progress update:', e); } } } } catch (error) { setError(error instanceof Error ? error.message : 'Unknown error occurred'); logStore.logWarning('Update Check Failed', { type: 'update', message: error instanceof Error ? error.message : 'Unknown error occurred', }); } finally { setIsChecking(false); } }; const handleUpdate = async () => { setShowUpdateDialog(false); try { const branchToCheck = isLatestBranch ? 'main' : 'stable'; // Start the update with autoUpdate set to true to force the update const response = await fetch('/api/update', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ branch: branchToCheck, autoUpdate: true, }), }); if (!response.ok) { throw new Error(`Update failed: ${response.statusText}`); } // Handle the update progress stream const reader = response.body?.getReader(); if (!reader) { throw new Error('No response stream available'); } while (true) { const { done, value } = await reader.read(); if (done) { break; } const chunk = new TextDecoder().decode(value); const lines = chunk.split('\n').filter(Boolean); for (const line of lines) { try { const progress = JSON.parse(line) as UpdateProgress; setUpdateProgress(progress); if (progress.error) { setError(progress.error); toast.error('Update failed'); } if (progress.stage === 'complete' && !progress.error) { toast.success('Update completed successfully'); } } catch (e) { console.error('Error parsing update progress:', e); } } } } catch (error) { setError(error instanceof Error ? error.message : 'Unknown error occurred'); toast.error('Update failed'); } }; return (

Updates

Check for and manage application updates

{/* Update Settings Card */}

Update Settings

Automatic Updates

Automatically check and apply updates when available

In-App Notifications

Show notifications when updates are available

Check Interval

How often to check for updates

{/* Update Status Card */}

Update Status

{updateProgress?.details?.updateReady && !updateSettings.autoUpdate && ( )}
{/* Show progress information */} {updateProgress && } {error &&
{error}
} {/* Show update source information */}

Updates are fetched from: stackblitz-labs/bolt.diy ( {isLatestBranch ? 'main' : 'stable'} branch)

{updateProgress?.details?.currentCommit && updateProgress?.details?.remoteCommit && (

Current version: {updateProgress.details.currentCommit} Latest version: {updateProgress.details.remoteCommit}

)}
{/* Update dialog */} Update Available

A new version is available from stackblitz-labs/bolt.diy ( {isLatestBranch ? 'main' : 'stable'} branch)

{updateProgress?.details?.commitMessages && updateProgress.details.commitMessages.length > 0 && (

Commit Messages:

    {updateProgress.details.commitMessages.map((msg, index) => (
  • {msg}
  • ))}
)} {updateProgress?.details?.changedFiles && (

Changed Files:

    {updateProgress.details.changedFiles.map((file, index) => (
  • {file}
  • ))}
)} {updateProgress?.details?.totalSize && (

Total size: {updateProgress.details.totalSize}

)} {updateProgress?.details?.additions !== undefined && updateProgress?.details?.deletions !== undefined && (

Changes: +{updateProgress.details.additions}{' '} -{updateProgress.details.deletions}

)}
setShowUpdateDialog(false)}> Cancel Update Now
); }; export default UpdateTab;