From 94fcde74c96a07f5286906323eae1ddae6db713c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 26 Feb 2026 17:53:22 +0000 Subject: [PATCH] 5 --- frontend/src/pages/observation.tsx | 636 ++++++++++++----------------- 1 file changed, 250 insertions(+), 386 deletions(-) diff --git a/frontend/src/pages/observation.tsx b/frontend/src/pages/observation.tsx index fc26b09..225e6f8 100644 --- a/frontend/src/pages/observation.tsx +++ b/frontend/src/pages/observation.tsx @@ -16,67 +16,88 @@ import { mdiAutoFix, mdiAccountGroup, mdiStarCircle, - mdiChatProcessingOutline + mdiChatProcessingOutline, + mdiSend, } from '@mdi/js'; import BaseIcon from '../components/BaseIcon'; -import { useAppDispatch } from '../stores/hooks'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { fetch as fetchSkyObjects } from '../stores/sky_objects/sky_objectsSlice'; +import { askGpt } from '../stores/openAiSlice'; import LayoutAuthenticated from '../layouts/Authenticated'; const CELEBRITIES = [ - { id: 'beyonce', name: 'Beyoncé', category: 'Singer', reaction: 'This view is absolutely flawless! Like a diamond in the sky.' }, - { id: 'jackson', name: 'Michael Jackson', category: 'Singer', reaction: 'Hee-hee! Looking at the stars is a thriller!' }, - { id: 'mercury', name: 'Freddie Mercury', category: 'Singer', reaction: 'I see a little silhouetto of a galaxy! Magnificent!' }, - { id: 'swift', name: 'Taylor Swift', category: 'Singer', reaction: 'I can see the sparks fly in that nebula. Enchanting!' }, - { id: 'dicaprio', name: 'Leonardo DiCaprio', category: 'Actor', reaction: 'I\'m the king of the world... or at least this telescope!' }, - { id: 'streep', name: 'Meryl Streep', category: 'Actress', reaction: 'The performance of these celestial bodies is award-worthy.' }, - { id: 'hanks', name: 'Tom Hanks', category: 'Actor', reaction: 'Houston, we have an incredible view here!' }, - { id: 'davinci', name: 'Leonardo da Vinci', category: 'Painter', reaction: 'The proportions of this universe are divine. A true masterpiece.' }, - { id: 'vangogh', name: 'Vincent van Gogh', category: 'Painter', reaction: 'The starry night... it\'s even more vibrant than I painted it!' }, - { id: 'kahlo', name: 'Frida Kahlo', category: 'Painter', reaction: 'Feet, what do I need them for if I have wings to fly to these stars?' }, + { + id: 'beyonce', + name: 'Beyoncé', + category: 'Singer', + img: 'https://images.pexels.com/photos/2747449/pexels-photo-2747449.jpeg?auto=compress&cs=tinysrgb&w=400', + personality: 'Elegant, confident, and uses musical metaphors. Calls fans "Hive".', + reaction: 'This view is absolutely flawless! Like a diamond in the sky.' + }, + { + id: 'jackson', + name: 'Michael Jackson', + category: 'Singer', + img: 'https://images.pexels.com/photos/167441/pexels-photo-167441.jpeg?auto=compress&cs=tinysrgb&w=400', + personality: 'Energetic, kind, uses catchphrases like "Hee-hee" and "Shamone". Loves the magic of nature.', + reaction: 'Hee-hee! Looking at the stars is a thriller!' + }, + { + id: 'mercury', + name: 'Freddie Mercury', + category: 'Singer', + img: 'https://images.pexels.com/photos/1763075/pexels-photo-1763075.jpeg?auto=compress&cs=tinysrgb&w=400', + personality: 'Theatrical, flamboyant, and grand. Loves opera and drama.', + reaction: 'I see a little silhouetto of a galaxy! Magnificent!' + }, + { + id: 'swift', + name: 'Taylor Swift', + category: 'Singer', + img: 'https://images.pexels.com/photos/1105666/pexels-photo-1105666.jpeg?auto=compress&cs=tinysrgb&w=400', + personality: 'Storyteller, poetic, mentions "eras" and "sparks flying". Very relatable and friendly.', + reaction: 'I can see the sparks fly in that nebula. Enchanting!' + }, + { + id: 'dicaprio', + name: 'Leonardo DiCaprio', + category: 'Actor', + img: 'https://images.pexels.com/photos/1587009/pexels-photo-1587009.jpeg?auto=compress&cs=tinysrgb&w=400', + personality: 'Passionate about the environment and exploration. Intense and focused.', + reaction: 'I\'m the king of the world... or at least this telescope!' + }, + { + id: 'streep', + name: 'Meryl Streep', + category: 'Actress', + img: 'https://images.pexels.com/photos/2836486/pexels-photo-2836486.jpeg?auto=compress&cs=tinysrgb&w=400', + personality: 'Sophisticated, masterful, and appreciates fine detail and craft.', + reaction: 'The performance of these celestial bodies is award-worthy.' + }, + { + id: 'davinci', + name: 'Leonardo da Vinci', + category: 'Painter', + img: 'https://images.pexels.com/photos/33152/european-rari-da-vinci-mona-lisa.jpg?auto=compress&cs=tinysrgb&w=400', + personality: 'Scientific, curious, observant. Mentions geometry and anatomy of the universe.', + reaction: 'The proportions of this universe are divine. A true masterpiece.' + }, + { + id: 'vangogh', + name: 'Vincent van Gogh', + category: 'Painter', + img: 'https://images.pexels.com/photos/161154/vincents-bedroom-in-arles-vincent-van-gogh-artist-painting-161154.jpeg?auto=compress&cs=tinysrgb&w=400', + personality: 'Emotional, sensitive to light and color. Sees swirls in the sky.', + reaction: 'The starry night... it\'s even more vibrant than I painted it!' + } ]; const PRESET_TARGETS = [ - { - id: 'mars', - name: 'Mars', - type: 'Planet', - img: 'https://images-assets.nasa.gov/image/PIA04591/PIA04591~medium.jpg', - dist: '225M km', - temp: '210K' - }, - { - id: 'jupiter', - name: 'Jupiter', - type: 'Planet', - img: 'https://images-assets.nasa.gov/image/PIA04866/PIA04866~medium.jpg', - dist: '778M km', - temp: '110K' - }, - { - id: 'orion', - name: 'Orion Nebula', - type: 'Nebula', - img: 'https://images-assets.nasa.gov/image/PIA08653/PIA08653~medium.jpg', - dist: '1,344 ly', - temp: '10,000K' - }, - { - id: 'andromeda', - name: 'Andromeda', - type: 'Galaxy', - img: 'https://images-assets.nasa.gov/image/PIA15416/PIA15416~medium.jpg', - dist: '2.5M ly', - temp: '2.7K' - }, - { - id: 'pillars', - name: 'Pillars of Creation', - type: 'Nebula', - img: 'https://images-assets.nasa.gov/image/as11-40-5874/as11-40-5874~medium.jpg', - dist: '6,500 ly', - temp: '15K' - } + { id: 'mars', name: 'Mars', type: 'Planet', img: 'https://images-assets.nasa.gov/image/PIA04591/PIA04591~medium.jpg', dist: '225M km' }, + { id: 'jupiter', name: 'Jupiter', type: 'Planet', img: 'https://images-assets.nasa.gov/image/PIA04866/PIA04866~medium.jpg', dist: '778M km' }, + { id: 'orion', name: 'Orion Nebula', type: 'Nebula', img: 'https://images-assets.nasa.gov/image/PIA08653/PIA08653~medium.jpg', dist: '1,344 ly' }, + { id: 'andromeda', name: 'Andromeda', type: 'Galaxy', img: 'https://images-assets.nasa.gov/image/PIA15416/PIA15416~medium.jpg', dist: '2.5M ly' }, + { id: 'pillars', name: 'Pillars of Creation', type: 'Nebula', img: 'https://images-assets.nasa.gov/image/as11-40-5874/as11-40-5874~medium.jpg', dist: '6,500 ly' } ]; const ObservationPage = () => { @@ -87,11 +108,7 @@ const ObservationPage = () => { const [selectedTarget, setSelectedTarget] = useState(null); const [isFocusing, setIsFocusing] = useState(false); const [isSharpnessMax, setIsSharpnessMax] = useState(false); - const [telemetry, setTelemetry] = useState({ - temp: -233, - dist: 1.5, - focal: 131, - }); + const [telemetry, setTelemetry] = useState({ temp: -233, dist: 1.5, focal: 131 }); // Simulation States const [audienceCount, setAudienceCount] = useState(0); @@ -99,14 +116,19 @@ const ObservationPage = () => { const [activeCelebrities, setActiveCelebrities] = useState([]); const [chatMessages, setChatMessages] = useState([]); const [showSimPanel, setShowSimPanel] = useState(false); + + // Interaction State + const [userQuery, setUserQuery] = useState(''); + const [isAsking, setIsAsking] = useState(false); + const [lastResponse, setLastResponse] = useState(null); // Recording states const [isRecording, setIsRecording] = useState(false); const [mediaRecorder, setMediaRecorder] = useState(null); - const [recordedChunks, setRecordedChunks] = useState([]); const [videoUrl, setVideoUrl] = useState(null); const dispatch = useAppDispatch(); + const { gptResponse } = useAppSelector((state) => state.openAi); useEffect(() => { dispatch(fetchSkyObjects({})); @@ -115,11 +137,7 @@ const ObservationPage = () => { const startCamera = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ - video: { - facingMode: 'environment', - width: { ideal: 1920 }, - height: { ideal: 1080 } - }, + video: { facingMode: 'environment', width: { ideal: 1920 }, height: { ideal: 1080 } }, audio: true }); if (videoRef.current) { @@ -128,23 +146,17 @@ const ObservationPage = () => { } } catch (err) { console.error("Camera/Audio access denied:", err); - alert("Camera and Microphone access are required for full simulation recording."); } }; const stopCamera = () => { if (videoRef.current && videoRef.current.srcObject) { - const tracks = (videoRef.current.srcObject as MediaStream).getTracks(); - tracks.forEach(track => track.stop()); + (videoRef.current.srcObject as MediaStream).getTracks().forEach(track => track.stop()); setIsCameraActive(false); if (isRecording) stopRecording(); } }; - useEffect(() => { - return () => stopCamera(); - }, []); - // Audience Simulation Logic useEffect(() => { let interval: any; @@ -152,28 +164,23 @@ const ObservationPage = () => { interval = setInterval(() => { setAudienceCount(prev => { const target = 1000000; - if (prev < target) { - return Math.min(prev + Math.floor(Math.random() * 5000) + 1000, target); - } - return target; + return prev < target ? Math.min(prev + Math.floor(Math.random() * 8000) + 2000, target) : target; }); - // Generate Chat Messages - if (activeCelebrities.length > 0 && Math.random() > 0.7) { + if (activeCelebrities.length > 0 && Math.random() > 0.85) { const randomCelebId = activeCelebrities[Math.floor(Math.random() * activeCelebrities.length)]; const celeb = CELEBRITIES.find(c => c.id === randomCelebId); if (celeb) { - const newMessage = { + setChatMessages(prev => [{ id: Date.now(), name: celeb.name, category: celeb.category, text: celeb.reaction, time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) - }; - setChatMessages(prev => [newMessage, ...prev].slice(0, 10)); + }, ...prev].slice(0, 10)); } } - }, 2000); + }, 1500); } else { setAudienceCount(0); setChatMessages([]); @@ -181,30 +188,56 @@ const ObservationPage = () => { return () => clearInterval(interval); }, [isSimActive, activeCelebrities]); - const toggleCelebrity = (id: string) => { - setActiveCelebrities(prev => - prev.includes(id) ? prev.filter(c => c !== id) : [...prev, id] - ); - }; + const handleAskCelebrities = async () => { + if (!userQuery.trim() || activeCelebrities.length === 0) return; + + setIsAsking(true); + const selectedNames = activeCelebrities.map(id => CELEBRITIES.find(c => c.id === id)?.name).join(', '); + const celebContext = activeCelebrities.map(id => { + const c = CELEBRITIES.find(x => x.id === id); + return `${c?.name} (${c?.personality})`; + }).join('; '); - const selectAllCelebrities = () => { - setActiveCelebrities(CELEBRITIES.map(c => c.id)); + const prompt = `You are simulating a live conversation between the user and these celebrities: ${selectedNames}. + User is observing ${selectedTarget ? selectedTarget.name : 'the cosmos'} through the James Webb Telescope. + Celebrity Contexts: ${celebContext}. + User asks: "${userQuery}". + Respond as one or more of these celebrities, staying perfectly in character. Keep it short, real-time, and exciting. + Format: [Name]: "Message"`; + + try { + const resultAction = await dispatch(askGpt(prompt)); + if (askGpt.fulfilled.match(resultAction)) { + const response = resultAction.payload.data; + const nameMatch = response.match(/\\\[(.*?)\\\]:\s*"(.*?)\\/); + + const newMessage = { + id: Date.now(), + name: nameMatch ? nameMatch[1] : (activeCelebrities.length > 0 ? CELEBRITIES.find(c => c.id === activeCelebrities[0])?.name : 'AI'), + category: 'Interactive Response', + text: nameMatch ? nameMatch[2] : response, + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), + isAi: true + }; + + setChatMessages(prev => [newMessage, ...prev].slice(0, 15)); + setLastResponse(newMessage); + } + } catch (err) { + console.error(err); + } finally { + setIsAsking(false); + setUserQuery(''); + } }; const handleZoom = (direction: 'in' | 'out') => { - setZoom(prev => { - if (direction === 'in') { - return Math.min(prev * 2.5, 100000000000000); - } else { - return Math.max(prev / 2.5, 1); - } - }); + setZoom(prev => direction === 'in' ? Math.min(prev * 2.5, 1e14) : Math.max(prev / 2.5, 1)); }; const selectTarget = (target: any) => { setSelectedTarget(target); setIsFocusing(true); - let currentZoom = zoom; const targetZoom = 1000000; const interval = setInterval(() => { @@ -219,394 +252,225 @@ const ObservationPage = () => { }, 100); }; - useEffect(() => { - const interval = setInterval(() => { - setTelemetry(prev => ({ - temp: prev.temp + (Math.random() - 0.5), - dist: 1.5 + (Math.random() - 0.5) * 0.01, - focal: 131.4 + (Math.random() - 0.5), - })); - }, 2000); - return () => clearInterval(interval); - }, []); - - const getFilter = () => { - let base = 'none'; - switch (mode) { - case 'ir': base = 'contrast(1.2) brightness(1.1) hue-rotate(180deg) saturate(1.5)'; break; - case 'deep': base = 'contrast(1.5) brightness(0.8) saturate(0.5) blur(0.5px)'; break; - default: base = 'none'; - } - - if (isSharpnessMax) { - base += ' contrast(1.4) brightness(1.1) saturate(1.2) drop-shadow(0 0 1px white)'; - } - - return base; - }; - - // Recording Logic const startRecording = () => { if (!videoRef.current || !videoRef.current.srcObject) return; const stream = videoRef.current.srcObject as MediaStream; const options = { mimeType: 'video/webm;codecs=vp8,opus' }; - if (!MediaRecorder.isTypeSupported(options.mimeType)) options.mimeType = 'video/webm'; - const recorder = new MediaRecorder(stream, options); + const recorder = new MediaRecorder(stream, MediaRecorder.isTypeSupported(options.mimeType) ? options : { mimeType: 'video/webm' }); const chunks: Blob[] = []; - recorder.ondataavailable = (event) => { if (event.data.size > 0) chunks.push(event.data); }; - recorder.onstop = () => { - const blob = new Blob(chunks, { type: 'video/webm' }); - const url = URL.createObjectURL(blob); - setVideoUrl(url); - }; + recorder.ondataavailable = (e) => chunks.push(e.data); + recorder.onstop = () => setVideoUrl(URL.createObjectURL(new Blob(chunks, { type: 'video/webm' }))); recorder.start(); setIsRecording(true); setMediaRecorder(recorder); - setVideoUrl(null); - }; - - const stopRecording = () => { - if (mediaRecorder && mediaRecorder.state !== 'inactive') { - mediaRecorder.stop(); - setIsRecording(false); - } }; + const stopRecording = () => { mediaRecorder?.stop(); setIsRecording(false); }; + const downloadVideo = () => { if (videoUrl) { const a = document.createElement('a'); - a.style.display = 'none'; a.href = videoUrl; - a.download = `JWST-CELEB-REC-${new Date().getTime()}.webm`; - document.body.appendChild(a); + a.download = `JWST-INTERACTIVE-REC-${Date.now()}.webm`; a.click(); - window.URL.revokeObjectURL(videoUrl); - setVideoUrl(null); } }; const formatZoom = (z: number) => { - if (z >= 1000000000000) return `${(z / 1000000000000).toFixed(1)}T`; - if (z >= 1000000000) return `${(z / 1000000000).toFixed(1)}B`; - if (z >= 1000000) return `${(z / 1000000).toFixed(1)}M`; - if (z >= 1000) return `${(z / 1000).toFixed(1)}K`; + if (z >= 1e12) return `${(z / 1e12).toFixed(1)}T`; + if (z >= 1e9) return `${(z / 1e9).toFixed(1)}B`; + if (z >= 1e6) return `${(z / 1e6).toFixed(1)}M`; return `${z.toFixed(0)}x`; }; - const isDeepZoom = zoom > 5000; - return (
- - JWST | Global Observation Live - + JWST | Interactive Global Live - {/* Main Viewport */} + {/* Main Viewport */}
{!isCameraActive && (
-

Systems Offline

- +

Telescope Standby

+
)} - {/* Live Camera Feed */} -