@@ -13,7 +13,6 @@ import {
1313 type OnScrollCallback as OnEditorScroll ,
1414} from '~/components/editor/codemirror/CodeMirrorEditor' ;
1515import { IconButton } from '~/components/ui/IconButton' ;
16- import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton' ;
1716import { Slider , type SliderOptions } from '~/components/ui/Slider' ;
1817import { workbenchStore , type WorkbenchViewType } from '~/lib/stores/workbench' ;
1918import { classNames } from '~/utils/classNames' ;
@@ -26,6 +25,10 @@ import useViewport from '~/lib/hooks';
2625import { usePreviewStore } from '~/lib/stores/previews' ;
2726import { chatStore } from '~/lib/stores/chat' ;
2827import type { ElementInfo } from './Inspector' ;
28+ import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton' ;
29+ import { useChatHistory } from '~/lib/persistence' ;
30+ import { streamingState } from '~/lib/stores/streaming' ;
31+ import * as DropdownMenu from '@radix-ui/react-dropdown-menu' ;
2932
3033interface WorkspaceProps {
3134 chatStarted ?: boolean ;
@@ -302,6 +305,9 @@ export const Workbench = memo(
302305 const canHideChat = showWorkbench || ! showChat ;
303306
304307 const isSmallViewport = useViewport ( 1024 ) ;
308+ const streaming = useStore ( streamingState ) ;
309+ const { exportChat } = useChatHistory ( ) ;
310+ const [ isSyncing , setIsSyncing ] = useState ( false ) ;
305311
306312 const setSelectedView = ( view : WorkbenchViewType ) => {
307313 workbenchStore . currentView . set ( view ) ;
@@ -351,6 +357,21 @@ export const Workbench = memo(
351357 workbenchStore . currentView . set ( 'diff' ) ;
352358 } , [ ] ) ;
353359
360+ const handleSyncFiles = useCallback ( async ( ) => {
361+ setIsSyncing ( true ) ;
362+
363+ try {
364+ const directoryHandle = await window . showDirectoryPicker ( ) ;
365+ await workbenchStore . syncFiles ( directoryHandle ) ;
366+ toast . success ( 'Files synced successfully' ) ;
367+ } catch ( error ) {
368+ console . error ( 'Error syncing files:' , error ) ;
369+ toast . error ( 'Failed to sync files' ) ;
370+ } finally {
371+ setIsSyncing ( false ) ;
372+ }
373+ } , [ ] ) ;
374+
354375 return (
355376 chatStarted && (
356377 < motion . div
@@ -386,15 +407,63 @@ export const Workbench = memo(
386407 < div className = "ml-auto" />
387408 { selectedView === 'code' && (
388409 < div className = "flex overflow-y-auto" >
389- < PanelHeaderButton
390- className = "mr-1 text-sm"
391- onClick = { ( ) => {
392- workbenchStore . toggleTerminal ( ! workbenchStore . showTerminal . get ( ) ) ;
393- } }
394- >
395- < div className = "i-ph:terminal" />
396- Toggle Terminal
397- </ PanelHeaderButton >
410+ { /* Export Chat Button */ }
411+ < ExportChatButton exportChat = { exportChat } />
412+
413+ { /* Sync Button */ }
414+ < div className = "flex border border-bolt-elements-borderColor rounded-md overflow-hidden ml-1" >
415+ < DropdownMenu . Root >
416+ < DropdownMenu . Trigger
417+ disabled = { isSyncing || streaming }
418+ className = "rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
419+ >
420+ { isSyncing ? 'Syncing...' : 'Sync' }
421+ < span className = { classNames ( 'i-ph:caret-down transition-transform' ) } />
422+ </ DropdownMenu . Trigger >
423+ < DropdownMenu . Content
424+ className = { classNames (
425+ 'min-w-[240px] z-[250]' ,
426+ 'bg-white dark:bg-[#141414]' ,
427+ 'rounded-lg shadow-lg' ,
428+ 'border border-gray-200/50 dark:border-gray-800/50' ,
429+ 'animate-in fade-in-0 zoom-in-95' ,
430+ 'py-1' ,
431+ ) }
432+ sideOffset = { 5 }
433+ align = "end"
434+ >
435+ < DropdownMenu . Item
436+ className = { classNames (
437+ 'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative' ,
438+ ) }
439+ onClick = { handleSyncFiles }
440+ disabled = { isSyncing }
441+ >
442+ < div className = "flex items-center gap-2" >
443+ { isSyncing ? (
444+ < div className = "i-ph:spinner" />
445+ ) : (
446+ < div className = "i-ph:cloud-arrow-down" />
447+ ) }
448+ < span > { isSyncing ? 'Syncing...' : 'Sync Files' } </ span >
449+ </ div >
450+ </ DropdownMenu . Item >
451+ </ DropdownMenu . Content >
452+ </ DropdownMenu . Root >
453+ </ div >
454+
455+ { /* Toggle Terminal Button */ }
456+ < div className = "flex border border-bolt-elements-borderColor rounded-md overflow-hidden ml-1" >
457+ < button
458+ onClick = { ( ) => {
459+ workbenchStore . toggleTerminal ( ! workbenchStore . showTerminal . get ( ) ) ;
460+ } }
461+ className = "rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
462+ >
463+ < div className = "i-ph:terminal" />
464+ Toggle Terminal
465+ </ button >
466+ </ div >
398467 </ div >
399468 ) }
400469
0 commit comments