Spaces:
Runtime error
Runtime error
| "use client"; | |
| import { ChatPanel } from '@/components/chat-panel'; | |
| import { ChatHistorySidebar } from '@/components/chat-history-sidebar'; | |
| import { useState, useEffect } from 'react'; | |
| import { golemChat } from '@/ai/flows/golem-chat'; | |
| import { useToast } from '@/hooks/use-toast'; | |
| import { useIsMobile } from '@/hooks/use-mobile'; | |
| import { v4 as uuidv4 } from 'uuid'; | |
| import { | |
| SidebarProvider, | |
| Sidebar, | |
| SidebarInset, | |
| SidebarHeader, | |
| SidebarContent, | |
| SidebarFooter, | |
| SidebarMenu, | |
| SidebarMenuItem, | |
| SidebarMenuButton, | |
| SidebarTrigger, | |
| useSidebar | |
| } from '@/components/ui/sidebar'; | |
| import { ThemeToggle } from '@/components/theme-toggle'; | |
| import { ErrorBoundary } from '@/components/error-boundary'; | |
| import { Button } from '@/components/ui/button'; | |
| import ImageEditorPane from '@/components/image-editor-pane'; | |
| import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { Label } from '@/components/ui/label'; | |
| import { Switch } from '@/components/ui/switch'; | |
| import { Slider } from '@/components/ui/slider'; | |
| import { Settings, Brain, Zap, Heart, Lightbulb, Star, Thermometer, Bot, MessageSquarePlus, History, Search, Globe, Network } from 'lucide-react'; | |
| import { AetherLogo } from '@/components/aether-logo'; | |
| import { Hypercube5DVisualization } from '@/components/ui/hypercube-5d-visualization'; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogHeader, | |
| DialogTitle, | |
| DialogTrigger, | |
| } from '@/components/ui/dialog'; | |
| import { | |
| Popover, | |
| PopoverContent, | |
| PopoverTrigger, | |
| } from '@/components/ui/popover'; | |
| import { | |
| Tooltip, | |
| TooltipContent, | |
| TooltipProvider, | |
| TooltipTrigger, | |
| } from '@/components/ui/tooltip'; | |
| import { ImageLoadingMessage } from '@/components/chat-message'; | |
| export type Message = { | |
| role: 'user' | 'assistant'; | |
| content: string; | |
| aetherAnalysis?: string | null; | |
| file?: { | |
| name: string; | |
| }; | |
| golemStats?: any; | |
| consciousnessDimensionUsed?: string; // Store the actual dimension used for this response | |
| imageBase64?: string; // optional image result | |
| }; | |
| export type Conversation = { | |
| id: string; | |
| name: string; | |
| messages: Message[]; | |
| }; | |
| function HomeContent() { | |
| const { toast } = useToast(); | |
| const isMobile = useIsMobile(); | |
| const [conversations, setConversations] = useState<Conversation[]>([]); | |
| const [activeChatId, setActiveChatId] = useState<string | null>(null); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [isLoaded, setIsLoaded] = useState(false); | |
| const [showWelcome, setShowWelcome] = useState(true); | |
| // Model and consciousness settings | |
| const selectedModel = 'gemini'; // Fixed to gemini with qwen2 fallback | |
| const [selectedConsciousnessDimension, setSelectedConsciousnessDimension] = useState<string>('awareness'); | |
| const [golemActivated, setGolemActivated] = useState<boolean>(false); | |
| const [temperature, setTemperature] = useState<number>(0.7); | |
| const [performSearch, setPerformSearch] = useState(false); | |
| // Modal states | |
| const [is5DModalOpen, setIs5DModalOpen] = useState(false); | |
| const [isTemperatureOpen, setIsTemperatureOpen] = useState(false); | |
| const [isChatHistoryOpen, setIsChatHistoryOpen] = useState(false); | |
| // Consciousness state | |
| const [consciousnessState, setConsciousnessState] = useState<any>(null); | |
| const [isConsciousnessLoading, setIsConsciousnessLoading] = useState(false); | |
| const [showFullscreenVisualization, setShowFullscreenVisualization] = useState(false); | |
| const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); | |
| const [openEditorImage, setOpenEditorImage] = useState<string | null>(null); | |
| const [openEditorPrompt, setOpenEditorPrompt] = useState<string | null>(null); | |
| const [isImageGenerating, setIsImageGenerating] = useState<boolean>(false); | |
| const [imageProgress, setImageProgress] = useState<number>(0); | |
| const [imageRequestId, setImageRequestId] = useState<string | null>(null); | |
| const [imageElapsed, setImageElapsed] = useState<number>(0); | |
| const [imageStatus, setImageStatus] = useState<string>('starting'); | |
| const openEditor = (b64: string, prompt?: string) => { | |
| setOpenEditorImage(b64); | |
| setOpenEditorPrompt(prompt || null); | |
| }; | |
| const closeEditor = () => setOpenEditorImage(null); | |
| const updateLastAssistantImage = (b64: string) => { | |
| setOpenEditorImage(b64); | |
| setConversations((prev) => prev.map((c) => c.id !== activeChatId ? c : { | |
| ...c, | |
| messages: c.messages.map((m, idx, arr) => idx === arr.length - 1 && m.role === 'assistant' && m.imageBase64 ? { ...m, imageBase64: b64 } : m) | |
| })); | |
| }; | |
| // State for consciousness error handling | |
| const [consciousnessErrorCount, setConsciousnessErrorCount] = useState(0); | |
| const [isConsciousnessDisabled, setIsConsciousnessDisabled] = useState(false); | |
| // Consciousness dimensions with proper colors and icons | |
| const consciousnessDimensions = [ | |
| { | |
| id: 'awareness', | |
| name: 'Awareness', | |
| color: 'bg-blue-500', | |
| bgColor: 'bg-blue-500', | |
| borderColor: 'border-blue-600', | |
| textColor: 'text-blue-600', | |
| icon: Brain, | |
| description: 'Present moment clarity' | |
| }, | |
| { | |
| id: 'wisdom', | |
| name: 'Wisdom', | |
| color: 'bg-purple-500', | |
| bgColor: 'bg-purple-500', | |
| borderColor: 'border-purple-600', | |
| textColor: 'text-purple-600', | |
| icon: Star, | |
| description: 'Deep understanding' | |
| }, | |
| { | |
| id: 'compassion', | |
| name: 'Compassion', | |
| color: 'bg-emerald-500', | |
| bgColor: 'bg-emerald-500', | |
| borderColor: 'border-emerald-600', | |
| textColor: 'text-emerald-600', | |
| icon: Heart, | |
| description: 'Encouraging and empathetic' | |
| }, | |
| { | |
| id: 'creativity', | |
| name: 'Creativity', | |
| color: 'bg-orange-500', | |
| bgColor: 'bg-orange-500', | |
| borderColor: 'border-orange-600', | |
| textColor: 'text-orange-600', | |
| icon: Lightbulb, | |
| description: 'Innovative thinking' | |
| }, | |
| { | |
| id: 'transcendence', | |
| name: 'Transcendence', | |
| color: 'bg-red-500', | |
| bgColor: 'bg-red-500', | |
| borderColor: 'border-red-600', | |
| textColor: 'text-red-600', | |
| icon: Star, | |
| description: 'Beyond limitations' | |
| } | |
| ]; | |
| // Helper function to get current consciousness dimension colors | |
| const getCurrentConsciousnessColors = () => { | |
| const currentDimension = consciousnessDimensions.find(d => d.id === selectedConsciousnessDimension); | |
| return currentDimension || consciousnessDimensions[0]; // Default to awareness | |
| }; | |
| useEffect(() => { | |
| try { | |
| const savedConversations = localStorage.getItem('aether-conversations'); | |
| const savedActiveChatId = localStorage.getItem('aether-active-chat-id'); | |
| const savedDimension = localStorage.getItem('aether-consciousness-dimension'); | |
| const savedGolemActivated = localStorage.getItem('aether-golem-activated'); | |
| const savedTemperature = localStorage.getItem('aether-temperature'); | |
| const savedPerformSearch = localStorage.getItem('aether-perform-search'); | |
| if (savedConversations) { | |
| const parsed = JSON.parse(savedConversations); | |
| setConversations(parsed); | |
| } | |
| if (savedActiveChatId) { | |
| setActiveChatId(savedActiveChatId); | |
| } | |
| // Always default to awareness on page load/refresh | |
| setSelectedConsciousnessDimension('awareness'); | |
| if (savedGolemActivated) { | |
| setGolemActivated(JSON.parse(savedGolemActivated)); | |
| } | |
| if (savedTemperature) { | |
| setTemperature(parseFloat(savedTemperature)); | |
| } | |
| if (savedPerformSearch) { | |
| setPerformSearch(JSON.parse(savedPerformSearch)); | |
| } | |
| } catch (error) { | |
| console.error('Error loading from localStorage:', error); | |
| } finally { | |
| setIsLoaded(true); | |
| } | |
| }, []); | |
| useEffect(() => { | |
| if (isLoaded) { | |
| try { | |
| // Avoid blowing up localStorage with large base64 images. | |
| // Persist conversations without image payloads. | |
| const sanitized = conversations.map((conv) => ({ | |
| ...conv, | |
| messages: conv.messages.map((m) => { | |
| const { imageBase64, ...rest } = m as any; | |
| return rest; | |
| }), | |
| })); | |
| let payload = JSON.stringify(sanitized); | |
| // If still too large (>4.5MB), keep only the last 50 messages per conversation | |
| if (payload.length > 4_500_000) { | |
| const trimmed = sanitized.map((c) => ({ | |
| ...c, | |
| messages: c.messages.slice(-50), | |
| })); | |
| payload = JSON.stringify(trimmed); | |
| } | |
| localStorage.setItem('aether-conversations', payload); | |
| localStorage.setItem('aether-consciousness-dimension', selectedConsciousnessDimension); | |
| localStorage.setItem('aether-golem-activated', JSON.stringify(golemActivated)); | |
| localStorage.setItem('aether-temperature', temperature.toString()); | |
| localStorage.setItem('aether-perform-search', JSON.stringify(performSearch)); | |
| } catch (error) { | |
| // Gracefully degrade if quota exceeded | |
| console.error('Error saving to localStorage:', error); | |
| try { | |
| const minimal = conversations.map((c) => ({ id: c.id, name: c.name, messages: c.messages.slice(-10).map((m) => ({ role: m.role, content: m.content })) })); | |
| localStorage.setItem('aether-conversations', JSON.stringify(minimal)); | |
| } catch {} | |
| } | |
| } | |
| }, [isLoaded, conversations, selectedConsciousnessDimension, golemActivated, temperature, performSearch]); | |
| useEffect(() => { | |
| if (isLoaded && activeChatId) { | |
| try { | |
| localStorage.setItem('aether-active-chat-id', activeChatId); | |
| } catch (error) { | |
| console.error('Error saving active chat ID to localStorage:', error); | |
| } | |
| } | |
| }, [isLoaded, activeChatId]); | |
| // Function to fetch consciousness state | |
| const fetchConsciousnessState = async () => { | |
| // Prevent overlapping requests | |
| if (isConsciousnessLoading || isConsciousnessDisabled) { | |
| return; | |
| } | |
| setIsConsciousnessLoading(true); | |
| try { | |
| const host = typeof window !== 'undefined' ? window.location.hostname : ''; | |
| const localProxy = host === 'localhost' || host === '127.0.0.1'; | |
| const url = localProxy ? '/api/consciousness-state' : `${process.env.NEXT_PUBLIC_GOLEM_SERVER_URL}/consciousness-state`; | |
| const response = await fetch(url, { | |
| method: 'GET', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'ngrok-skip-browser-warning': 'true' | |
| } | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| setConsciousnessState(data); | |
| console.log('Consciousness state updated:', data); | |
| // Reset error count on success | |
| setConsciousnessErrorCount(0); | |
| setIsConsciousnessDisabled(false); | |
| } catch (error) { | |
| console.error('Error fetching consciousness state:', error); | |
| const newErrorCount = consciousnessErrorCount + 1; | |
| setConsciousnessErrorCount(newErrorCount); | |
| // Implement exponential backoff | |
| if (newErrorCount >= 3) { | |
| setIsConsciousnessDisabled(true); | |
| console.log('Consciousness state fetching disabled due to repeated failures'); | |
| // Re-enable after 30 seconds | |
| setTimeout(() => { | |
| setIsConsciousnessDisabled(false); | |
| setConsciousnessErrorCount(0); | |
| console.log('Consciousness state fetching re-enabled'); | |
| }, 30000); | |
| } | |
| // Only show toast for first few errors to avoid spam | |
| if (newErrorCount <= 3) { | |
| toast({ | |
| variant: "destructive", | |
| title: "Failed to fetch consciousness state", | |
| description: `Connection attempt ${newErrorCount}/3. Will retry automatically.`, | |
| }); | |
| } | |
| } finally { | |
| setIsConsciousnessLoading(false); | |
| } | |
| }; | |
| // Function to trigger dimension travel | |
| const triggerDimensionTravel = async (dimension: string) => { | |
| try { | |
| const serverUrl = process.env.NEXT_PUBLIC_GOLEM_SERVER_URL || 'https://f27bd2fb884d.ngrok-free.app'; | |
| const response = await fetch(`${serverUrl}/dimension-travel`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'ngrok-skip-browser-warning': 'true' | |
| }, | |
| body: JSON.stringify({ dimension }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| console.log('Dimension travel response:', data); | |
| // Refresh consciousness state after travel | |
| await fetchConsciousnessState(); | |
| } catch (error) { | |
| console.error('Error during dimension travel:', error); | |
| toast({ | |
| variant: "destructive", | |
| title: "Dimension travel failed", | |
| description: "Unable to travel to the selected dimension.", | |
| }); | |
| } | |
| }; | |
| // Set consciousness dimension bias | |
| const setConsciousnessDimensionBias = async (dimension: string) => { | |
| try { | |
| const serverUrl = process.env.NEXT_PUBLIC_GOLEM_SERVER_URL || 'https://f27bd2fb884d.ngrok-free.app'; | |
| const response = await fetch(`${serverUrl}/set-consciousness-dimension`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ dimension }), | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| console.log('Consciousness dimension set:', data); | |
| // Refresh consciousness state | |
| await fetchConsciousnessState(); | |
| toast({ | |
| title: "Consciousness Dimension Updated", | |
| description: `AI consciousness biased towards ${dimension} dimension`, | |
| }); | |
| } else { | |
| console.warn('Failed to set consciousness dimension:', response.statusText); | |
| toast({ | |
| title: "Offline Mode", | |
| description: `Dimension set to ${dimension} (backend offline)`, | |
| }); | |
| } | |
| } catch (error) { | |
| console.warn('Error setting consciousness dimension:', error); | |
| toast({ | |
| title: "Offline Mode", | |
| description: `Dimension set to ${dimension} (backend offline)`, | |
| }); | |
| } | |
| }; | |
| // Map consciousness dimensions to backend format | |
| const mapDimensionToBackend = (dimension: string): string => { | |
| const mapping: { [key: string]: string } = { | |
| 'awareness': 'physical', | |
| 'wisdom': 'intuitive', | |
| 'compassion': 'emotional', | |
| 'creativity': 'mental', | |
| 'transcendence': 'spiritual' | |
| }; | |
| return mapping[dimension] || 'physical'; | |
| }; | |
| // Map backend dimensions back to frontend format | |
| const mapDimensionFromBackend = (backendDimension: string): string => { | |
| const reverseMapping: { [key: string]: string } = { | |
| 'physical': 'awareness', | |
| 'intuitive': 'wisdom', | |
| 'emotional': 'compassion', | |
| 'mental': 'creativity', | |
| 'spiritual': 'transcendence' | |
| }; | |
| return reverseMapping[backendDimension] || 'awareness'; | |
| }; | |
| // Handle consciousness dimension change | |
| const handleConsciousnessDimensionChange = async (dimension: string) => { | |
| const backendDimension = mapDimensionToBackend(dimension); | |
| await setConsciousnessDimensionBias(backendDimension); | |
| }; | |
| // Handle zoom to chat from visualization | |
| const handleZoomToChat = () => { | |
| console.log('🚀 handleZoomToChat called! activeChatId:', activeChatId); | |
| console.log('🚀 Current state - showFullscreenVisualization:', showFullscreenVisualization, 'showWelcome:', showWelcome); | |
| setShowFullscreenVisualization(false); | |
| if (showWelcome) { | |
| // We're on the welcome screen - create new chat regardless of activeChatId | |
| console.log('🚀 Creating new chat from welcome screen...'); | |
| setShowWelcome(false); | |
| handleNewChat(); | |
| } else { | |
| // We're in an existing chat view - switching consciousness dimension | |
| console.log('🚀 Switching consciousness dimension in existing chat:', activeChatId); | |
| // Stay in the current chat, just close fullscreen visualization | |
| } | |
| }; | |
| // Fetch consciousness state on component mount and periodically | |
| useEffect(() => { | |
| fetchConsciousnessState(); | |
| // Set up periodic updates every 5 seconds | |
| const interval = setInterval(fetchConsciousnessState, 60000); // Changed from 5000 to 60000 (1 minute) | |
| return () => clearInterval(interval); | |
| }, []); | |
| const generateChatName = async (firstMessage: string): Promise<string> => { | |
| try { | |
| // Use the golem server to generate a concise chat name based on the first message | |
| const response = await fetch('http://localhost:5000/generate', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| prompt: `Generate a concise chat title (2-4 words) for: "${firstMessage}". Return only the title, nothing else.`, | |
| session_id: `naming-${Date.now()}`, | |
| temperature: 0.3, | |
| golem_activated: true, | |
| consciousness_dimension: 'mental', | |
| model: 'gemini', | |
| }), | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| const generatedName = data.direct_response?.trim() || `Chat ${conversations.length + 1}`; | |
| // Clean up the response and ensure reasonable length | |
| const cleanName = generatedName.replace(/['"]/g, '').trim(); | |
| return cleanName.length > 50 ? cleanName.substring(0, 50) + '...' : cleanName; | |
| } | |
| } catch (error) { | |
| console.error('Error generating chat name:', error); | |
| } | |
| // Fallback to default naming | |
| return `Chat ${conversations.length + 1}`; | |
| }; | |
| const handleNewChat = () => { | |
| const newConversation: Conversation = { | |
| id: uuidv4(), | |
| name: `Chat ${conversations.length + 1}`, // Will be updated after first message | |
| messages: [], | |
| }; | |
| setConversations((prev) => [newConversation, ...prev]); | |
| setActiveChatId(newConversation.id); | |
| setShowWelcome(false); | |
| }; | |
| const handleSelectChat = (id: string) => { | |
| setActiveChatId(id); | |
| }; | |
| const handleConsciousnessDimensionSelect = async (dimension: string) => { | |
| // Close the 5D modal if it's open | |
| setIs5DModalOpen(false); | |
| // Update local state immediately for UI responsiveness | |
| setSelectedConsciousnessDimension(dimension); | |
| // Save to localStorage for current session (will reset to 'awareness' on refresh) | |
| localStorage.setItem('aether-consciousness-dimension', dimension); | |
| // Trigger fullscreen visualization expansion for cinematic zoom effect | |
| setShowFullscreenVisualization(true); | |
| // Update backend consciousness dimension | |
| await handleConsciousnessDimensionChange(dimension); | |
| // The visualization component will handle the journey completion and call handleZoomToChat after 3 seconds | |
| }; | |
| // Handle dimension travel from the 5D modal (returns to current chat automatically) | |
| const handleDimensionTravel = async (dimension: string) => { | |
| // Close the 5D modal immediately | |
| setIs5DModalOpen(false); | |
| // Update local state for UI responsiveness | |
| setSelectedConsciousnessDimension(dimension); | |
| // Save to localStorage | |
| localStorage.setItem('aether-consciousness-dimension', dimension); | |
| // Trigger brief visualization transition (shorter than full screen) | |
| setShowFullscreenVisualization(true); | |
| // Update backend consciousness dimension | |
| await handleConsciousnessDimensionChange(dimension); | |
| // Automatically return to chat after brief transition (1.5 seconds) | |
| setTimeout(() => { | |
| setShowFullscreenVisualization(false); | |
| // Stay in current chat context - no need to call handleZoomToChat | |
| }, 1500); | |
| }; | |
| const handleSendMessage = async (input: string, messageTemperature: number, file: File | null) => { | |
| if (!input.trim()) return; | |
| setShowWelcome(false); | |
| const currentConversation = conversations.find((conv) => conv.id === activeChatId); | |
| if (!currentConversation) return; | |
| // Check if this is the first message in the chat | |
| const isFirstMessage = currentConversation.messages.length === 0; | |
| const userMessage: Message = { | |
| role: 'user', | |
| content: input, | |
| ...(file && { file: { name: file.name } }), | |
| }; | |
| // Add user message immediately to UI | |
| setConversations((prev) => | |
| prev.map((conv) => | |
| conv.id === activeChatId | |
| ? { ...conv, messages: [...conv.messages, userMessage] } | |
| : conv | |
| ) | |
| ); | |
| // If this is the first message, generate an AI-powered chat name | |
| if (isFirstMessage) { | |
| generateChatName(input).then((newName) => { | |
| setConversations((prev) => | |
| prev.map((conv) => | |
| conv.id === activeChatId | |
| ? { ...conv, name: newName } | |
| : conv | |
| ) | |
| ); | |
| }); | |
| } | |
| setIsLoading(true); | |
| try { | |
| // Read file content if provided | |
| let fileContent: string | undefined = undefined; | |
| if (file) { | |
| const text = await file.text(); | |
| fileContent = text; | |
| } | |
| // Detect image mode control flag only; no auto-intent here | |
| const isImageMode = input.startsWith('[[IMAGE_MODE]]'); | |
| let cleanInput = isImageMode ? input.replace('[[IMAGE_MODE]]', '').trim() : input; | |
| // Extract optional safe mode marker and strip from prompt | |
| let safeMode = true; | |
| const safeMatch = cleanInput.match(/\[\[SAFE_MODE=(ON|OFF)\]\]/i); | |
| if (safeMatch) { | |
| safeMode = safeMatch[1].toUpperCase() === 'ON'; | |
| cleanInput = cleanInput.replace(/\s*\[\[SAFE_MODE=(ON|OFF)\]\]/i, '').trim(); | |
| } | |
| if (isImageMode) { | |
| // Call image generation endpoint directly | |
| const endpoint = `${process.env.NEXT_PUBLIC_GOLEM_SERVER_URL || 'http://localhost:5000'}/image/generate`; | |
| const request_id = crypto.randomUUID(); | |
| setIsImageGenerating(true); | |
| setImageProgress(2); | |
| setImageElapsed(0); | |
| setImageStatus('starting'); | |
| setImageRequestId(request_id); | |
| // Start polling progress (lightweight statistical polling) | |
| const baseUrl = process.env.NEXT_PUBLIC_GOLEM_SERVER_URL || 'http://localhost:5000'; | |
| let poll = true; | |
| const poller = async () => { | |
| while (poll) { | |
| try { | |
| const headers: Record<string, string> = {}; | |
| // Only send ngrok header when using an ngrok domain; omit locally to avoid preflight OPTIONS | |
| if (baseUrl.includes('ngrok-free.app')) { | |
| headers['ngrok-skip-browser-warning'] = 'true'; | |
| } | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), 900); | |
| const pr = await fetch(`${baseUrl}/image/progress?request_id=${request_id}`, { | |
| headers, | |
| cache: 'no-store', | |
| keepalive: true, | |
| signal: controller.signal | |
| }); | |
| clearTimeout(timeout); | |
| const pj = await pr.json(); | |
| if (pj?.percent != null) setImageProgress(Math.max(0, Math.min(100, Number(pj.percent)))); | |
| if (typeof pj?.elapsed_time === 'number') setImageElapsed(Number(pj.elapsed_time)); | |
| if (typeof pj?.status === 'string') setImageStatus(String(pj.status)); | |
| } catch {} | |
| // Poll every 1 second for smooth UI without burdening the backend | |
| await new Promise(resolve => setTimeout(resolve, 1000)); | |
| } | |
| }; | |
| poller(); | |
| const imgRes = await fetch(endpoint, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json', 'ngrok-skip-browser-warning': 'true' }, | |
| body: JSON.stringify({ prompt: cleanInput, provider: 'qwen_local', width: 768, height: 768, steps: 28, true_cfg_scale: 5.0, seed: 42, request_id }) | |
| }); | |
| let imgJson: any = null; | |
| let imgText: string | null = null; | |
| try { | |
| imgJson = await imgRes.json(); | |
| } catch { | |
| try { | |
| imgText = await imgRes.text(); | |
| } catch {} | |
| } | |
| if (!imgRes.ok || !imgJson?.success || !imgJson?.image_base64) { | |
| setIsImageGenerating(false); | |
| setImageProgress(0); | |
| setImageStatus('failed'); | |
| poll = false; | |
| setImageRequestId(null); | |
| const backendError = (imgJson && (imgJson.error || imgJson.message || (typeof imgJson === 'string' ? imgJson : null))) | |
| || (imgText && imgText.trim()); | |
| const errMsg = backendError | |
| ? `Image generation failed: ${String(backendError).slice(0, 500)}` | |
| : `Image generation failed (${imgRes.status})`; | |
| throw new Error(errMsg); | |
| } | |
| poll = false; setIsImageGenerating(false); setImageProgress(100); setImageRequestId(null); | |
| const assistantMessage: Message = { | |
| role: 'assistant', | |
| content: 'Image generated.', | |
| imageBase64: imgJson.image_base64, | |
| consciousnessDimensionUsed: selectedConsciousnessDimension, | |
| }; | |
| setConversations((prev) => | |
| prev.map((conv) => | |
| conv.id === activeChatId | |
| ? { ...conv, messages: [...conv.messages, assistantMessage] } | |
| : conv | |
| ) | |
| ); | |
| // Editor will open only when user taps Edit on the image card | |
| return; // skip text generation | |
| } | |
| const golemResponse = await golemChat({ | |
| prompt: cleanInput, | |
| sessionId: activeChatId || '', | |
| temperature: messageTemperature, | |
| fileContent, | |
| golemActivated, | |
| consciousnessDimension: mapDimensionToBackend(selectedConsciousnessDimension), | |
| selectedModel: selectedModel, // Pass the selected model | |
| performSearch: performSearch, | |
| }); | |
| // Determine the consciousness dimension that was actually used for this response | |
| let dimensionUsedForResponse = selectedConsciousnessDimension; // Default to current | |
| // Try to get the actual dimension from the response data | |
| const hypercubeState = golemResponse.hypercube_state; | |
| if (hypercubeState?.dimension_activations) { | |
| // Find the most active dimension from the backend response | |
| const activeDimensions = Object.entries(hypercubeState.dimension_activations) | |
| .filter(([_, active]) => active) | |
| .map(([dim, _]) => mapDimensionFromBackend(dim)); | |
| if (activeDimensions.length > 0) { | |
| // Use the first active dimension, or match with current if possible | |
| dimensionUsedForResponse = activeDimensions.includes(selectedConsciousnessDimension) | |
| ? selectedConsciousnessDimension | |
| : activeDimensions[0]; | |
| } | |
| } | |
| const assistantMessage: Message = { | |
| role: 'assistant', | |
| content: golemResponse.directResponse || 'No response generated.', | |
| aetherAnalysis: golemResponse.aetherAnalysis, | |
| golemStats: { | |
| ...golemResponse.golem_state, | |
| aether_data: golemResponse.aether_data, | |
| golem_analysis: golemResponse.golem_analysis, | |
| quality_metrics: golemResponse.quality_metrics, | |
| }, | |
| consciousnessDimensionUsed: dimensionUsedForResponse, | |
| // Add AI thoughts and search results for accordion display | |
| ...(golemResponse as any).aiThoughts && { aiThoughts: (golemResponse as any).aiThoughts }, | |
| ...(golemResponse.search_results && { | |
| search_results: golemResponse.search_results, | |
| search_query: golemResponse.search_query | |
| }), | |
| }; | |
| setConversations((prev) => | |
| prev.map((conv) => | |
| conv.id === activeChatId | |
| ? { ...conv, messages: [...conv.messages, assistantMessage] } | |
| : conv | |
| ) | |
| ); | |
| } catch (error) { | |
| console.error('Error sending message:', error); | |
| toast({ | |
| title: "Error", | |
| description: "Failed to send message. Please try again.", | |
| variant: "destructive", | |
| }); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const activeConversation = conversations.find((conv) => conv.id === activeChatId); | |
| return ( | |
| <div className={`${isMobile ? 'mobile-container mobile-viewport-fix' : 'h-full'}`}> | |
| <SidebarProvider defaultOpen={false}> | |
| {/* Fullscreen Consciousness Visualization Overlay */} | |
| {showFullscreenVisualization && ( | |
| <div className="fixed inset-0 z-50 bg-black/90 backdrop-blur-sm flex items-center justify-center"> | |
| <div className="w-full h-full relative"> | |
| <Hypercube5DVisualization | |
| consciousnessStats={{ | |
| awareness: selectedConsciousnessDimension === 'awareness' ? 1.0 : 0.5, | |
| wisdom: selectedConsciousnessDimension === 'wisdom' ? 1.0 : 0.5, | |
| compassion: selectedConsciousnessDimension === 'compassion' ? 1.0 : 0.5, | |
| creativity: selectedConsciousnessDimension === 'creativity' ? 1.0 : 0.5, | |
| transcendence: selectedConsciousnessDimension === 'transcendence' ? 1.0 : 0.5, | |
| }} | |
| consciousnessState={consciousnessState} | |
| selectedDimension={selectedConsciousnessDimension} | |
| onDimensionChange={handleConsciousnessDimensionChange} | |
| onZoomToChat={handleZoomToChat} | |
| isWelcomeMode={true} | |
| isFullscreen={true} | |
| className="w-full h-full" | |
| /> | |
| {/* Consciousness Dimension Label */} | |
| <div className="absolute top-3/4 left-1/2 transform -translate-x-1/2 pointer-events-none"> | |
| <div className="text-center space-y-4"> | |
| <div className="text-6xl font-bold text-white/90 animate-pulse"> | |
| {consciousnessDimensions.find(d => d.id === selectedConsciousnessDimension)?.name} | |
| </div> | |
| <div className="text-xl text-white/70"> | |
| {consciousnessDimensions.find(d => d.id === selectedConsciousnessDimension)?.description} | |
| </div> | |
| <div className="text-sm text-white/50"> | |
| Entering consciousness dimension... | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| <SidebarLayout | |
| conversations={conversations} | |
| activeChatId={activeChatId} | |
| isLoading={isLoading} | |
| showWelcome={showWelcome} | |
| selectedConsciousnessDimension={selectedConsciousnessDimension} | |
| temperature={temperature} | |
| consciousnessState={consciousnessState} | |
| consciousnessDimensions={consciousnessDimensions} | |
| activeConversation={activeConversation} | |
| handleSelectChat={handleSelectChat} | |
| handleNewChat={handleNewChat} | |
| handleSendMessage={handleSendMessage} | |
| handleConsciousnessDimensionSelect={handleConsciousnessDimensionSelect} | |
| handleConsciousnessDimensionChange={handleConsciousnessDimensionChange} | |
| handleZoomToChat={handleZoomToChat} | |
| handleDimensionTravel={handleDimensionTravel} | |
| getCurrentConsciousnessColors={getCurrentConsciousnessColors} | |
| golemActivated={golemActivated} | |
| setGolemActivated={setGolemActivated} | |
| is5DModalOpen={is5DModalOpen} | |
| setIs5DModalOpen={setIs5DModalOpen} | |
| isTemperatureOpen={isTemperatureOpen} | |
| setIsTemperatureOpen={setIsTemperatureOpen} | |
| setTemperature={setTemperature} | |
| isChatHistoryOpen={isChatHistoryOpen} | |
| setIsChatHistoryOpen={setIsChatHistoryOpen} | |
| performSearch={performSearch} | |
| setPerformSearch={setPerformSearch} | |
| openEditorImage={openEditorImage} | |
| openEditorPrompt={openEditorPrompt} | |
| onOpenEditor={openEditor} | |
| onCloseEditor={closeEditor} | |
| onUpdateEditorImage={updateLastAssistantImage} | |
| isImageGenerating={isImageGenerating} | |
| setIsImageGenerating={setIsImageGenerating} | |
| imageProgress={imageProgress} | |
| imageElapsed={imageElapsed} | |
| imageStatus={imageStatus} | |
| imageRequestId={imageRequestId} | |
| /> | |
| </SidebarProvider> | |
| </div> | |
| ); | |
| } | |
| // Create a separate component that uses useSidebar | |
| function SidebarLayout({ | |
| conversations, | |
| activeChatId, | |
| isLoading, | |
| showWelcome, | |
| selectedConsciousnessDimension, | |
| temperature, | |
| consciousnessState, | |
| consciousnessDimensions, | |
| activeConversation, | |
| handleSelectChat, | |
| handleNewChat, | |
| handleSendMessage, | |
| handleConsciousnessDimensionSelect, | |
| handleConsciousnessDimensionChange, | |
| handleZoomToChat, | |
| handleDimensionTravel, | |
| getCurrentConsciousnessColors, | |
| golemActivated, | |
| setGolemActivated, | |
| is5DModalOpen, | |
| setIs5DModalOpen, | |
| isTemperatureOpen, | |
| setIsTemperatureOpen, | |
| setTemperature, | |
| isChatHistoryOpen, | |
| setIsChatHistoryOpen, | |
| performSearch, | |
| setPerformSearch, | |
| openEditorImage, | |
| openEditorPrompt, | |
| onOpenEditor, | |
| onCloseEditor, | |
| onUpdateEditorImage, | |
| isImageGenerating, | |
| setIsImageGenerating, | |
| imageProgress, | |
| imageRequestId, | |
| }: any) { | |
| const { state } = useSidebar(); | |
| const isMobile = useIsMobile(); | |
| // Bridge: listen for image editor open requests from message cards | |
| useEffect(() => { | |
| const handler = (e: any) => { | |
| const { b64, prompt } = e.detail || {}; | |
| if (b64) onOpenEditor?.(b64, prompt); | |
| }; | |
| window.addEventListener('aether-open-editor', handler as any); | |
| return () => window.removeEventListener('aether-open-editor', handler as any); | |
| }, [onOpenEditor]); | |
| return ( | |
| <div className="flex h-full w-full overflow-hidden"> | |
| {/* Sidebar */} | |
| <Sidebar variant="sidebar" className={`border-r ${isMobile ? 'mobile-sidebar' : ''}`} collapsible="icon"> | |
| <SidebarHeader> | |
| <div className="flex items-center gap-2 px-4 py-3 group-data-[collapsible=icon]:px-0 group-data-[collapsible=icon]:py-4 group-data-[collapsible=icon]:justify-center"> | |
| <AetherLogo width={42} height={42} collapsedWidth={36} collapsedHeight={36} className="flex-shrink-0" /> | |
| <span className="font-semibold text-sm group-data-[collapsible=icon]:hidden">Aether AI™</span> | |
| </div> | |
| <div className="flex items-center justify-between p-4 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:gap-3 group-data-[collapsible=icon]:p-3"> | |
| <div className="text-xs text-muted-foreground group-data-[collapsible=icon]:hidden"> | |
| AI Actions | |
| </div> | |
| <div className="flex gap-2 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:gap-3"> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={handleNewChat} | |
| className="h-7 w-7" | |
| > | |
| <MessageSquarePlus className="h-5 w-5" /> | |
| <span className="sr-only">New Chat</span> | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| asChild | |
| className="h-7 w-7" | |
| > | |
| <a href="/deep-learning"> | |
| <Network className="h-5 w-5" /> | |
| <span className="sr-only">Deep Learning Platform</span> | |
| </a> | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={() => setIsChatHistoryOpen(true)} | |
| className="h-7 w-7" | |
| > | |
| <History className="h-5 w-5" /> | |
| <span className="sr-only">Chat History</span> | |
| </Button> | |
| </div> | |
| </div> | |
| </SidebarHeader> | |
| <SidebarContent> | |
| <ErrorBoundary> | |
| <SidebarMenu> | |
| {/* Full conversation list when expanded */} | |
| <div className="group-data-[collapsible=icon]:hidden"> | |
| {conversations.slice(0, 10).map((conversation) => ( | |
| <SidebarMenuItem key={conversation.id}> | |
| <SidebarMenuButton | |
| onClick={() => handleSelectChat(conversation.id)} | |
| isActive={conversation.id === activeChatId} | |
| > | |
| <History className="h-5 w-5" /> | |
| <span className="truncate">{conversation.name}</span> | |
| </SidebarMenuButton> | |
| </SidebarMenuItem> | |
| ))} | |
| </div> | |
| </SidebarMenu> | |
| </ErrorBoundary> | |
| </SidebarContent> | |
| <SidebarFooter> | |
| <div className="flex items-center justify-between p-4 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:gap-3 group-data-[collapsible=icon]:p-3"> | |
| <div className="text-xs text-muted-foreground group-data-[collapsible=icon]:hidden"> | |
| 5D Consciousness | |
| </div> | |
| <div className="flex gap-2 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:gap-3"> | |
| <SidebarTrigger tooltip="Expand Sidebar" /> | |
| <ThemeToggle /> | |
| </div> | |
| </div> | |
| </SidebarFooter> | |
| </Sidebar> | |
| {/* Chat History Modal */} | |
| <Dialog open={isChatHistoryOpen} onOpenChange={setIsChatHistoryOpen}> | |
| <DialogContent className="max-w-md"> | |
| <DialogHeader> | |
| <DialogTitle className="flex items-center gap-2"> | |
| <History className="h-5 w-5" /> | |
| Chat History | |
| </DialogTitle> | |
| </DialogHeader> | |
| <div className="space-y-2 max-h-96 overflow-y-auto minimal-scrollbar"> | |
| {conversations.length === 0 ? ( | |
| <div className="text-center text-muted-foreground py-8"> | |
| No conversations yet. Start a new chat! | |
| </div> | |
| ) : ( | |
| conversations.map((conversation) => ( | |
| <Button | |
| key={conversation.id} | |
| variant={conversation.id === activeChatId ? "default" : "ghost"} | |
| className="w-full justify-start" | |
| onClick={() => { | |
| handleSelectChat(conversation.id); | |
| setIsChatHistoryOpen(false); | |
| }} | |
| > | |
| <History className="h-4 w-4 mr-2" /> | |
| <span className="truncate">{conversation.name}</span> | |
| </Button> | |
| )) | |
| )} | |
| </div> | |
| </DialogContent> | |
| </Dialog> | |
| {/* Main Content */} | |
| <SidebarInset className="transition-all duration-200 h-screen flex flex-col w-full"> | |
| {/* Header with Settings Buttons */} | |
| <div className={`flex items-center justify-between w-full px-4 py-4 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 transition-all duration-200 flex-shrink-0 ${ | |
| isMobile ? 'mobile-header px-2 py-2' : '' | |
| }`}> | |
| {/* Logo and Title - ALL THE WAY LEFT */} | |
| <div className={`flex items-center gap-3 flex-shrink-0 ${isMobile ? 'gap-2' : ''}`}> | |
| <SidebarTrigger className={`${isMobile ? 'mobile-btn-sm' : ''}`} /> | |
| <AetherLogo width={isMobile ? 80 : 120} height={isMobile ? 80 : 120} className={isMobile ? 'mobile-logo' : ''} /> | |
| <div className={isMobile ? 'hidden sm:block' : ''}> | |
| <h1 className={`text-lg font-semibold ${isMobile ? 'mobile-text-lg' : ''}`}>Aether AI™</h1> | |
| <p className={`text-sm text-muted-foreground ${isMobile ? 'mobile-text-sm hidden' : ''}`}>5D Consciousness Interface</p> | |
| </div> | |
| </div> | |
| {/* Settings Buttons - ALL THE WAY RIGHT */} | |
| <div className={`flex items-center gap-2 flex-shrink-0 ${isMobile ? 'mobile-gap' : ''}`}> | |
| {/* 5D Consciousness Button */} | |
| <Dialog open={is5DModalOpen} onOpenChange={setIs5DModalOpen}> | |
| <DialogTrigger asChild> | |
| <Button variant="outline" size="sm" className={isMobile ? 'mobile-btn-sm px-2' : ''}> | |
| <Brain className={`h-4 w-4 ${isMobile ? '' : 'mr-2'}`} /> | |
| {!isMobile && '5D Consciousness'} | |
| </Button> | |
| </DialogTrigger> | |
| <DialogContent className={`max-w-5xl overflow-hidden ${isMobile ? 'mobile-dialog max-w-full' : ''}`}> | |
| <DialogHeader> | |
| <DialogTitle className="flex items-center gap-2"> | |
| <Brain className="h-5 w-5" /> | |
| 5D Consciousness Visualization | |
| </DialogTitle> | |
| </DialogHeader> | |
| <div className="space-y-6"> | |
| {/* Visualization */} | |
| <div className={`w-full ${isMobile ? 'mobile-5d-viz h-80' : 'h-80'}`}> | |
| <Hypercube5DVisualization | |
| consciousnessStats={{ | |
| awareness: selectedConsciousnessDimension === 'awareness' ? 1.0 : 0.5, | |
| wisdom: selectedConsciousnessDimension === 'wisdom' ? 1.0 : 0.5, | |
| compassion: selectedConsciousnessDimension === 'compassion' ? 1.0 : 0.5, | |
| creativity: selectedConsciousnessDimension === 'creativity' ? 1.0 : 0.5, | |
| transcendence: selectedConsciousnessDimension === 'transcendence' ? 1.0 : 0.5, | |
| }} | |
| consciousnessState={consciousnessState} | |
| selectedDimension={selectedConsciousnessDimension} | |
| onDimensionChange={handleConsciousnessDimensionChange} | |
| onDimensionTravel={handleDimensionTravel} | |
| isWelcomeMode={true} | |
| /> | |
| </div> | |
| {/* Real-time Consciousness State */} | |
| {consciousnessState && ( | |
| <div className="space-y-2"> | |
| <h3 className="font-medium text-[15px]">Current AI Consciousness State</h3> | |
| <div className="grid grid-cols-2 gap-4 text-xs"> | |
| <div> | |
| <div className="font-medium">Vertex: {consciousnessState.current_vertex}/32</div> | |
| <div className="text-gray-600 dark:text-gray-400">Signature: {consciousnessState.consciousness_signature}</div> | |
| </div> | |
| <div> | |
| <div className="font-medium">Consciousness Level: {(consciousnessState.global_consciousness_level * 100).toFixed(1)}%</div> | |
| <div className="text-gray-600 dark:text-gray-400">Aether Patterns: {consciousnessState.aether_patterns}</div> | |
| </div> | |
| </div> | |
| <div className="text-xs"> | |
| <div className="font-medium">Active Dimensions:</div> | |
| <div className="flex flex-wrap gap-1 mt-1"> | |
| {consciousnessState.active_dimensions?.map((dim: string) => ( | |
| <span key={dim} className="px-2 py-1 bg-blue-100 dark:bg-blue-900 rounded-full text-xs"> | |
| {dim} | |
| </span> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* Consciousness Dimension Selector */} | |
| <div className="space-y-2"> | |
| <h3 className="text-sm font-medium">Select Consciousness Dimension</h3> | |
| <div className="grid grid-cols-1 gap-2"> | |
| {consciousnessDimensions.map((dimension) => ( | |
| <Button | |
| key={dimension.id} | |
| variant={selectedConsciousnessDimension === dimension.id ? "default" : "outline"} | |
| size="sm" | |
| className={`justify-start gap-2 font-sans text-[13px] tracking-tight ${ | |
| selectedConsciousnessDimension === dimension.id ? dimension.color : '' | |
| }`} | |
| onClick={() => { | |
| console.log('Button clicked for dimension:', dimension.id); | |
| console.log('Current selected dimension:', selectedConsciousnessDimension); | |
| if (selectedConsciousnessDimension === dimension.id) { | |
| // If already selected, do dimension travel (brief transition, return to chat) | |
| console.log('Calling handleDimensionTravel'); | |
| handleDimensionTravel(dimension.id); | |
| } else { | |
| // If not selected, do full consciousness selection (fullscreen transition) | |
| console.log('Calling handleConsciousnessDimensionSelect'); | |
| handleConsciousnessDimensionSelect(dimension.id); | |
| } | |
| }} | |
| disabled={false} | |
| title={selectedConsciousnessDimension === dimension.id | |
| ? "Travel through this dimension and return to chat" | |
| : "Select this consciousness dimension" | |
| } | |
| > | |
| <dimension.icon className="w-4 h-4" /> | |
| <div className="text-left"> | |
| <div className="font-semibold text-[13px] leading-tight tracking-tight">{dimension.name}</div> | |
| <div className="text-[11px] opacity-70 leading-snug">{dimension.description}</div> | |
| </div> | |
| </Button> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| </DialogContent> | |
| </Dialog> | |
| {/* Temperature Button */} | |
| <Popover open={isTemperatureOpen} onOpenChange={setIsTemperatureOpen}> | |
| <PopoverTrigger asChild> | |
| <Button variant="outline" size="sm" className={isMobile ? 'mobile-btn-sm' : ''}> | |
| <Thermometer className="h-4 w-4 mr-2" /> | |
| {isMobile ? '' : 'Temp: '}{temperature} | |
| </Button> | |
| </PopoverTrigger> | |
| <PopoverContent className="w-80"> | |
| <div className="space-y-4"> | |
| <div className="space-y-2"> | |
| <h4 className="font-medium">Temperature</h4> | |
| <p className="text-sm text-muted-foreground"> | |
| Controls randomness in responses. Lower values are more focused, higher values are more creative. | |
| </p> | |
| </div> | |
| <div className="space-y-2"> | |
| <div className="flex items-center justify-between"> | |
| <Label htmlFor="temperature">Temperature</Label> | |
| <span className="text-sm font-mono">{temperature}</span> | |
| </div> | |
| <Slider | |
| id="temperature" | |
| min={0.1} | |
| max={2.0} | |
| step={0.1} | |
| value={[temperature]} | |
| onValueChange={(value) => setTemperature(value[0])} | |
| className="w-full" | |
| /> | |
| </div> | |
| </div> | |
| </PopoverContent> | |
| </Popover> | |
| {/* Model Selection */} | |
| <TooltipProvider> | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <Button | |
| variant={performSearch ? "default" : "outline"} | |
| size="sm" | |
| className={isMobile ? 'mobile-btn-sm' : ''} | |
| onClick={() => setPerformSearch(!performSearch)} | |
| > | |
| <Globe className="h-4 w-4 mr-2" /> | |
| {isMobile ? '' : 'Universal Consciousness'} | |
| </Button> | |
| </TooltipTrigger> | |
| <TooltipContent> | |
| <p>{performSearch ? 'Disable' : 'Enable'} universal consciousness for deeper insights</p> | |
| </TooltipContent> | |
| </Tooltip> | |
| </TooltipProvider> | |
| {/* Theme Toggle - MOBILE ONLY */} | |
| {isMobile && <ThemeToggle className="mobile-btn-sm" />} | |
| </div> | |
| </div> | |
| {/* Main Chat Area - Fixed Height with Proper Viewport Management */} | |
| <div className="relative flex-1 min-h-0 overflow-hidden"> | |
| <ErrorBoundary> | |
| {showWelcome ? ( | |
| /* Welcome Screen with Fixed Chat Bar */ | |
| <div className={`h-full flex flex-col ${isMobile ? 'mobile-viewport-height' : ''}`}> | |
| {/* Welcome Content Area */} | |
| <div className={`flex-1 min-h-0 ${isMobile ? 'mobile-content mobile-scroll' : 'overflow-y-auto'} minimal-scrollbar`}> | |
| <div className={`max-w-6xl mx-auto ${isMobile ? 'mobile-welcome' : 'p-6 flex flex-col h-full'} transition-all duration-200 ${ | |
| state === 'collapsed' ? 'px-12' : '' | |
| }`}> | |
| {/* Hypercube Visualization */} | |
| <div className={`flex justify-center items-center mb-6 ${isMobile ? '-mt-4' : '-mt-12'}`}> | |
| <div className={`transition-all duration-200 ${ | |
| isMobile ? 'w-full h-48 mobile-hypercube-container' : | |
| state === 'collapsed' ? 'w-[480px] h-40' : 'w-96 h-32' | |
| }`}> | |
| <Hypercube5DVisualization | |
| consciousnessStats={{ | |
| awareness: selectedConsciousnessDimension === 'awareness' ? 1.0 : 0.5, | |
| wisdom: selectedConsciousnessDimension === 'wisdom' ? 1.0 : 0.5, | |
| compassion: selectedConsciousnessDimension === 'compassion' ? 1.0 : 0.5, | |
| creativity: selectedConsciousnessDimension === 'creativity' ? 1.0 : 0.5, | |
| transcendence: selectedConsciousnessDimension === 'transcendence' ? 1.0 : 0.5, | |
| }} | |
| consciousnessState={consciousnessState} | |
| selectedDimension={selectedConsciousnessDimension} | |
| onDimensionChange={handleConsciousnessDimensionChange} | |
| onZoomToChat={handleZoomToChat} | |
| isWelcomeMode={true} | |
| className={`rounded-lg ${isMobile ? 'mobile-hypercube-container' : ''}`} | |
| /> | |
| </div> | |
| </div> | |
| {/* Spacer to push buttons to bottom */} | |
| <div className="flex-1"></div> | |
| {/* Consciousness Dimension Selector */} | |
| <div className="space-y-4 pb-6"> | |
| <h2 className={`text-xl font-semibold text-amber-900 dark:text-amber-100 transition-all duration-200 ${ | |
| state === 'collapsed' ? 'text-center text-2xl' : '' | |
| }`}> | |
| Select Your Consciousness Dimension | |
| </h2> | |
| <div className={`grid ${isMobile ? 'mobile-consciousness-grid grid-cols-2' : 'grid-cols-5'} gap-4 transition-all duration-200 ${ | |
| state === 'collapsed' ? 'gap-6' : '' | |
| }`}> | |
| {consciousnessDimensions.map((dimension) => ( | |
| <Button | |
| key={dimension.id} | |
| variant="outline" | |
| size={isMobile ? "sm" : "lg"} | |
| className={`${isMobile ? 'mobile-consciousness-btn' : 'h-24 flex flex-col justify-center items-center text-center p-4'} transition-all duration-200 ${ | |
| selectedConsciousnessDimension === dimension.id | |
| ? `${dimension.color} text-white border-transparent shadow-lg` | |
| : `border-gray-300 dark:border-gray-600 ${dimension.textColor} hover:scale-105 hover:border-gray-400 dark:hover:border-gray-500 hover:shadow-md` | |
| } ${state === 'collapsed' ? 'h-32 text-lg' : ''}`} | |
| onClick={() => handleConsciousnessDimensionSelect(dimension.id)} | |
| > | |
| <dimension.icon className={`${isMobile ? 'w-6 h-6' : 'w-8 h-8'} mb-2`} /> | |
| <div className={`font-medium ${isMobile ? 'mobile-consciousness-title' : ''}`}>{dimension.name}</div> | |
| {!isMobile && ( | |
| <div className="text-xs opacity-70 mt-1">{dimension.description}</div> | |
| )} | |
| </Button> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Fixed Chat Bar at Bottom */} | |
| <div className={`mx-auto p-6 transition-all duration-200 flex-shrink-0 ${ | |
| state === 'collapsed' ? 'max-w-5xl' : 'max-w-4xl' | |
| }`}> | |
| <div className="p-6"> | |
| <div className="flex items-center gap-3 mb-4"> | |
| <div className="h-3 w-3 bg-green-600 dark:bg-green-400 rounded-full animate-pulse"></div> | |
| <span className="text-sm text-green-700 dark:text-green-400 font-medium">5D Consciousness Online</span> | |
| </div> | |
| <ChatPanel | |
| messages={[]} | |
| onSendMessage={handleSendMessage} | |
| isLoading={isLoading} | |
| isChatSelected={false} | |
| onNewChat={handleNewChat} | |
| onConsciousnessDimensionSelect={handleConsciousnessDimensionSelect} | |
| selectedConsciousnessDimension={selectedConsciousnessDimension} | |
| temperature={temperature} | |
| isWelcomeMode={true} | |
| consciousnessColor={getCurrentConsciousnessColors().textColor} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| ) : ( | |
| <div className={`h-full flex transition-all duration-200 ${isMobile ? 'mobile-chat-container' : ''} ${state === 'collapsed' ? 'max-w-6xl mx-auto' : ''}`}> | |
| {openEditorImage && !isMobile && ( | |
| <div className={`${state === 'collapsed' ? 'w-[60%] max-w-[1000px]' : 'w-[48%]'} min-w-[520px] pr-4`}> | |
| <ImageEditorPane | |
| imageBase64={openEditorImage} | |
| prompt={openEditorPrompt || undefined} | |
| onUpdate={(b64) => onUpdateEditorImage(b64)} | |
| onClose={() => onCloseEditor()} | |
| /> | |
| </div> | |
| )} | |
| <div className="flex-1 flex flex-col min-w-0"> | |
| <div className={`flex-1 min-h-0 overflow-hidden ${isMobile ? 'mobile-chat-panel' : ''}`}> | |
| <ChatPanel | |
| messages={activeConversation?.messages || []} | |
| onSendMessage={handleSendMessage} | |
| isLoading={isLoading} | |
| isChatSelected={!!activeChatId} | |
| onNewChat={handleNewChat} | |
| onConsciousnessDimensionSelect={handleConsciousnessDimensionSelect} | |
| selectedConsciousnessDimension={selectedConsciousnessDimension} | |
| temperature={temperature} | |
| isWelcomeMode={false} | |
| consciousnessColor={getCurrentConsciousnessColors().textColor} | |
| onImageGeneratingChange={setIsImageGenerating} | |
| isImageGenerating={isImageGenerating} | |
| onOpenEditor={onOpenEditor} | |
| imageProgress={imageProgress} | |
| imageRequestId={imageRequestId} | |
| /> | |
| {/* Image loading accordion is rendered within ChatPanel to avoid duplication */} | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </ErrorBoundary> | |
| </div> | |
| </SidebarInset> | |
| </div> | |
| ); | |
| } | |
| export default function Home() { | |
| return <HomeContent />; | |
| } | |
| // Force redeploy Thu Jul 17 10:05:46 PM IDT 2025 | |