@ -381,39 +381,47 @@ export function ProjectSetupModal({
if ( ! isOpen ) return null ;
if ( ! isOpen ) return null ;
return (
return (
< div className = "fixed inset-0 z-[100] flex items-center justify-center bg-black/50" >
< div
className = "fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm"
onClick = { onClose }
onWheelCapture = { ( e ) = > e . stopPropagation ( ) }
>
< div
< div
className = "bg-neutral-800 rounded-lg w-[480px] border border-neutral-700 shadow-xl flex flex-col max-h-[80vh]"
className = "bg-neutral-800 rounded-xl w-[520px] border border-neutral-700 shadow-2xl overflow-clip flex flex-col max-h-[80vh]"
onClick = { ( e ) = > e . stopPropagation ( ) }
onKeyDown = { handleKeyDown }
onKeyDown = { handleKeyDown }
>
>
< div className = "px-6 pt-6 pb-0 shrink-0" >
< div className = "px-8 pt-8 pb-0 shrink-0" >
< h2 className = "text-lg font-semibold text-neutral-100 mb-4" >
< div className = "flex items-center gap-2 mb-5" >
{ mode === "new" ? "New Project" : "Project Settings" }
< img src = "/banana_icon.png" alt = "" className = "w-6 h-6" / >
< / h2 >
< h2 className = "text-xl font-medium text-neutral-100" >
{ mode === "new" ? "New Project" : "Project Settings" }
< / h2 >
< / div >
{ /* Tab Bar */ }
{ /* Tab Bar */ }
< div className = "flex gap-4 border-b border-neutral-700" >
< div className = "flex gap-1.5 p-1 bg-neutral-900/50 rounded-lg " >
< button
< button
onClick = { ( ) = > setActiveTab ( "project" ) }
onClick = { ( ) = > setActiveTab ( "project" ) }
className = { ` pb-2 text-sm ${ activeTab === "project" ? "text-neutral-100 border-b-2 border-white " : "text-neutral-400" } ` }
className = { ` px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${ activeTab === "project" ? "bg-neutral-700 text-neutral-100 font-medium " : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/5 0" } ` }
>
>
Project
Project
< / button >
< / button >
< button
< button
onClick = { ( ) = > setActiveTab ( "providers" ) }
onClick = { ( ) = > setActiveTab ( "providers" ) }
className = { ` pb-2 text-sm ${ activeTab === "providers" ? "text-neutral-100 border-b-2 border-white " : "text-neutral-400" } ` }
className = { ` px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${ activeTab === "providers" ? "bg-neutral-700 text-neutral-100 font-medium " : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/5 0" } ` }
>
>
Providers
Providers
< / button >
< / button >
< button
< button
onClick = { ( ) = > setActiveTab ( "nodeDefaults" ) }
onClick = { ( ) = > setActiveTab ( "nodeDefaults" ) }
className = { ` pb-2 text-sm ${ activeTab === "nodeDefaults" ? "text-neutral-100 border-b-2 border-white " : "text-neutral-400" } ` }
className = { ` px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${ activeTab === "nodeDefaults" ? "bg-neutral-700 text-neutral-100 font-medium " : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/5 0" } ` }
>
>
Node Defaults
Node Defaults
< / button >
< / button >
< button
< button
onClick = { ( ) = > setActiveTab ( "canvas" ) }
onClick = { ( ) = > setActiveTab ( "canvas" ) }
className = { ` pb-2 text-sm ${ activeTab === "canvas" ? "text-neutral-100 border-b-2 border-white " : "text-neutral-400" } ` }
className = { ` px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${ activeTab === "canvas" ? "bg-neutral-700 text-neutral-100 font-medium " : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/5 0" } ` }
>
>
Canvas
Canvas
< / button >
< / button >
@ -421,7 +429,7 @@ export function ProjectSetupModal({
< / div >
< / div >
{ /* Scrollable tab content area */ }
{ /* Scrollable tab content area */ }
< div className = "flex-1 min-h-0 overflow-y-auto px-6 py-4 " >
< div className = "flex-1 min-h-0 overflow-y-auto px-8 py-5 " >
{ /* Project Tab Content */ }
{ /* Project Tab Content */ }
{ activeTab === "project" && (
{ activeTab === "project" && (
@ -436,7 +444,7 @@ export function ProjectSetupModal({
onChange = { ( e ) = > setName ( e . target . value ) }
onChange = { ( e ) = > setName ( e . target . value ) }
placeholder = "my-project"
placeholder = "my-project"
autoFocus
autoFocus
className = "w-full px-3 py-2 bg-neutral-900 border border-neutral-600 rounded text-neutral-100 text-sm focus:outline-none focus:border-neutral-500"
className = "w-full px-3 py-2 bg-neutral-900 border border-neutral-600 rounded-lg text-neutral-100 text-sm focus:outline-none focus:border-neutral-500"
/ >
/ >
< / div >
< / div >
@ -450,18 +458,18 @@ export function ProjectSetupModal({
value = { directoryPath }
value = { directoryPath }
onChange = { ( e ) = > setDirectoryPath ( e . target . value ) }
onChange = { ( e ) = > setDirectoryPath ( e . target . value ) }
placeholder = "/Users/username/projects/my-project"
placeholder = "/Users/username/projects/my-project"
className = "flex-1 px-3 py-2 bg-neutral-900 border border-neutral-600 rounded text-neutral-100 text-sm focus:outline-none focus:border-neutral-500"
className = "flex-1 px-3 py-2 bg-neutral-900 border border-neutral-600 rounded-lg text-neutral-100 text-sm focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
onClick = { handleBrowse }
onClick = { handleBrowse }
disabled = { isBrowsing }
disabled = { isBrowsing }
className = "px-3 py-2 bg-neutral-700 hover:bg-neutral-600 disabled:bg-neutral-700 disabled:opacity-50 text-neutral-200 text-sm rounded transition-colors"
className = "px-3 py-2 bg-neutral-700 hover:bg-neutral-600 disabled:bg-neutral-700 disabled:opacity-50 text-neutral-200 text-sm rounded-lg transition-colors"
>
>
{ isBrowsing ? "..." : "Browse" }
{ isBrowsing ? "..." : "Browse" }
< / button >
< / button >
< / div >
< / div >
< p className = "text-xs text-neutral-5 00 mt-1" >
< p className = "text-xs text-neutral-4 00 mt-1" >
Workflow files and images will be saved here . Subfolders for inputs and generations will be auto - created .
Workflow files and images will be saved here . Subfolders for inputs and generations will be auto - created .
< / p >
< / p >
< / div >
< / div >
@ -470,7 +478,7 @@ export function ProjectSetupModal({
< label className = "flex items-center justify-between gap-3 cursor-pointer" >
< label className = "flex items-center justify-between gap-3 cursor-pointer" >
< div >
< div >
< span className = "text-sm text-neutral-200" > Embed images as base64 < / span >
< span className = "text-sm text-neutral-200" > Embed images as base64 < / span >
< p className = "text-xs text-neutral-5 00" >
< p className = "text-xs text-neutral-4 00" >
Embeds all images in workflow , larger workflow files . Can hit memory limits on very large workflows .
Embeds all images in workflow , larger workflow files . Can hit memory limits on very large workflows .
< / p >
< / p >
< / div >
< / div >
@ -490,7 +498,7 @@ export function ProjectSetupModal({
< label className = "flex items-center justify-between gap-3 cursor-pointer" >
< label className = "flex items-center justify-between gap-3 cursor-pointer" >
< div >
< div >
< span className = "text-sm text-neutral-200" > Show model settings on nodes < / span >
< span className = "text-sm text-neutral-200" > Show model settings on nodes < / span >
< p className = "text-xs text-neutral-5 00" >
< p className = "text-xs text-neutral-4 00" >
Show model parameters inside generation nodes instead of the side panel
Show model parameters inside generation nodes instead of the side panel
< / p >
< / p >
< / div >
< / div >
@ -535,7 +543,7 @@ export function ProjectSetupModal({
value = { localProviders . providers . gemini ? . apiKey || "" }
value = { localProviders . providers . gemini ? . apiKey || "" }
onChange = { ( e ) = > updateLocalProvider ( "gemini" , { apiKey : e.target.value || null } ) }
onChange = { ( e ) = > updateLocalProvider ( "gemini" , { apiKey : e.target.value || null } ) }
placeholder = "AIza..."
placeholder = "AIza..."
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
@ -583,7 +591,7 @@ export function ProjectSetupModal({
value = { localProviders . providers . openai ? . apiKey || "" }
value = { localProviders . providers . openai ? . apiKey || "" }
onChange = { ( e ) = > updateLocalProvider ( "openai" , { apiKey : e.target.value || null } ) }
onChange = { ( e ) = > updateLocalProvider ( "openai" , { apiKey : e.target.value || null } ) }
placeholder = "sk-..."
placeholder = "sk-..."
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
@ -631,7 +639,7 @@ export function ProjectSetupModal({
value = { localProviders . providers . anthropic ? . apiKey || "" }
value = { localProviders . providers . anthropic ? . apiKey || "" }
onChange = { ( e ) = > updateLocalProvider ( "anthropic" , { apiKey : e.target.value || null } ) }
onChange = { ( e ) = > updateLocalProvider ( "anthropic" , { apiKey : e.target.value || null } ) }
placeholder = "sk-ant-..."
placeholder = "sk-ant-..."
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
@ -679,7 +687,7 @@ export function ProjectSetupModal({
value = { localProviders . providers . replicate ? . apiKey || "" }
value = { localProviders . providers . replicate ? . apiKey || "" }
onChange = { ( e ) = > updateLocalProvider ( "replicate" , { apiKey : e.target.value || null } ) }
onChange = { ( e ) = > updateLocalProvider ( "replicate" , { apiKey : e.target.value || null } ) }
placeholder = "r8_..."
placeholder = "r8_..."
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
@ -727,7 +735,7 @@ export function ProjectSetupModal({
value = { localProviders . providers . fal ? . apiKey || "" }
value = { localProviders . providers . fal ? . apiKey || "" }
onChange = { ( e ) = > updateLocalProvider ( "fal" , { apiKey : e.target.value || null } ) }
onChange = { ( e ) = > updateLocalProvider ( "fal" , { apiKey : e.target.value || null } ) }
placeholder = "..."
placeholder = "..."
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
@ -756,16 +764,10 @@ export function ProjectSetupModal({
{ /* Kie.ai Provider */ }
{ /* Kie.ai Provider */ }
< div className = "p-3 bg-neutral-900 rounded-lg border border-neutral-700" >
< div className = "p-3 bg-neutral-900 rounded-lg border border-neutral-700" >
< div className = "flex items-center justify-between" >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-2" >
< span className = "text-sm font-medium text-neutral-100" > Kie . ai < / span >
< svg className = "w-4 h-4 text-orange-400" viewBox = "0 0 24 24" fill = "currentColor" >
< path d = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" / >
< / svg >
< span className = "text-sm font-medium text-neutral-100" > Kie . ai < / span >
< span className = "text-xs text-neutral-500" > ( Sora , Veo , Kling , etc . ) < / span >
< / div >
{ envStatus ? . kie && ! overrideActive . kie ? (
{ envStatus ? . kie && ! overrideActive . kie ? (
< div className = "flex items-center gap-2" >
< div className = "flex items-center gap-2" >
< span className = "text-xs text-green-400" > ✓ From . env < / span >
< span className = "text-xs text-green-400" > Configured via . env < / span >
< button
< button
type = "button"
type = "button"
onClick = { ( ) = > setOverrideActive ( ( prev ) = > ( { . . . prev , kie : true } ) ) }
onClick = { ( ) = > setOverrideActive ( ( prev ) = > ( { . . . prev , kie : true } ) ) }
@ -781,7 +783,7 @@ export function ProjectSetupModal({
value = { localProviders . providers . kie ? . apiKey || "" }
value = { localProviders . providers . kie ? . apiKey || "" }
onChange = { ( e ) = > updateLocalProvider ( "kie" , { apiKey : e.target.value || null } ) }
onChange = { ( e ) = > updateLocalProvider ( "kie" , { apiKey : e.target.value || null } ) }
placeholder = "..."
placeholder = "..."
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
@ -829,7 +831,7 @@ export function ProjectSetupModal({
value = { localProviders . providers . wavespeed ? . apiKey || "" }
value = { localProviders . providers . wavespeed ? . apiKey || "" }
onChange = { ( e ) = > updateLocalProvider ( "wavespeed" , { apiKey : e.target.value || null } ) }
onChange = { ( e ) = > updateLocalProvider ( "wavespeed" , { apiKey : e.target.value || null } ) }
placeholder = "..."
placeholder = "..."
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
className = "w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/ >
/ >
< button
< button
type = "button"
type = "button"
@ -855,7 +857,7 @@ export function ProjectSetupModal({
< / div >
< / div >
< / div >
< / div >
< p className = "text-xs text-neutral-5 00 mt-2" >
< p className = "text-xs text-neutral-4 00 mt-2" >
Add API keys via < code className = "px-1 py-0.5 bg-neutral-800 rounded" > . env . local < / code > for better security . Keys added here override . env and are stored in your browser .
Add API keys via < code className = "px-1 py-0.5 bg-neutral-800 rounded" > . env . local < / code > for better security . Keys added here override . env and are stored in your browser .
< / p >
< / p >
< / div >
< / div >
@ -897,7 +899,7 @@ export function ProjectSetupModal({
< / >
< / >
) : (
) : (
< >
< >
< span className = "text-xs text-neutral-5 00" > System default ( Gemini nano - banana - pro ) < / span >
< span className = "text-xs text-neutral-4 00" > System default ( Gemini nano - banana - pro ) < / span >
< button
< button
type = "button"
type = "button"
onClick = { ( ) = > setShowImageModelDialog ( true ) }
onClick = { ( ) = > setShowImageModelDialog ( true ) }
@ -944,7 +946,7 @@ export function ProjectSetupModal({
< / >
< / >
) : (
) : (
< >
< >
< span className = "text-xs text-neutral-5 00" > None set ( select on first use ) < / span >
< span className = "text-xs text-neutral-4 00" > None set ( select on first use ) < / span >
< button
< button
type = "button"
type = "button"
onClick = { ( ) = > setShowVideoModelDialog ( true ) }
onClick = { ( ) = > setShowVideoModelDialog ( true ) }
@ -978,7 +980,7 @@ export function ProjectSetupModal({
< / div >
< / div >
{ ! localNodeDefaults . llm ? (
{ ! localNodeDefaults . llm ? (
< p className = "text-xs text-neutral-5 00" > Using system defaults ( Google Gemini 3 Flash ) < / p >
< p className = "text-xs text-neutral-4 00" > Using system defaults ( Google Gemini 3 Flash ) < / p >
) : null }
) : null }
{ /* Provider dropdown */ }
{ /* Provider dropdown */ }
@ -1092,14 +1094,14 @@ export function ProjectSetupModal({
className = "flex-1 h-1 bg-neutral-700 rounded-lg appearance-none cursor-pointer accent-neutral-400"
className = "flex-1 h-1 bg-neutral-700 rounded-lg appearance-none cursor-pointer accent-neutral-400"
/ >
/ >
< / div >
< / div >
< p className = "text-xs text-neutral-5 00" >
< p className = "text-xs text-neutral-4 00" >
Maximum number of nodes to execute in parallel during workflow execution .
Maximum number of nodes to execute in parallel during workflow execution .
Higher values may improve speed but increase API rate limit risk .
Higher values may improve speed but increase API rate limit risk .
< / p >
< / p >
< / div >
< / div >
< / div >
< / div >
< p className = "text-xs text-neutral-5 00 mt-2" >
< p className = "text-xs text-neutral-4 00 mt-2" >
These defaults are applied when creating nodes via keyboard shortcuts ( Shift + G , Shift + L , etc ) .
These defaults are applied when creating nodes via keyboard shortcuts ( Shift + G , Shift + L , etc ) .
< / p >
< / p >
< / div >
< / div >
@ -1107,106 +1109,107 @@ export function ProjectSetupModal({
{ /* Canvas Tab Content */ }
{ /* Canvas Tab Content */ }
{ activeTab === "canvas" && (
{ activeTab === "canvas" && (
< div className = "space-y-5" >
< div className = "space-y-3" >
< p className = "text-xs text-neutral-400" > Configure how you navigate and interact with the canvas . < / p >
{ /* Pan Mode */ }
{ /* Pan Mode */ }
< div >
< div className = "p-3 bg-neutral-900 rounded-lg border border-neutral-700" >
< h3 className = "text-sm font-medium text-neutral-200 mb-2 "> Pan Mode < / h3 >
< div className = "flex flex-col gap-3 ">
< div className = "space-y-1.5 " >
< div className = "flex items-center justify-between " >
{ ( [
< span className = "text-sm font-medium text-neutral-100" > Pan Mode < / span >
{ value : "space" as PanMode , label : "Space + Drag" , description : "Hold Space and drag to pan (default)" } ,
< p className = "text-xs text-neutral-400" >
{ value : "middleMouse" as PanMode , label : "Middle Mouse" , description : "Click and drag with middle mouse button" } ,
{ localCanvasSettings . panMode === "space" && "Hold Space and drag to pan" }
{ value : "always" as PanMode , label : "Always On" , description : "Pan without holding any keys" } ,
{ localCanvasSettings . panMode === "middleMouse" && "Click and drag with middle mouse button" }
] as const ) . map ( ( option ) = > (
{ localCanvasSettings . panMode === "always" && "Pan without holding any keys" }
< label
< / p >
key = { option . value }
< / div >
className = { ` flex items-start p-2.5 rounded-lg border cursor-pointer transition-colors ${
< div className = "flex gap-1 p-0.5 bg-neutral-800 rounded-md" >
localCanvasSettings . panMode === option . value
{ ( [
? "border-blue-500 bg-blue-500/10"
{ value : "space" as PanMode , label : "Space + Drag" } ,
: "border-neutral-700 hover:border-neutral-600 bg-neutral-900/50"
{ value : "middleMouse" as PanMode , label : "Middle Mouse" } ,
} ` }
{ value : "always" as PanMode , label : "Always On" } ,
>
] as const ) . map ( ( option ) = > (
< input
< button
type = "radio"
key = { option . value }
name = "panMode "
type = "button "
value = { option . value }
onClick = { ( ) = > setLocalCanvasSettings ( { . . . localCanvasSettings , panMode : option.value } ) }
checked = { localCanvasSettings . panMode === option . value }
className = { ` flex-1 px-2 py-1.5 text-xs rounded transition-all duration-150 ${
onChange = { ( e ) = > setLocalCanvasSettings ( { . . . localCanvasSettings , panMode : e.target.value as PanMode } ) }
localCanvasSettings . panMode === option . value
className = "mt-0.5 mr-3 "
? "bg-neutral-700 text-neutral-100 font-medium "
/ >
: "text-neutral-400 hover:text-neutral-300"
< div className = "flex-1" >
} ` }
< div className = "text-sm font-medium text-neutral-200" > { option . label } < / div >
>
< div className = "text-xs text-neutral-400 mt-0.5" > { option . description } < / div >
{ option . label }
< / div >
< / button >
< / label >
) ) }
) ) }
< / div >
< / div >
< / div >
< / div >
< / div >
{ /* Zoom Mode */ }
{ /* Zoom Mode */ }
< div >
< div className = "p-3 bg-neutral-900 rounded-lg border border-neutral-700" >
< h3 className = "text-sm font-medium text-neutral-200 mb-2" > Zoom Mode < / h3 >
< div className = "flex flex-col gap-3" >
< div className = "space-y-1.5 " >
< div className = "flex items-center justify-between " >
{ ( [
< span className = "text-sm font-medium text-neutral-100" > Zoom Mode < / span >
{ value : "altScroll" as ZoomMode , label : "Alt + Scroll" , description : "Hold Alt and scroll to zoom (default)" } ,
< p className = "text-xs text-neutral-400" >
{ value : "ctrlScroll" as ZoomMode , label : "Ctrl + Scroll" , description : "Hold Ctrl/Cmd and scroll to zoom" } ,
{ localCanvasSettings . zoomMode === "altScroll" && "Hold Alt and scroll to zoom" }
{ value : "scroll" as ZoomMode , label : "Scroll" , description : "Scroll to zoom without holding any keys" } ,
{ localCanvasSettings . zoomMode === "ctrlScroll" && "Hold Ctrl/Cmd and scroll to zoom" }
] as const ) . map ( ( option ) = > (
{ localCanvasSettings . zoomMode === "scroll" && "Scroll to zoom without modifier keys" }
< label
< / p >
key = { option . value }
< / div >
className = { ` flex items-start p-2.5 rounded-lg border cursor-pointer transition-colors ${
< div className = "flex gap-1 p-0.5 bg-neutral-800 rounded-md" >
localCanvasSettings . zoomMode === option . value
{ ( [
? "border-blue-500 bg-blue-500/10"
{ value : "altScroll" as ZoomMode , label : "Alt + Scroll" } ,
: "border-neutral-700 hover:border-neutral-600 bg-neutral-900/50"
{ value : "ctrlScroll" as ZoomMode , label : "Ctrl + Scroll" } ,
} ` }
{ value : "scroll" as ZoomMode , label : "Scroll" } ,
>
] as const ) . map ( ( option ) = > (
< input
< button
type = "radio"
key = { option . value }
name = "zoomMode "
type = "button "
value = { option . value }
onClick = { ( ) = > setLocalCanvasSettings ( { . . . localCanvasSettings , zoomMode : option.value } ) }
checked = { localCanvasSettings . zoomMode === option . value }
className = { ` flex-1 px-2 py-1.5 text-xs rounded transition-all duration-150 ${
onChange = { ( e ) = > setLocalCanvasSettings ( { . . . localCanvasSettings , zoomMode : e.target.value as ZoomMode } ) }
localCanvasSettings . zoomMode === option . value
className = "mt-0.5 mr-3 "
? "bg-neutral-700 text-neutral-100 font-medium "
/ >
: "text-neutral-400 hover:text-neutral-300"
< div className = "flex-1" >
} ` }
< div className = "text-sm font-medium text-neutral-200" > { option . label } < / div >
>
< div className = "text-xs text-neutral-400 mt-0.5" > { option . description } < / div >
{ option . label }
< / div >
< / button >
< / label >
) ) }
) ) }
< / div >
< / div >
< / div >
< / div >
< / div >
{ /* Selection Mode */ }
{ /* Selection Mode */ }
< div >
< div className = "p-3 bg-neutral-900 rounded-lg border border-neutral-700" >
< h3 className = "text-sm font-medium text-neutral-200 mb-2 "> Selection Mode < / h3 >
< div className = "flex flex-col gap-3 ">
< div className = "space-y-1.5 " >
< div className = "flex items-center justify-between " >
{ ( [
< span className = "text-sm font-medium text-neutral-100" > Selection Mode < / span >
{ value : "click" as SelectionMode , label : "Click" , description : "Click to select nodes (default)" } ,
< p className = "text-xs text-neutral-400" >
{ value : "altDrag" as SelectionMode , label : "Alt + Drag" , description : "Hold Alt and drag to select multiple nodes" } ,
{ localCanvasSettings . selectionMode === "click" && "Click to select nodes" }
{ value : "shiftDrag" as SelectionMode , label : "Shift + Drag" , description : "Hold Shift and drag to select multiple nodes" } ,
{ localCanvasSettings . selectionMode === "altDrag" && "Hold Alt and drag to select" }
] as const ) . map ( ( option ) = > (
{ localCanvasSettings . selectionMode === "shiftDrag" && "Hold Shift and drag to select" }
< label
< / p >
key = { option . value }
< / div >
className = { ` flex items-start p-2.5 rounded-lg border cursor-pointer transition-colors ${
< div className = "flex gap-1 p-0.5 bg-neutral-800 rounded-md" >
localCanvasSettings . selectionMode === option . value
{ ( [
? "border-blue-500 bg-blue-500/10"
{ value : "click" as SelectionMode , label : "Click" } ,
: "border-neutral-700 hover:border-neutral-600 bg-neutral-900/50"
{ value : "altDrag" as SelectionMode , label : "Alt + Drag" } ,
} ` }
{ value : "shiftDrag" as SelectionMode , label : "Shift + Drag" } ,
>
] as const ) . map ( ( option ) = > (
< input
< button
type = "radio"
key = { option . value }
name = "selectionMode "
type = "button "
value = { option . value }
onClick = { ( ) = > setLocalCanvasSettings ( { . . . localCanvasSettings , selectionMode : option.value } ) }
checked = { localCanvasSettings . selectionMode === option . value }
className = { ` flex-1 px-2 py-1.5 text-xs rounded transition-all duration-150 ${
onChange = { ( e ) = > setLocalCanvasSettings ( { . . . localCanvasSettings , selectionMode : e.target.value as SelectionMode } ) }
localCanvasSettings . selectionMode === option . value
className = "mt-0.5 mr-3 "
? "bg-neutral-700 text-neutral-100 font-medium "
/ >
: "text-neutral-400 hover:text-neutral-300"
< div className = "flex-1" >
} ` }
< div className = "text-sm font-medium text-neutral-200" > { option . label } < / div >
>
< div className = "text-xs text-neutral-400 mt-0.5" > { option . description } < / div >
{ option . label }
< / div >
< / button >
< / label >
) ) }
) ) }
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
@ -1215,7 +1218,7 @@ export function ProjectSetupModal({
< / div >
< / div >
{ /* Fixed footer */ }
{ /* Fixed footer */ }
< div className = "flex justify-end gap-2 px-6 py-4 border-t border-neutral-70 0 shrink-0" >
< div className = "flex justify-end gap-2 px-8 py-5 border-t border-neutral-700/5 0 shrink-0" >
< button
< button
onClick = { onClose }
onClick = { onClose }
className = "px-4 py-2 text-sm text-neutral-400 hover:text-neutral-100 transition-colors"
className = "px-4 py-2 text-sm text-neutral-400 hover:text-neutral-100 transition-colors"
@ -1225,7 +1228,7 @@ export function ProjectSetupModal({
< button
< button
onClick = { handleSave }
onClick = { handleSave }
disabled = { activeTab === "project" && ( isValidating || isBrowsing ) }
disabled = { activeTab === "project" && ( isValidating || isBrowsing ) }
className = "px-4 py-2 text-sm bg-white text-neutral-900 rounded hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
className = "px-4 py-2 text-sm bg-white text-neutral-900 rounded-lg hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
>
{ activeTab === "project"
{ activeTab === "project"
? ( isValidating ? "Validating..." : mode === "new" ? "Create" : "Save" )
? ( isValidating ? "Validating..." : mode === "new" ? "Create" : "Save" )