import React, { useEffect, useState, Component, type ErrorInfo } from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import { NoteViewer } from '@/components/NoteViewer'; import { SearchWidget } from '@/components/SearchWidget'; import type { Note } from '@/types/note'; import type { SearchResult } from '@/types/search'; import { getNote } from '@/services/api'; import { Loader2, AlertTriangle } from 'lucide-react'; // Mock window.openai for development if (!window.openai) { console.warn("Mocking window.openai (dev mode)"); window.openai = { toolOutput: null }; } // Extend window interface declare global { interface Window { openai: { toolOutput: any; toolInput?: any; }; } } class WidgetErrorBoundary extends Component<{ children: React.ReactNode }, { hasError: boolean, error: Error | null }> { constructor(props: { children: React.ReactNode }) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error("Widget Error:", error, errorInfo); } render() { if (this.state.hasError) { return (

Something went wrong

{this.state.error?.message || "The widget could not be displayed."}

); } return this.props.children; } } const WidgetApp = () => { const [view, setView] = useState<'loading' | 'note' | 'search' | 'error'>('loading'); const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { // Function to check for data const checkData = () => { const toolOutput = window.openai.toolOutput; if (toolOutput) { console.log("Tool output found:", toolOutput); if (toolOutput.note) { setView('note'); setData(toolOutput.note); } else if (toolOutput.results) { setView('search'); setData(toolOutput.results); } else { console.warn("Unknown tool output format", toolOutput); setError("Unknown content format."); setView('error'); } return true; } return false; }; // Check immediately if (checkData()) return; // If not found, check if we have toolInput (loading state) // Note: window.openai types might vary, we check safely const toolInput = (window.openai as any).toolInput; if (toolInput) { console.log("Tool input present, waiting for output:", toolInput); setView('loading'); } else { // Fallback for dev testing via URL (only if no toolInput either) const params = new URLSearchParams(window.location.search); const mockType = params.get('type'); if (mockType === 'note') { // ... existing mock logic ... setView('note'); setData({ title: "Demo Note", note_path: "demo.md", body: "# Demo Note\n\nMock note.", version: 1, size_bytes: 100, created: new Date().toISOString(), updated: new Date().toISOString(), metadata: {} }); return; } else if (mockType === 'search') { setView('search'); setData([{ title: "Result 1", note_path: "res1.md", snippet: "Match", score: 1, updated: new Date() }]); return; } // If absolutely nothing, show error (or keep loading if we suspect latency) // But sticking to 'loading' is safer than error if we are polling. } // Poll for data injection const interval = setInterval(() => { if (checkData()) clearInterval(interval); }, 500); return () => clearInterval(interval); }, []); const handleWikilinkClick = (linkText: string) => { console.log("Clicked wikilink in widget:", linkText); // Convert wikilink text to path (simple slugification or just try reading it) // We can try to fetch it directly. // Reuse handleNoteSelect logic but we need to slugify first? // For now, let's try strict filename matching or just pass text. // getNote expects a path. // We'll just alert for now or try to resolve it. // Let's try to fetch it as a path (assuming user clicked "Getting Started") handleNoteSelect(linkText + ".md"); }; const handleNoteSelect = async (path: string) => { console.log("Selected note:", path); setView('loading'); try { const note = await getNote(path); setData(note); setView('note'); } catch (err: any) { console.error("Failed to load note:", err); const msg = err?.message || String(err); const status = err?.status ? ` (Status: ${err.status})` : ''; setError(`Failed to load note: ${path}\nError: ${msg}${status}`); setView('error'); } }; return (
{view === 'loading' && (

{window.openai?.toolInput?.query ? `Searching for "${window.openai.toolInput.query}"...` : "Waiting for tool execution..."}

(Please confirm the action in ChatGPT)

)} {view === 'error' && (

Error

{error}

Debug Info (window.openai):

                {JSON.stringify(window.openai, null, 2)}
              
)} {view === 'note' && data && ( )} {view === 'search' && data && ( )}
); }; ReactDOM.createRoot(document.getElementById('root')!).render( );