diff --git a/src/components/ProjectSetupModal.tsx b/src/components/ProjectSetupModal.tsx index b966ee70..375e0cc1 100644 --- a/src/components/ProjectSetupModal.tsx +++ b/src/components/ProjectSetupModal.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import { generateWorkflowId, useWorkflowStore } from "@/store/workflowStore"; +import { ProviderType, ProviderSettings } from "@/types"; interface ProjectSetupModalProps { isOpen: boolean; @@ -16,8 +17,20 @@ export function ProjectSetupModal({ onSave, mode, }: ProjectSetupModalProps) { - const { workflowName, saveDirectoryPath, useExternalImageStorage, setUseExternalImageStorage } = useWorkflowStore(); + const { + workflowName, + saveDirectoryPath, + useExternalImageStorage, + setUseExternalImageStorage, + providerSettings, + updateProviderApiKey, + toggleProvider, + } = useWorkflowStore(); + // Tab state + const [activeTab, setActiveTab] = useState<"project" | "providers">("project"); + + // Project tab state const [name, setName] = useState(""); const [directoryPath, setDirectoryPath] = useState(""); const [externalStorage, setExternalStorage] = useState(true); @@ -25,18 +38,38 @@ export function ProjectSetupModal({ const [isBrowsing, setIsBrowsing] = useState(false); const [error, setError] = useState(null); + // Provider tab state + const [localProviders, setLocalProviders] = useState(providerSettings); + const [showApiKey, setShowApiKey] = useState>({ + gemini: false, + replicate: false, + fal: false, + }); + // Pre-fill when opening in settings mode useEffect(() => { - if (isOpen && mode === "settings") { - setName(workflowName || ""); - setDirectoryPath(saveDirectoryPath || ""); - setExternalStorage(useExternalImageStorage); - } else if (isOpen && mode === "new") { - setName(""); - setDirectoryPath(""); - setExternalStorage(true); + if (isOpen) { + // Reset to project tab when opening + if (mode === "new") { + setActiveTab("project"); + } + + if (mode === "settings") { + setName(workflowName || ""); + setDirectoryPath(saveDirectoryPath || ""); + setExternalStorage(useExternalImageStorage); + } else if (mode === "new") { + setName(""); + setDirectoryPath(""); + setExternalStorage(true); + } + + // Sync local providers state + setLocalProviders(providerSettings); + setShowApiKey({ gemini: false, replicate: false, fal: false }); + setError(null); } - }, [isOpen, mode, workflowName, saveDirectoryPath, useExternalImageStorage]); + }, [isOpen, mode, workflowName, saveDirectoryPath, useExternalImageStorage, providerSettings]); const handleBrowse = async () => { setIsBrowsing(true); @@ -67,7 +100,7 @@ export function ProjectSetupModal({ } }; - const handleSave = async () => { + const handleSaveProject = async () => { if (!name.trim()) { setError("Project name is required"); return; @@ -113,6 +146,34 @@ export function ProjectSetupModal({ } }; + const handleSaveProviders = () => { + // Save each provider's settings + const providerIds: ProviderType[] = ["gemini", "replicate", "fal"]; + for (const providerId of providerIds) { + const local = localProviders.providers[providerId]; + const current = providerSettings.providers[providerId]; + + // Update enabled state if changed + if (local.enabled !== current.enabled) { + toggleProvider(providerId, local.enabled); + } + + // Update API key if changed + if (local.apiKey !== current.apiKey) { + updateProviderApiKey(providerId, local.apiKey); + } + } + onClose(); + }; + + const handleSave = () => { + if (activeTab === "project") { + handleSaveProject(); + } else { + handleSaveProviders(); + } + }; + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !isValidating && !isBrowsing) { handleSave(); @@ -122,6 +183,21 @@ export function ProjectSetupModal({ } }; + const updateLocalProvider = ( + providerId: ProviderType, + updates: { enabled?: boolean; apiKey?: string | null } + ) => { + setLocalProviders((prev) => ({ + providers: { + ...prev.providers, + [providerId]: { + ...prev.providers[providerId], + ...updates, + }, + }, + })); + }; + if (!isOpen) return null; return ( @@ -134,66 +210,190 @@ export function ProjectSetupModal({ {mode === "new" ? "New Project" : "Project Settings"} -
-
- - setName(e.target.value)} - placeholder="my-project" - 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" - /> -
+ {/* Tab Bar */} +
+ + +
-
- -
+ {/* Project Tab Content */} + {activeTab === "project" && ( +
+
+ setDirectoryPath(e.target.value)} - 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" + value={name} + onChange={(e) => setName(e.target.value)} + placeholder="my-project" + 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" /> -
-

- Workflow files and images will be saved here. Subfolders for inputs and generations will be auto-created. -

-
-
-
+ {/* Providers Tab Content */} + {activeTab === "providers" && ( +
+ {/* Gemini Provider */} +
+
+
+ + {localProviders.providers.gemini.name} + + + Default + +
+ Enabled +
+

+ Configured via GEMINI_API_KEY environment variable +

+
+ + {/* Replicate Provider */} +
+
+ + {localProviders.providers.replicate.name} + + +
+ {localProviders.providers.replicate.enabled && ( +
+ +
+ updateLocalProvider("replicate", { apiKey: e.target.value || null })} + placeholder="r8_..." + className="flex-1 px-3 py-2 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-sm focus:outline-none focus:border-neutral-500" + /> + +
+
+ )} +
+ + {/* fal.ai Provider */} +
+
+ + {localProviders.providers.fal.name} + + +
+ {localProviders.providers.fal.enabled && ( +
+ +
+ updateLocalProvider("fal", { apiKey: e.target.value || null })} + placeholder="fal_..." + className="flex-1 px-3 py-2 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-sm focus:outline-none focus:border-neutral-500" + /> + +
+
+ )} +
+ +

+ API keys are stored locally in your browser and never sent to our servers. +

+
+ )}