Spaces:
Running
Running
import React, { useState } from 'react'; | |
import { Brain, Settings, Circle, Sparkles, Zap, FileText, ChevronDown, ChevronRight, Upload, AlertCircle } from 'lucide-react'; | |
import FileList from './FileList'; | |
import AdminLogin from './AdminLogin'; | |
interface SidebarProps { | |
isOpen: boolean; | |
cocoons: Array<{ | |
id: string; | |
type: string; | |
wrapped: any; | |
}>; | |
aiState: { | |
quantumState: number[]; | |
chaosState: number[]; | |
activePerspectives: string[]; | |
ethicalScore: number; | |
processingPower: number; | |
}; | |
darkMode: boolean; | |
supabase: any; | |
isAdmin: boolean; | |
setIsAdmin: (isAdmin: boolean) => void; | |
} | |
const Sidebar: React.FC<SidebarProps> = ({ | |
isOpen, | |
cocoons, | |
aiState, | |
darkMode, | |
supabase, | |
isAdmin, | |
setIsAdmin | |
}) => { | |
const [activeSection, setActiveSection] = useState<string>('cocoons'); | |
const [selectedFile, setSelectedFile] = useState<File | null>(null); | |
const [uploadError, setUploadError] = useState<string | null>(null); | |
const [isUploading, setIsUploading] = useState(false); | |
const [showAdminPrompt, setShowAdminPrompt] = useState(false); | |
const [authError, setAuthError] = useState<string | null>(null); | |
if (!isOpen) return null; | |
const handleAdminLogin = async (password: string) => { | |
try { | |
setAuthError(null); | |
const { data: { user, session }, error } = await supabase.auth.signInWithPassword({ | |
email: 'admin@codette.ai', | |
password: password | |
}); | |
if (error) { | |
setAuthError(error.message); | |
throw error; | |
} | |
if (!session) { | |
throw new Error('No session after login'); | |
} | |
// Verify admin role | |
const { data: { role }, error: roleError } = await supabase.rpc('get_user_role'); | |
if (roleError) { | |
throw roleError; | |
} | |
if (role === 'admin') { | |
setIsAdmin(true); | |
setShowAdminPrompt(false); | |
setAuthError(null); | |
} else { | |
throw new Error('Insufficient permissions'); | |
} | |
} catch (error: any) { | |
console.error('Login error:', error); | |
setAuthError(error.message || 'Invalid login credentials'); | |
throw error; | |
} | |
}; | |
const handleFileUpload = async () => { | |
if (!selectedFile) return; | |
if (!isAdmin) { | |
setUploadError('Only administrators can upload files.'); | |
return; | |
} | |
try { | |
setIsUploading(true); | |
setUploadError(null); | |
// Get user role from session | |
const { data: { user }, error: userError } = await supabase.auth.getUser(); | |
if (userError) throw userError; | |
if (!user || user.role !== 'admin') { | |
throw new Error('Only administrators can upload files.'); | |
} | |
// Upload file to Supabase storage | |
const { data, error } = await supabase.storage | |
.from('codette-files') | |
.upload(`${Date.now()}-${selectedFile.name}`, selectedFile, { | |
upsert: false | |
}); | |
if (error) throw error; | |
// Add file reference to database | |
const { error: dbError } = await supabase | |
.from('codette_files') | |
.insert([ | |
{ | |
filename: selectedFile.name, | |
storage_path: data.path, | |
file_type: selectedFile.type, | |
uploaded_at: new Date().toISOString() | |
} | |
]); | |
if (dbError) throw dbError; | |
setSelectedFile(null); | |
setUploadError(null); | |
} catch (error: any) { | |
console.error('Error uploading file:', error); | |
setUploadError(error.message || 'Failed to upload file. Please try again.'); | |
} finally { | |
setIsUploading(false); | |
} | |
}; | |
const handleSettingsClick = () => { | |
if (!isAdmin) { | |
setShowAdminPrompt(true); | |
} | |
setActiveSection('settings'); | |
}; | |
return ( | |
<aside className={`w-64 flex-shrink-0 border-r transition-colors duration-300 flex flex-col ${ | |
darkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200' | |
}`}> | |
<nav className="flex-1 overflow-y-auto"> | |
<div className="p-4"> | |
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 mb-2"> | |
Navigation | |
</h2> | |
<ul className="space-y-1"> | |
<li> | |
<button | |
onClick={() => setActiveSection('cocoons')} | |
className={`w-full flex items-center px-3 py-2 rounded-md ${ | |
activeSection === 'cocoons' | |
? darkMode | |
? 'bg-gray-700 text-white' | |
: 'bg-gray-200 text-gray-900' | |
: darkMode | |
? 'text-gray-300 hover:bg-gray-700' | |
: 'text-gray-700 hover:bg-gray-100' | |
}`} | |
> | |
<Brain size={18} className="mr-2" /> | |
<span>Thought Cocoons</span> | |
</button> | |
</li> | |
<li> | |
<button | |
onClick={() => setActiveSection('perspectives')} | |
className={`w-full flex items-center px-3 py-2 rounded-md ${ | |
activeSection === 'perspectives' | |
? darkMode | |
? 'bg-gray-700 text-white' | |
: 'bg-gray-200 text-gray-900' | |
: darkMode | |
? 'text-gray-300 hover:bg-gray-700' | |
: 'text-gray-700 hover:bg-gray-100' | |
}`} | |
> | |
<Sparkles size={18} className="mr-2" /> | |
<span>Perspectives</span> | |
</button> | |
</li> | |
<li> | |
<button | |
onClick={handleSettingsClick} | |
className={`w-full flex items-center px-3 py-2 rounded-md ${ | |
activeSection === 'settings' | |
? darkMode | |
? 'bg-gray-700 text-white' | |
: 'bg-gray-200 text-gray-900' | |
: darkMode | |
? 'text-gray-300 hover:bg-gray-700' | |
: 'text-gray-700 hover:bg-gray-100' | |
}`} | |
> | |
<Settings size={18} className="mr-2" /> | |
<span>Settings</span> | |
</button> | |
</li> | |
</ul> | |
</div> | |
<div className="px-4 py-2"> | |
<div className={`h-px ${darkMode ? 'bg-gray-700' : 'bg-gray-200'}`}></div> | |
</div> | |
{activeSection === 'cocoons' && ( | |
<div className="p-4"> | |
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 mb-2"> | |
Recent Thought Cocoons | |
</h2> | |
{cocoons.length === 0 ? ( | |
<div className={`p-3 rounded-md ${ | |
darkMode ? 'bg-gray-700 text-gray-300' : 'bg-gray-100 text-gray-500' | |
}`}> | |
<p className="text-sm">No thought cocoons yet.</p> | |
<p className="text-xs mt-1 italic">Interact with Codette to generate thought patterns.</p> | |
</div> | |
) : ( | |
<ul className="space-y-2"> | |
{cocoons.map((cocoon) => ( | |
<li key={cocoon.id}> | |
<div className={`p-3 rounded-md ${ | |
darkMode ? 'bg-gray-700 hover:bg-gray-600' : 'bg-gray-100 hover:bg-gray-200' | |
} cursor-pointer transition-colors`}> | |
<div className="flex items-start"> | |
<FileText size={16} className={`mr-2 mt-0.5 ${ | |
cocoon.type === 'prompt' | |
? 'text-blue-500' | |
: cocoon.type === 'encrypted' | |
? 'text-purple-500' | |
: 'text-green-500' | |
}`} /> | |
<div> | |
<div className="text-sm font-medium"> | |
{cocoon.type === 'prompt' ? 'User Query' : | |
cocoon.type === 'encrypted' ? 'Encrypted Thought' : | |
'Symbolic Pattern'} | |
</div> | |
<div className="text-xs truncate mt-1 max-w-[180px]"> | |
{cocoon.type === 'encrypted' | |
? '🔒 Encrypted content' | |
: typeof cocoon.wrapped === 'object' && cocoon.wrapped.query | |
? cocoon.wrapped.query | |
: `Cocoon ID: ${cocoon.id}`} | |
</div> | |
<div className={`text-xs mt-1 ${ | |
darkMode ? 'text-gray-400' : 'text-gray-500' | |
}`}> | |
{typeof cocoon.wrapped === 'object' && cocoon.wrapped.timestamp | |
? new Date(cocoon.wrapped.timestamp).toLocaleTimeString() | |
: 'Unknown time'} | |
</div> | |
</div> | |
</div> | |
</div> | |
</li> | |
))} | |
</ul> | |
)} | |
{/* File List Component */} | |
<div className="mt-6"> | |
<FileList supabase={supabase} darkMode={darkMode} isAdmin={isAdmin} /> | |
</div> | |
</div> | |
)} | |
{activeSection === 'perspectives' && ( | |
<div className="p-4"> | |
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 mb-2"> | |
Active Perspectives | |
</h2> | |
<ul className="space-y-2"> | |
{aiState.activePerspectives.map((perspective) => ( | |
<li key={perspective}> | |
<div className={`p-3 rounded-md ${ | |
darkMode ? 'bg-gray-700' : 'bg-gray-100' | |
}`}> | |
<div className="flex items-center"> | |
<Zap size={16} className="mr-2 text-yellow-500" /> | |
<div className="text-sm font-medium capitalize"> | |
{perspective.replace('_', ' ')} | |
</div> | |
</div> | |
<div className={`text-xs mt-2 ${ | |
darkMode ? 'text-gray-400' : 'text-gray-500' | |
}`}> | |
Confidence: {(Math.random() * 0.4 + 0.6).toFixed(2)} | |
</div> | |
</div> | |
</li> | |
))} | |
</ul> | |
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 mt-6 mb-2"> | |
Available Perspectives | |
</h2> | |
<ul className="space-y-2"> | |
{['creative', 'bias_mitigation', 'quantum_computing', 'resilient_kindness'].map((perspective) => ( | |
<li key={perspective}> | |
<div className={`p-3 rounded-md ${ | |
darkMode ? 'bg-gray-700 bg-opacity-50' : 'bg-gray-100 bg-opacity-50' | |
}`}> | |
<div className="flex items-center"> | |
<Circle size={16} className={`mr-2 ${ | |
darkMode ? 'text-gray-500' : 'text-gray-400' | |
}`} /> | |
<div className={`text-sm capitalize ${ | |
darkMode ? 'text-gray-400' : 'text-gray-500' | |
}`}> | |
{perspective.replace('_', ' ')} | |
</div> | |
</div> | |
</div> | |
</li> | |
))} | |
</ul> | |
</div> | |
)} | |
{activeSection === 'settings' && ( | |
<div className="p-4"> | |
{showAdminPrompt ? ( | |
<AdminLogin | |
onLogin={handleAdminLogin} | |
darkMode={darkMode} | |
error={authError} | |
/> | |
) : ( | |
<> | |
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 mb-4"> | |
AI Core Settings | |
</h2> | |
<div className="space-y-4"> | |
{isAdmin && ( | |
<div className="p-4 rounded-md bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100"> | |
Logged in as Administrator | |
</div> | |
)} | |
{isAdmin && ( | |
<div className="space-y-2"> | |
<label className="block text-sm font-medium">Upload File for Codette</label> | |
<div className="flex items-center space-x-2"> | |
<input | |
type="file" | |
onChange={(e) => { | |
setSelectedFile(e.target.files?.[0] || null); | |
setUploadError(null); | |
}} | |
className="hidden" | |
id="file-upload" | |
disabled={isUploading} | |
/> | |
<label | |
htmlFor="file-upload" | |
className={`flex-1 cursor-pointer px-4 py-2 rounded-md border-2 border-dashed ${ | |
darkMode | |
? 'border-gray-600 hover:border-gray-500' | |
: 'border-gray-300 hover:border-gray-400' | |
} flex items-center justify-center ${isUploading ? 'opacity-50 cursor-not-allowed' : ''}`} | |
> | |
<Upload size={18} className="mr-2" /> | |
{selectedFile ? selectedFile.name : 'Choose File'} | |
</label> | |
{selectedFile && ( | |
<button | |
onClick={handleFileUpload} | |
disabled={isUploading} | |
className={`px-4 py-2 rounded-md ${ | |
darkMode | |
? 'bg-purple-600 hover:bg-purple-700' | |
: 'bg-purple-500 hover:bg-purple-600' | |
} text-white ${isUploading ? 'opacity-50 cursor-not-allowed' : ''}`} | |
> | |
{isUploading ? 'Uploading...' : 'Upload'} | |
</button> | |
)} | |
</div> | |
{uploadError && ( | |
<div className="mt-2 p-2 rounded-md bg-red-100 dark:bg-red-900 flex items-start space-x-2"> | |
<AlertCircle className="flex-shrink-0 text-red-500 dark:text-red-400" size={16} /> | |
<p className="text-sm text-red-600 dark:text-red-300"> | |
{uploadError} | |
</p> | |
</div> | |
)} | |
</div> | |
)} | |
<div> | |
<label className="flex items-center justify-between"> | |
<span className="text-sm font-medium">Recursive Depth</span> | |
<select | |
className={`text-sm rounded-md ${ | |
darkMode | |
? 'bg-gray-700 border-gray-600 text-white' | |
: 'bg-white border-gray-300 text-gray-900' | |
}`} | |
> | |
<option>Normal</option> | |
<option>Deep</option> | |
<option>Shallow</option> | |
</select> | |
</label> | |
</div> | |
<div> | |
<label className="flex items-center justify-between"> | |
<span className="text-sm font-medium">Ethical Filter</span> | |
<div className="relative inline-block w-10 align-middle select-none"> | |
<input | |
type="checkbox" | |
id="ethical-toggle" | |
className="sr-only" | |
defaultChecked={true} | |
/> | |
<label | |
htmlFor="ethical-toggle" | |
className={`block h-6 overflow-hidden rounded-full cursor-pointer ${ | |
darkMode ? 'bg-gray-700' : 'bg-gray-300' | |
}`} | |
> | |
<span | |
className={`absolute block w-4 h-4 rounded-full transform transition-transform duration-200 ease-in-out bg-white left-1 top-1 translate-x-4`} | |
></span> | |
</label> | |
</div> | |
</label> | |
</div> | |
<div className="pt-2 pb-1"> | |
<label className="block text-sm font-medium mb-2">Processing Speed</label> | |
<input | |
type="range" | |
min="0" | |
max="100" | |
defaultValue="75" | |
className="w-full" | |
/> | |
<div className="flex justify-between text-xs text-gray-500 mt-1"> | |
<span>Accurate</span> | |
<span>Balanced</span> | |
<span>Fast</span> | |
</div> | |
</div> | |
<div className="pt-4"> | |
<button className={`w-full py-2 px-4 rounded-md ${ | |
darkMode | |
? 'bg-blue-600 hover:bg-blue-700 text-white' | |
: 'bg-blue-500 hover:bg-blue-600 text-white' | |
}`}> | |
Apply Settings | |
</button> | |
</div> | |
</div> | |
</> | |
)} | |
</div> | |
)} | |
</nav> | |
<div className={`p-4 border-t ${darkMode ? 'border-gray-700' : 'border-gray-200'}`}> | |
<div className={`rounded-md p-3 ${darkMode ? 'bg-gray-700' : 'bg-gray-100'}`}> | |
<div className="flex items-center"> | |
<div className={`w-2 h-2 rounded-full ${ | |
Math.random() > 0.5 | |
? 'bg-green-500' | |
: 'bg-yellow-500' | |
} mr-2`}></div> | |
<div className="text-sm font-medium">System Status</div> | |
</div> | |
<div className={`text-xs mt-1 ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}> | |
All neural paths functioning | |
</div> | |
</div> | |
</div> | |
</aside> | |
); | |
}; | |
export default Sidebar; |