import React, { useState, useEffect } from 'react'; import { X, Github, Upload, Play, Loader2, GitBranch, Search, AlertTriangle } from 'lucide-react'; import { JulesSource } from '../types'; interface NewSessionModalProps { isOpen: boolean; onClose: () => void; onSubmit: (config: NewSessionConfig) => Promise; initialPrompt: string; sources: JulesSource[]; isLoading: boolean; githubToken?: string; githubProfile?: string; } export interface NewSessionConfig { title: string; prompt: string; sourceId: string; // The Jules Source Name e.g., "sources/github/..." githubRepoId: string; // "owner/repo" for HF API branch: string; hfImport?: { spaceId: string; }; runHfDeploymentCue?: boolean; } export const NewSessionModal: React.FC = ({ isOpen, onClose, onSubmit, initialPrompt, sources, isLoading, githubToken, githubProfile }) => { const [title, setTitle] = useState(''); const [repoSearch, setRepoSearch] = useState(''); const [isManualTitle, setIsManualTitle] = useState(false); const [selectedSource, setSelectedSource] = useState(null); const [branch, setBranch] = useState('main'); const [availableBranches, setAvailableBranches] = useState([]); const [isLoadingBranches, setIsLoadingBranches] = useState(false); const [prompt, setPrompt] = useState(initialPrompt); const [isHfImportEnabled, setIsHfImportEnabled] = useState(false); const [hfSpaceId, setHfSpaceId] = useState(''); const [runHfDeploymentCue, setRunHfDeploymentCue] = useState(false); // Update prompt if initialPrompt changes useEffect(() => { setPrompt(initialPrompt); // Auto-generate a title from prompt ONLY if no repo is set if (initialPrompt && !title && !repoSearch && !isManualTitle) { setTitle(initialPrompt.slice(0, 30) + (initialPrompt.length > 30 ? '...' : '')); } }, [initialPrompt]); // Handle URL transformation and Auto-Title useEffect(() => { let currentRepo = repoSearch; // URL transformation: https://github.com/owner/repo(.git) -> owner/repo const githubUrlRegex = /https?:\/\/github\.com\/([^/]+)\/([^/]+)/; const match = currentRepo.match(githubUrlRegex); if (match) { let repoPart = match[2].replace(/\.git$/, ''); const transformed = `${match[1]}/${repoPart}`; setRepoSearch(transformed); currentRepo = transformed; } // Auto-Title based on repo if title is empty or not manually set if (!isManualTitle && currentRepo.includes('/')) { const repoName = currentRepo.split('/').pop() || ''; if (repoName) { setTitle(repoName.charAt(0).toUpperCase() + repoName.slice(1)); } } }, [repoSearch, isManualTitle]); // Handle Repo Selection and Branch Fetching useEffect(() => { const fetchBranches = async () => { const repoId = selectedSource ? `${selectedSource.githubRepo.owner}/${selectedSource.githubRepo.repo}` : repoSearch.includes('/') ? repoSearch : null; if (!repoId) { setAvailableBranches([]); return; } setIsLoadingBranches(true); try { const [owner, repo] = repoId.split('/'); const url = `/api/github/branches?owner=${owner}&repo=${repo}${githubToken ? '&token=' + githubToken : ''}${githubProfile ? '&profile=' + githubProfile : ''}`; const res = await fetch(url); if (res.ok) { const data = await res.json(); const names = data.map((b: any) => b.name); setAvailableBranches(names); if (!branch || !names.includes(branch)) { setBranch(names.find((n: string) => n === 'main' || n === 'master') || names[0] || 'main'); } } else { setAvailableBranches([]); } } catch (e) { console.error("Failed to fetch branches", e); setAvailableBranches([]); } finally { setIsLoadingBranches(false); } }; const timer = setTimeout(fetchBranches, 500); return () => clearTimeout(timer); }, [selectedSource, repoSearch, githubToken, githubProfile]); // Find matching source when typing useEffect(() => { if (repoSearch.includes('/')) { const match = sources.find(s => `${s.githubRepo.owner}/${s.githubRepo.repo}`.toLowerCase() === repoSearch.toLowerCase() ); if (match) { setSelectedSource(match); } else { setSelectedSource(null); } } }, [repoSearch, sources]); if (!isOpen) return null; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // We must have a GitHub repository for Jules to use if (!repoSearch) { alert("Please enter a target GitHub repository (owner/repo)."); return; } // We must have a Hugging Face Space ID if import is enabled if (isHfImportEnabled && !hfSpaceId) { alert("Please enter a Hugging Face Space ID to import from."); return; } const githubRepoId = selectedSource ? `${selectedSource.githubRepo.owner}/${selectedSource.githubRepo.repo}` : repoSearch; const config: NewSessionConfig = { title, prompt, sourceId: selectedSource?.name || `sources/github/${githubRepoId}`, // Fallback ID if not found githubRepoId, branch, hfImport: isHfImportEnabled ? { spaceId: hfSpaceId } : undefined, runHfDeploymentCue }; onSubmit(config); }; return (

Start New Session

{!isLoading && ( )}
{/* Session Details */}
{ setTitle(e.target.value); setIsManualTitle(true); }} className="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" placeholder="e.g. Implement Login Feature" disabled={isLoading} />