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' && (
Debug Info (window.openai):
{JSON.stringify(window.openai, null, 2)}
)}
{view === 'note' && data && (
)}
{view === 'search' && data && (
)}
);
};
ReactDOM.createRoot(document.getElementById('root')!).render(
);