import { useEffect, useState } from 'react'; // ─── Bridge Declarations ────────────────────────────────────────────────── declare global { interface Window { photon?: { toolInput: Record; widgetState: any; setWidgetState: (state: any) => void; callTool: (name: string, args: any) => Promise; onProgress: (cb: (e: any) => void) => () => void; onEmit: (cb: (e: { emit: string; data?: any }) => void) => () => void; onResult: (cb: (r: any) => void) => () => void; onError: (cb: (err: any) => void) => () => void; onThemeChange: (cb: (theme: 'light' | 'dark') => void) => () => void; theme: 'light' | 'dark'; }; [photonName: string]: any; } } // ─── React Integration Hooks ────────────────────────────────────────────── export function usePhoton() { const [hasBridge] = useState(() => typeof window.photon !== 'undefined'); const [input] = useState(() => window.photon?.toolInput || {}); const [state, setState] = useState(() => window.photon?.widgetState || {}); const [theme, setTheme] = useState<'light' | 'dark'>(() => window.photon?.theme || 'light'); useEffect(() => { if (!window.photon) return; return window.photon.onThemeChange((newTheme) => { setTheme(newTheme); }); }, []); const updateState = (newState: any) => { setState(newState); window.photon?.setWidgetState(newState); }; const callTool = async (name: string, args: any = {}) => { if (window.photon) { return window.photon.callTool(name, args); } // Mock local fallback for independent browser testing console.warn(`[Photon Mock] calling ${name} with`, args); return new Promise((resolve) => setTimeout(() => { if (name === 'add') resolve({ a: args.a, b: args.b, sum: args.a + args.b }); else if (name === 'echo') resolve(`Echo: ${args.message}`); else resolve({ mockResult: true }); }, 500)); }; return { hasBridge, input, state, updateState, theme, callTool }; } export function usePhotonEmit(callback: (event: { emit: string; data?: any }) => void) { useEffect(() => { if (!window.photon) return; return window.photon.onEmit(callback); }, [callback]); } // ─── Main Component ─────────────────────────────────────────────────────── export default function App() { const { hasBridge, input, theme, callTool } = usePhoton(); const [echoText, setEchoText] = useState('Hello Photon!'); const [echoResult, setEchoResult] = useState(''); const [numA, setNumA] = useState(5); const [numB, setNumB] = useState(10); const [addResult, setAddResult] = useState(null); const [loading, setLoading] = useState>({}); const [events, setEvents] = useState>([]); // Subscribe to real-time events usePhotonEmit((event) => { setEvents((prev) => [ { time: new Date().toLocaleTimeString(), emit: event.emit, data: event.data, }, ...prev.slice(0, 9), // Keep last 10 events ]); }); const handleEcho = async () => { setLoading((l) => ({ ...l, echo: true })); try { const res = await callTool('echo', { message: echoText }); setEchoResult(res); } catch (err: any) { setEchoResult(`Error: ${err.message || err}`); } finally { setLoading((l) => ({ ...l, echo: false })); } }; const handleAdd = async () => { setLoading((l) => ({ ...l, add: true })); try { const res = await callTool('add', { a: numA, b: numB }); setAddResult(res?.sum ?? null); } catch (err: any) { console.error(err); } finally { setLoading((l) => ({ ...l, add: false })); } }; return (
⚛️

Photon React Dashboard

{hasBridge ? 'Photon Connected' : 'Local Mock Mode'}
{/* Card 1: Parameters Received */}

📥 Initial Parameters

Values passed by the LLM agent or environment triggers:

              {JSON.stringify(input, null, 2)}
            
{/* Card 2: Interactive Tools */}

⚙️ Test Backend Tools

Call methods exposed on the server-side Photon class:

Echo Tool

setEchoText(e.target.value)} placeholder="Enter message..." />
{echoResult &&
{echoResult}
}

Add Tool

setNumA(Number(e.target.value))} style={{ width: '80px' }} /> + setNumB(Number(e.target.value))} style={{ width: '80px' }} />
{addResult !== null && (
Sum: {addResult}
)}
{/* Card 3: Real-time Event Feed */}

⚡ Real-Time Events

Live messages pushed from the backend via the event pub/sub pipeline:

{events.length === 0 ? (
No events received yet. Try running some tools.
) : ( events.map((e, index) => (
{e.time} {e.emit} {JSON.stringify(e.data)}
)) )}
); }