Merge pull request #148 from replayio/PRO-1467-Update-deploy-modal

Update deploy chat modal for clarity and styling
This commit is contained in:
Strider 2025-06-10 14:57:30 -04:00 committed by GitHub
commit aa28274f02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 422 additions and 215 deletions

View File

@ -6,7 +6,7 @@ import { generateRandomId } from '~/lib/replay/ReplayProtocolClient';
import { workbenchStore } from '~/lib/stores/workbench';
import { chatStore } from '~/lib/stores/chat';
import { database } from '~/lib/persistence/chats';
import { deployRepository } from '~/lib/replay/Deploy';
import { deployRepository, downloadRepository } from '~/lib/replay/Deploy';
import DeployChatModal from './components/DeployChatModal';
ReactModal.setAppElement('#root');
@ -22,14 +22,61 @@ export function DeployChatButton() {
const [deploySettings, setDeploySettings] = useState<DeploySettingsDatabase | null>(null);
const [error, setError] = useState<string | null>(null);
const [status, setStatus] = useState<DeployStatus>(DeployStatus.NotStarted);
const [databaseFound, setDatabaseFound] = useState(false);
const handleCheckDatabase = async () => {
const repositoryId = workbenchStore.repositoryId.get();
if (!repositoryId) {
toast.error('No repository ID found');
return;
}
try {
const repositoryContents = await downloadRepository(repositoryId);
// Convert base64 to blob
const byteCharacters = atob(repositoryContents);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'application/zip' });
const reader = new FileReader();
reader.onload = (event) => {
if (!event.target?.result) {
setIsModalOpen(false);
toast.error('Could not read repository contents');
return;
}
const zipContents = event.target.result as string;
const directoryToFind = 'supabase';
if (zipContents.includes(directoryToFind)) {
setDatabaseFound(true);
} else {
setDatabaseFound(false);
}
};
reader.readAsText(blob);
} catch (error) {
setIsModalOpen(false);
console.error('Error downloading repository:', error);
toast.error('Failed to download repository');
}
};
const handleOpenModal = async () => {
const chatId = chatStore.currentChat.get()?.id;
if (!chatId) {
toast.error('No chat open');
toast.error('No chat ID found');
return;
}
await handleCheckDatabase();
const existingSettings = await database.getChatDeploySettings(chatId);
setIsModalOpen(true);
@ -146,11 +193,10 @@ export function DeployChatButton() {
<>
<button
className="flex gap-2 bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
onClick={() => {
handleOpenModal();
}}
onClick={handleOpenModal}
>
Deploy
<div className="i-ph:rocket-launch-fill text-[1.3em]" />
Deploy App
</button>
<DeployChatModal
isModalOpen={isModalOpen}
@ -160,6 +206,7 @@ export function DeployChatButton() {
setDeploySettings={setDeploySettings}
error={error}
handleDeploy={handleDeploy}
databaseFound={databaseFound}
/>
</>
);

View File

@ -1,4 +1,5 @@
import { DeployStatus } from '~/components/header/DeployChat/DeployChatButton';
import DeploymentSuccessful from './DeploymentSuccessful';
interface DeployChatModalProps {
isModalOpen: boolean;
@ -8,6 +9,7 @@ interface DeployChatModalProps {
setDeploySettings: (settings: any) => void;
error: string | null;
handleDeploy: () => void;
databaseFound: boolean;
}
const DeployChatModal = ({
@ -18,234 +20,358 @@ const DeployChatModal = ({
setDeploySettings,
error,
handleDeploy,
databaseFound,
}: DeployChatModalProps) => {
const handleOverlayClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
setIsModalOpen(false);
}
};
return (
<>
{isModalOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 z-40 flex items-center justify-center">
<div className="bg-white rounded-lg p-6 max-w-2xl w-full z-50">
<div
className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-40 flex items-center justify-center"
onClick={handleOverlayClick}
>
<div
className="bg-bolt-elements-background-depth-1 rounded-lg p-8 max-w-2xl w-full z-50 border border-bolt-elements-borderColor overflow-y-auto max-h-[95vh]"
onClick={(e) => e.stopPropagation()}
>
{status === DeployStatus.Succeeded ? (
<>
<div className="text-center mb-2">Deployment Succeeded</div>
<div className="text-center">
<div className="flex justify-center gap-2 mt-4">
<a href={deploySettings?.siteURL} target="_blank" rel="noopener noreferrer">
<button className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition-colors">
{deploySettings?.siteURL}
</button>
</a>
<button
onClick={() => {
setIsModalOpen(false);
}}
className="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400"
>
Close
</button>
</div>
</div>
</>
<DeploymentSuccessful deploySettings={deploySettings} setIsModalOpen={setIsModalOpen} />
) : (
<>
<h2 className="text-xl font-semibold text-center mb-4">Deploy</h2>
<div className="text-center mb-4">Deploy this chat's app to production.</div>
<h2 className="text-2xl font-bold mb-6 text-bolt-elements-textPrimary text-center">
Deploy Your Application
</h2>
<div className="text-center mb-6 text-bolt-elements-textSecondary">
<p className="mb-2">
Deploy your chat application to production using Netlify{databaseFound ? ' and Supabase' : ''}.
</p>
<p className="mb-2">This process will:</p>
<div className="flex justify-center">
<ul className="text-left list-disc list-inside mb-4 inline-block">
<li>Create a new Netlify site or update an existing one</li>
{databaseFound && <li>Set up your database with Supabase</li>}
<li>Configure all necessary environment variables</li>
<li>Deploy your application with production settings</li>
</ul>
</div>
</div>
<div className="mb-8 p-4 bg-bolt-elements-background-depth-2 rounded-lg border border-bolt-elements-borderColor">
<h3 className="text-sm font-medium text-bolt-elements-textPrimary mb-2">Before you begin:</h3>
<p className="text-xs text-bolt-elements-textSecondary mb-3 whitespace-pre-wrap">
You'll need accounts with both Netlify and {databaseFound ? 'Supabase ' : ''}to deploy your
application. If you haven't already, please sign up using the links below:
</p>
<div className="flex flex-col gap-2">
<a
href="https://app.netlify.com/signup"
target="_blank"
rel="noopener noreferrer"
className="text-xs text-green-500 hover:text-green-600 transition-colors"
>
Sign up for Netlify
</a>
{databaseFound && (
<a
href="https://supabase.com/dashboard/sign-up"
target="_blank"
rel="noopener noreferrer"
className="text-xs text-green-500 hover:text-green-600 transition-colors"
>
Sign up for Supabase
</a>
)}
</div>
</div>
{deploySettings?.siteURL && (
<div className="text-center mb-4">
<span className="text-lg text-gray-700 pr-2">Existing site:</span>
<div className="text-center mb-6">
<span className="text-lg text-bolt-elements-textPrimary pr-2">Existing site:</span>
<a href={deploySettings?.siteURL} target="_blank" rel="noopener noreferrer">
<button className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition-colors">
<button className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors font-medium">
{deploySettings?.siteURL}
</button>
</a>
</div>
)}
<div className="grid grid-cols-2 gap-2 mb-4 items-center">
<label className="text-sm font-lg text-gray-700 text-right">Netlify Auth Token:</label>
<input
name="netlifyAuthToken"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.netlify?.authToken}
placeholder="nfp_..."
onChange={(e) => {
const netlify = {
authToken: e.target.value,
siteId: deploySettings?.netlify?.siteId || '',
createInfo: deploySettings?.netlify?.createInfo || undefined,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
<label className="text-sm font-lg text-gray-700 text-right">Netlify Site ID (existing site):</label>
<input
name="netlifySiteId"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.netlify?.siteId}
placeholder="123e4567-..."
onChange={(e) => {
const netlify = {
authToken: deploySettings?.netlify?.authToken || '',
siteId: e.target.value,
createInfo: deploySettings?.netlify?.createInfo || undefined,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
<label className="text-sm font-lg text-gray-700 text-right">Netlify Account Slug (new site):</label>
<input
name="netlifyAccountSlug"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.netlify?.createInfo?.accountSlug}
placeholder="abc..."
onChange={(e) => {
const createInfo = {
accountSlug: e.target.value,
siteName: deploySettings?.netlify?.createInfo?.siteName || '',
};
const netlify = {
authToken: deploySettings?.netlify?.authToken || '',
siteId: deploySettings?.netlify?.siteId || '',
createInfo,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
<label className="text-sm font-lg text-gray-700 text-right">Netlify Site Name (new site):</label>
<input
name="netlifySiteName"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.netlify?.createInfo?.siteName}
placeholder="my-chat-app..."
onChange={(e) => {
const createInfo = {
accountSlug: deploySettings?.netlify?.createInfo?.accountSlug || '',
siteName: e.target.value,
};
const netlify = {
authToken: deploySettings?.netlify?.authToken || '',
siteId: deploySettings?.netlify?.siteId || '',
createInfo,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
<label className="text-sm font-lg text-gray-700 text-right">Supabase Database URL:</label>
<input
name="supabaseDatabaseURL"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.supabase?.databaseURL}
placeholder="https://abc...def.supabase.co"
onChange={(e) => {
const supabase = {
databaseURL: e.target.value,
anonKey: deploySettings?.supabase?.anonKey || '',
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
postgresURL: deploySettings?.supabase?.postgresURL || '',
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
<label className="text-sm font-lg text-gray-700 text-right">Supabase Anonymous Key:</label>
<input
name="supabaseAnonKey"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.supabase?.anonKey}
placeholder="ey..."
onChange={(e) => {
const supabase = {
databaseURL: deploySettings?.supabase?.databaseURL || '',
anonKey: e.target.value,
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
postgresURL: deploySettings?.supabase?.postgresURL || '',
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
<label className="text-sm font-lg text-gray-700 text-right">Supabase Service Role Key:</label>
<input
name="supabaseServiceRoleKey"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.supabase?.serviceRoleKey}
placeholder="ey..."
onChange={(e) => {
const supabase = {
databaseURL: deploySettings?.supabase?.databaseURL || '',
anonKey: deploySettings?.supabase?.anonKey || '',
serviceRoleKey: e.target.value,
postgresURL: deploySettings?.supabase?.postgresURL || '',
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
<label className="text-sm font-lg text-gray-700 text-right">Supabase Postgres URL:</label>
<input
name="supabasePostgresURL"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 py-2 border border-gray-300"
value={deploySettings?.supabase?.postgresURL}
placeholder="postgresql://postgres:<password>@db.abc...def.supabase.co:5432/postgres"
onChange={(e) => {
const supabase = {
databaseURL: deploySettings?.supabase?.databaseURL || '',
anonKey: deploySettings?.supabase?.anonKey || '',
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
postgresURL: e.target.value,
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
</div>
<div className="flex justify-center gap-2 mt-4">
{status === DeployStatus.Started && (
<div className="w-full text-bolt-elements-textSecondary flex items-center">
<span className="i-svg-spinners:3-dots-fade inline-block w-[1em] h-[1em] mr-2 text-4xl"></span>
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Netlify Auth Token
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
Your authentication token from Netlify account settings. Used to authorize deployments.
</p>
</div>
)}
<input
name="netlifyAuthToken"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.netlify?.authToken}
placeholder="nfp_..."
onChange={(e) => {
const netlify = {
authToken: e.target.value,
siteId: deploySettings?.netlify?.siteId || '',
createInfo: deploySettings?.netlify?.createInfo || undefined,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
</div>
{status === DeployStatus.NotStarted && (
<button
onClick={handleDeploy}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition-colors"
>
Deploy
</button>
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Netlify Site ID (existing site)
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
The ID of your existing Netlify site if you want to update an existing deployment.
</p>
</div>
<input
name="netlifySiteId"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.netlify?.siteId}
placeholder="123e4567-..."
onChange={(e) => {
const netlify = {
authToken: deploySettings?.netlify?.authToken || '',
siteId: e.target.value,
createInfo: deploySettings?.netlify?.createInfo || undefined,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Netlify Account Slug (new site)
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
Your Netlify account name, required when creating a new site.
</p>
</div>
<input
name="netlifyAccountSlug"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.netlify?.createInfo?.accountSlug}
placeholder="abc..."
onChange={(e) => {
const createInfo = {
accountSlug: e.target.value,
siteName: deploySettings?.netlify?.createInfo?.siteName || '',
};
const netlify = {
authToken: deploySettings?.netlify?.authToken || '',
siteId: deploySettings?.netlify?.siteId || '',
createInfo,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Netlify Site Name (new site)
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
The desired name for your new Netlify site. Will be part of your site's URL.
</p>
</div>
<input
name="netlifySiteName"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.netlify?.createInfo?.siteName}
placeholder="my-chat-app..."
onChange={(e) => {
const createInfo = {
accountSlug: deploySettings?.netlify?.createInfo?.accountSlug || '',
siteName: e.target.value,
};
const netlify = {
authToken: deploySettings?.netlify?.authToken || '',
siteId: deploySettings?.netlify?.siteId || '',
createInfo,
};
setDeploySettings({
...deploySettings,
netlify,
});
}}
/>
</div>
{databaseFound && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Supabase Database URL
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
The URL of your Supabase project, used to connect to your database.
</p>
</div>
<input
name="supabaseDatabaseURL"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.supabase?.databaseURL}
placeholder="https://abc...def.supabase.co"
onChange={(e) => {
const supabase = {
databaseURL: e.target.value,
anonKey: deploySettings?.supabase?.anonKey || '',
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
postgresURL: deploySettings?.supabase?.postgresURL || '',
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Supabase Anonymous Key
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
Public API key for client-side database access with restricted permissions.
</p>
</div>
<input
name="supabaseAnonKey"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.supabase?.anonKey}
placeholder="ey..."
onChange={(e) => {
const supabase = {
databaseURL: deploySettings?.supabase?.databaseURL || '',
anonKey: e.target.value,
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
postgresURL: deploySettings?.supabase?.postgresURL || '',
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Supabase Service Role Key
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
Admin API key for server-side operations with full database access.
</p>
</div>
<input
name="supabaseServiceRoleKey"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.supabase?.serviceRoleKey}
placeholder="ey..."
onChange={(e) => {
const supabase = {
databaseURL: deploySettings?.supabase?.databaseURL || '',
anonKey: deploySettings?.supabase?.anonKey || '',
serviceRoleKey: e.target.value,
postgresURL: deploySettings?.supabase?.postgresURL || '',
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
Supabase Postgres URL
</label>
<div className="w-full mb-2">
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
Direct connection URL to your Postgres database for advanced operations.
</p>
</div>
<input
name="supabasePostgresURL"
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
value={deploySettings?.supabase?.postgresURL}
placeholder="postgresql://postgres:<password>@db.abc...def.supabase.co:5432/postgres"
onChange={(e) => {
const supabase = {
databaseURL: deploySettings?.supabase?.databaseURL || '',
anonKey: deploySettings?.supabase?.anonKey || '',
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
postgresURL: e.target.value,
};
setDeploySettings({
...deploySettings,
supabase,
});
}}
/>
</div>
</>
)}
<button
onClick={() => {
setIsModalOpen(false);
}}
className="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400 transition-colors"
>
Cancel
</button>
</div>
{error && <div className="mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">{error}</div>}
<div className="flex justify-center gap-3">
{status === DeployStatus.Started ? (
<div className="w-full text-bolt-elements-textSecondary flex items-center justify-center">
<span className="i-svg-spinners:3-dots-fade inline-block w-[1em] h-[1em] mr-2 text-4xl"></span>
<span>Deploying your application...</span>
</div>
) : (
<>
<button
onClick={handleDeploy}
className="px-4 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors font-medium"
>
Deploy
</button>
<button
onClick={() => setIsModalOpen(false)}
className="px-4 py-3 bg-gray-300 rounded-lg hover:bg-gray-400 transition-colors font-medium"
>
Cancel
</button>
</>
)}
</div>
{error && (
<div className="mt-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg">
<p className="font-medium mb-1">Deployment Error</p>
<p>{error}</p>
</div>
)}
</>
)}
</div>

View File

@ -0,0 +1,33 @@
const DeploymentSuccessful = ({
deploySettings,
setIsModalOpen,
}: {
deploySettings: any;
setIsModalOpen: (isOpen: boolean) => void;
}) => {
return (
<>
<h2 className="text-2xl font-bold mb-6 text-bolt-elements-textPrimary text-center">Deployment Succeeded! 🎉</h2>
<div className="text-center">
<p className="text-bolt-elements-textSecondary mb-6">
Your application has been successfully deployed. You can now access it at the URL below.
</p>
<div className="flex justify-center gap-2">
<a href={deploySettings?.siteURL} target="_blank" rel="noopener noreferrer">
<button className="px-4 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors font-medium">
{deploySettings?.siteURL}
</button>
</a>
<button
onClick={() => setIsModalOpen(false)}
className="px-4 py-3 bg-gray-300 rounded-lg hover:bg-gray-400 transition-colors font-medium"
>
Close
</button>
</div>
</div>
</>
);
};
export default DeploymentSuccessful;

View File

@ -51,6 +51,7 @@ export function DownloadButton() {
onClick={handleDownload}
>
<div className="i-ph:download-fill text-[1.3em]" />
Download Repo
</button>
</>
);

View File

@ -45,10 +45,10 @@ export function Header() {
{chatStarted && (
<>
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
<span className="flex-1 min-w-fit px-4 truncate text-center text-bolt-elements-textPrimary">
<ClientOnly>{() => <DeployChatButton />}</ClientOnly>
</span>
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
<span className="flex-1 min-w-fit px-4 truncate text-center text-bolt-elements-textPrimary">
<ClientOnly>{() => <DownloadButton />}</ClientOnly>
</span>
</>