import { Component, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; // ─── 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'; }; } } @Component({ selector: 'app-root', standalone: true, imports: [CommonModule, FormsModule], template: `
{{ hasBridge ? 'Photon Connected' : 'Local Mock Mode' }}

📥 Initial Parameters

Values passed by the LLM agent or environment triggers:

{{ inputJson }}

⚙️ Test Backend Tools

Call methods exposed on the server-side Photon class:

Echo Tool

{{ echoResult }}

Add Tool

+
Sum: {{ addResult }}

⚡ Real-Time Events

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

No events received yet. Try running some tools.
{{ e.time }} {{ e.emit }} {{ e.dataJson }}
`, }) export class AppComponent implements OnInit, OnDestroy { hasBridge = typeof window.photon !== 'undefined'; input = window.photon?.toolInput || {}; inputJson = JSON.stringify(this.input, null, 2); theme: 'light' | 'dark' = window.photon?.theme || 'light'; echoText = 'Hello Photon!'; echoResult = ''; numA = 5; numB = 10; addResult: number | null = null; loading: Record = {}; events: Array<{ time: string; emit: string; dataJson: string }> = []; private unsubscribeTheme?: () => void; private unsubscribeEmit?: () => void; ngOnInit() { if (window.photon) { this.unsubscribeTheme = window.photon.onThemeChange((newTheme) => { this.theme = newTheme; }); this.unsubscribeEmit = window.photon.onEmit((event) => { this.events.unshift({ time: new Date().toLocaleTimeString(), emit: event.emit, dataJson: JSON.stringify(event.data), }); if (this.events.length > 10) { this.events.pop(); } }); } } ngOnDestroy() { if (this.unsubscribeTheme) this.unsubscribeTheme(); if (this.unsubscribeEmit) this.unsubscribeEmit(); } async callTool(name: string, args: any = {}) { if (window.photon) { return window.photon.callTool(name, args); } 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) ); } async handleEcho() { this.loading['echo'] = true; try { const res = (await this.callTool('echo', { message: this.echoText })) as string; this.echoResult = res; } catch (err: any) { this.echoResult = `Error: ${err.message || err}`; } finally { this.loading['echo'] = false; } } async handleAdd() { this.loading['add'] = true; try { const res = (await this.callTool('add', { a: this.numA, b: this.numB })) as any; this.addResult = res?.sum ?? null; } catch (err: any) { console.error(err); } finally { this.loading['add'] = false; } } }