Spaces:
Running
Running
| // frontend-fetch-example.js | |
| // Frontend code snippet for Reuben OS to fetch and display session files | |
| // React Component Example for Session Management | |
| import React, { useState, useEffect, useCallback } from 'react'; | |
| // Configuration | |
| const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; | |
| const POLL_INTERVAL = 5000; // Poll every 5 seconds | |
| // ===== SESSION MANAGER COMPONENT ===== | |
| export function SessionManager() { | |
| const [sessionId, setSessionId] = useState(''); | |
| const [files, setFiles] = useState([]); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState(null); | |
| const [quizDetected, setQuizDetected] = useState(false); | |
| // Generate a new session ID on component mount | |
| useEffect(() => { | |
| const generateSessionId = () => { | |
| const timestamp = Date.now(); | |
| const random = Math.random().toString(36).substring(2, 9); | |
| return `session_${timestamp}_${random}`; | |
| }; | |
| // Check if session ID exists in localStorage, otherwise generate new one | |
| const storedSessionId = localStorage.getItem('reubenOSSessionId'); | |
| if (storedSessionId) { | |
| setSessionId(storedSessionId); | |
| } else { | |
| const newSessionId = generateSessionId(); | |
| setSessionId(newSessionId); | |
| localStorage.setItem('reubenOSSessionId', newSessionId); | |
| } | |
| }, []); | |
| // Fetch files for the current session | |
| const fetchFiles = useCallback(async () => { | |
| if (!sessionId) return; | |
| setLoading(true); | |
| setError(null); | |
| try { | |
| const response = await fetch(`${API_URL}/api/mcp-handler?sessionId=${sessionId}`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| if (data.success) { | |
| setFiles(data.files || []); | |
| // Check if quiz.json exists | |
| const hasQuiz = data.files.some(file => file.name === 'quiz.json'); | |
| setQuizDetected(hasQuiz); | |
| } else { | |
| setError(data.error || 'Failed to fetch files'); | |
| } | |
| } catch (err) { | |
| console.error('Error fetching files:', err); | |
| setError(err.message); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }, [sessionId]); | |
| // Set up polling | |
| useEffect(() => { | |
| if (!sessionId) return; | |
| // Initial fetch | |
| fetchFiles(); | |
| // Set up polling interval | |
| const interval = setInterval(fetchFiles, POLL_INTERVAL); | |
| return () => clearInterval(interval); | |
| }, [sessionId, fetchFiles]); | |
| // Handle manual refresh | |
| const handleRefresh = () => { | |
| fetchFiles(); | |
| }; | |
| // Copy session ID to clipboard | |
| const copySessionId = () => { | |
| navigator.clipboard.writeText(sessionId); | |
| alert('Session ID copied to clipboard!'); | |
| }; | |
| return ( | |
| <div className="session-manager"> | |
| <div className="session-header"> | |
| <h2>Session Manager</h2> | |
| <div className="session-info"> | |
| <span>Session ID: {sessionId}</span> | |
| <button onClick={copySessionId}>Copy ID</button> | |
| </div> | |
| <button onClick={handleRefresh} disabled={loading}> | |
| {loading ? 'Loading...' : 'Refresh'} | |
| </button> | |
| </div> | |
| {error && ( | |
| <div className="error-message"> | |
| Error: {error} | |
| </div> | |
| )} | |
| {quizDetected && ( | |
| <div className="quiz-alert"> | |
| 🎯 Quiz Detected! Click to launch Quiz App | |
| <button onClick={() => launchQuizApp(sessionId)}> | |
| Launch Quiz | |
| </button> | |
| </div> | |
| )} | |
| <div className="files-list"> | |
| <h3>Files ({files.length})</h3> | |
| {files.length === 0 ? ( | |
| <p>No files yet. Use Claude to save files to this session.</p> | |
| ) : ( | |
| <ul> | |
| {files.map((file, index) => ( | |
| <FileItem key={index} file={file} sessionId={sessionId} /> | |
| ))} | |
| </ul> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // ===== FILE ITEM COMPONENT ===== | |
| function FileItem({ file, sessionId }) { | |
| const [showContent, setShowContent] = useState(false); | |
| const handleDownload = () => { | |
| // Create a download link | |
| const blob = new Blob([file.content || ''], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = file.name; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| }; | |
| const getFileIcon = (fileName) => { | |
| if (fileName.endsWith('.dart')) return '🎯'; | |
| if (fileName.endsWith('.tex')) return '📜'; | |
| if (fileName.endsWith('.json')) return '📋'; | |
| if (fileName.endsWith('.js') || fileName.endsWith('.ts')) return '📝'; | |
| if (fileName.endsWith('.py')) return '🐍'; | |
| return '📄'; | |
| }; | |
| return ( | |
| <li className="file-item"> | |
| <div className="file-header"> | |
| <span className="file-icon">{getFileIcon(file.name)}</span> | |
| <span className="file-name">{file.name}</span> | |
| <span className="file-size">({(file.size / 1024).toFixed(2)} KB)</span> | |
| <button onClick={() => setShowContent(!showContent)}> | |
| {showContent ? 'Hide' : 'Show'} | |
| </button> | |
| <button onClick={handleDownload}>Download</button> | |
| </div> | |
| {showContent && file.content && ( | |
| <div className="file-content"> | |
| <pre>{file.content.substring(0, 500)}</pre> | |
| {file.content.length > 500 && <p>... (truncated)</p>} | |
| </div> | |
| )} | |
| </li> | |
| ); | |
| } | |
| // ===== QUIZ APP LAUNCHER ===== | |
| function launchQuizApp(sessionId) { | |
| // This function would navigate to your Quiz app with the session ID | |
| window.location.href = `/apps/quiz?sessionId=${sessionId}`; | |
| } | |
| // ===== UTILITY FUNCTIONS ===== | |
| // Function to save a file from the frontend (for testing) | |
| export async function saveFile(sessionId, fileName, content) { | |
| try { | |
| const response = await fetch(`${API_URL}/api/mcp-handler`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| sessionId, | |
| action: 'save_file', | |
| fileName, | |
| content, | |
| }), | |
| }); | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Error saving file:', error); | |
| throw error; | |
| } | |
| } | |
| // Function to clear all files for a session | |
| export async function clearSession(sessionId) { | |
| try { | |
| const response = await fetch(`${API_URL}/api/mcp-handler`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| sessionId, | |
| action: 'clear_session', | |
| fileName: '', | |
| content: '', | |
| }), | |
| }); | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Error clearing session:', error); | |
| throw error; | |
| } | |
| } | |
| // ===== FLUTTER APP COMPONENT ===== | |
| export function FlutterAppViewer({ sessionId }) { | |
| const [dartFiles, setDartFiles] = useState([]); | |
| useEffect(() => { | |
| const fetchDartFiles = async () => { | |
| try { | |
| const response = await fetch(`${API_URL}/api/mcp-handler?sessionId=${sessionId}`); | |
| const data = await response.json(); | |
| if (data.success) { | |
| // Filter only .dart files | |
| const dartOnly = data.files.filter(f => f.name.endsWith('.dart')); | |
| setDartFiles(dartOnly); | |
| } | |
| } catch (error) { | |
| console.error('Error fetching Dart files:', error); | |
| } | |
| }; | |
| if (sessionId) { | |
| fetchDartFiles(); | |
| const interval = setInterval(fetchDartFiles, POLL_INTERVAL); | |
| return () => clearInterval(interval); | |
| } | |
| }, [sessionId]); | |
| const openInZapp = (file) => { | |
| // Open Dart code in Zapp runner | |
| const zappUrl = 'https://zapp.run/'; | |
| const code = encodeURIComponent(file.content); | |
| window.open(`${zappUrl}?code=${code}`, '_blank'); | |
| }; | |
| return ( | |
| <div className="flutter-viewer"> | |
| <h3>Flutter/Dart Files</h3> | |
| {dartFiles.map((file, index) => ( | |
| <div key={index} className="dart-file"> | |
| <span>{file.name}</span> | |
| <button onClick={() => openInZapp(file)}> | |
| Open in Zapp | |
| </button> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| } | |
| // ===== LATEX VIEWER COMPONENT ===== | |
| export function LaTeXViewer({ sessionId }) { | |
| const [texFiles, setTexFiles] = useState([]); | |
| useEffect(() => { | |
| const fetchTexFiles = async () => { | |
| try { | |
| const response = await fetch(`${API_URL}/api/mcp-handler?sessionId=${sessionId}`); | |
| const data = await response.json(); | |
| if (data.success) { | |
| // Filter only .tex files | |
| const texOnly = data.files.filter(f => f.name.endsWith('.tex')); | |
| setTexFiles(texOnly); | |
| } | |
| } catch (error) { | |
| console.error('Error fetching LaTeX files:', error); | |
| } | |
| }; | |
| if (sessionId) { | |
| fetchTexFiles(); | |
| const interval = setInterval(fetchTexFiles, POLL_INTERVAL); | |
| return () => clearInterval(interval); | |
| } | |
| }, [sessionId]); | |
| return ( | |
| <div className="latex-viewer"> | |
| <h3>LaTeX Documents</h3> | |
| {texFiles.map((file, index) => ( | |
| <div key={index} className="tex-file"> | |
| <span>{file.name}</span> | |
| <button onClick={() => openInLatexEditor(file)}> | |
| Open in Editor | |
| </button> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| } | |
| function openInLatexEditor(file) { | |
| // Your LaTeX editor integration | |
| window.location.href = `/apps/latex-studio?file=${file.name}`; | |
| } | |
| // ===== EXAMPLE USAGE IN NEXT.JS PAGE ===== | |
| /* | |
| // pages/index.js or pages/dashboard.js | |
| import { SessionManager } from '../components/SessionManager'; | |
| export default function Dashboard() { | |
| return ( | |
| <div className="dashboard"> | |
| <h1>Reuben OS - File Manager</h1> | |
| <SessionManager /> | |
| </div> | |
| ); | |
| } | |
| */ | |
| // ===== CSS STYLES (add to your global styles or styled-components) ===== | |
| const styles = ` | |
| .session-manager { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .session-header { | |
| background: #f5f5f5; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| .session-info { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin: 10px 0; | |
| font-family: monospace; | |
| background: white; | |
| padding: 10px; | |
| border-radius: 4px; | |
| } | |
| .error-message { | |
| background: #fee; | |
| color: #c00; | |
| padding: 10px; | |
| border-radius: 4px; | |
| margin: 10px 0; | |
| } | |
| .quiz-alert { | |
| background: #efe; | |
| color: #060; | |
| padding: 15px; | |
| border-radius: 4px; | |
| margin: 10px 0; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .files-list { | |
| background: white; | |
| padding: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .file-item { | |
| list-style: none; | |
| padding: 10px; | |
| border-bottom: 1px solid #eee; | |
| } | |
| .file-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .file-icon { | |
| font-size: 20px; | |
| } | |
| .file-name { | |
| flex: 1; | |
| font-weight: 500; | |
| } | |
| .file-size { | |
| color: #666; | |
| font-size: 0.9em; | |
| } | |
| .file-content { | |
| margin-top: 10px; | |
| padding: 10px; | |
| background: #f5f5f5; | |
| border-radius: 4px; | |
| overflow-x: auto; | |
| } | |
| .file-content pre { | |
| margin: 0; | |
| font-size: 0.9em; | |
| line-height: 1.4; | |
| } | |
| button { | |
| padding: 6px 12px; | |
| background: #007bff; | |
| color: white; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| } | |
| button:hover { | |
| background: #0056b3; | |
| } | |
| button:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| `; |