'use client'; import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild, Switch, } from '@headlessui/react'; import { X, Plus, Trash2, Play, Save, Brain } from 'lucide-react'; import { Fragment, useState, useEffect } from 'react'; import MarkdownRenderer from '@/components/MarkdownRenderer'; import ModelSelector from '@/components/MessageInputActions/ModelSelector'; import ToolSelector from '@/components/MessageInputActions/ToolSelector'; import { WidgetConfig, Source } from '@/lib/types/widget'; // Helper function to replace date/time variables in prompts on the client side const replaceDateTimeVariables = (prompt: string): string => { let processedPrompt = prompt; // Replace UTC datetime if (processedPrompt.includes('{{current_utc_datetime}}')) { const utcDateTime = new Date().toISOString(); processedPrompt = processedPrompt.replace( /\{\{current_utc_datetime\}\}/g, utcDateTime, ); } // Replace local datetime if (processedPrompt.includes('{{current_local_datetime}}')) { const now = new Date(); const localDateTime = new Date( now.getTime() - now.getTimezoneOffset() * 60000, ).toISOString(); processedPrompt = processedPrompt.replace( /\{\{current_local_datetime\}\}/g, localDateTime, ); } return processedPrompt; }; interface WidgetConfigModalProps { isOpen: boolean; onClose: () => void; onSave: (config: WidgetConfig) => void; editingWidget?: WidgetConfig | null; } const WidgetConfigModal = ({ isOpen, onClose, onSave, editingWidget, }: WidgetConfigModalProps) => { const [config, setConfig] = useState({ title: '', sources: [{ url: '', type: 'Web Page' }], prompt: '', provider: 'openai', model: 'gpt-4', refreshFrequency: 60, refreshUnit: 'minutes', }); const [previewContent, setPreviewContent] = useState(''); const [isPreviewLoading, setIsPreviewLoading] = useState(false); const [selectedModel, setSelectedModel] = useState<{ provider: string; model: string; } | null>(null); const [selectedTools, setSelectedTools] = useState([]); const [showThinking, setShowThinking] = useState(false); // Update config when editingWidget changes useEffect(() => { if (editingWidget) { setConfig({ title: editingWidget.title, sources: editingWidget.sources, prompt: editingWidget.prompt, provider: editingWidget.provider, model: editingWidget.model, refreshFrequency: editingWidget.refreshFrequency, refreshUnit: editingWidget.refreshUnit, }); setSelectedModel({ provider: editingWidget.provider, model: editingWidget.model, }); setSelectedTools(editingWidget.tool_names || []); } else { // Reset to default values for new widget setConfig({ title: '', sources: [{ url: '', type: 'Web Page' }], prompt: '', provider: 'openai', model: 'gpt-4', refreshFrequency: 60, refreshUnit: 'minutes', }); setSelectedModel({ provider: 'openai', model: 'gpt-4', }); setSelectedTools([]); } }, [editingWidget]); // Update config when model selection changes useEffect(() => { if (selectedModel) { setConfig((prev) => ({ ...prev, provider: selectedModel.provider, model: selectedModel.model, })); } }, [selectedModel]); const handleSave = () => { if (!config.title.trim() || !config.prompt.trim()) { return; // TODO: Add proper validation feedback } // Filter out sources with empty or whitespace-only URLs const filteredConfig = { ...config, sources: config.sources.filter((s) => s.url.trim()), tool_names: selectedTools, }; onSave(filteredConfig); handleClose(); }; const handleClose = () => { setPreviewContent(''); // Clear preview content when closing onClose(); }; const handlePreview = async () => { if (!config.prompt.trim()) { setPreviewContent('Please enter a prompt before running preview.'); return; } setIsPreviewLoading(true); try { // Replace date/time variables on the client side const processedPrompt = replaceDateTimeVariables(config.prompt); const response = await fetch('/api/dashboard/process-widget', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ sources: config.sources.filter((s) => s.url.trim()), // Only send sources with URLs prompt: processedPrompt, provider: config.provider, model: config.model, tool_names: selectedTools, }), }); const result = await response.json(); if (result.success) { setPreviewContent(result.content); } else { setPreviewContent( `**Preview Error:** ${result.error || 'Unknown error occurred'}\n\n${result.content || ''}`, ); } } catch (error) { console.error('Preview error:', error); setPreviewContent( `**Network Error:** Failed to connect to the preview service.\n\n${error instanceof Error ? error.message : 'Unknown error'}`, ); } finally { setIsPreviewLoading(false); } }; const addSource = () => { setConfig((prev) => ({ ...prev, sources: [...prev.sources, { url: '', type: 'Web Page' }], })); }; const removeSource = (index: number) => { setConfig((prev) => ({ ...prev, sources: prev.sources.filter((_, i) => i !== index), })); }; const updateSource = (index: number, field: keyof Source, value: string) => { setConfig((prev) => ({ ...prev, sources: prev.sources.map((source, i) => i === index ? { ...source, [field]: value } : source, ), })); }; return (
{editingWidget ? 'Edit Widget' : 'Create New Widget'}
{/* Left Column - Configuration */}
{/* Widget Title */}
setConfig((prev) => ({ ...prev, title: e.target.value, })) } className="w-full px-3 py-2 border border-light-200 dark:border-dark-200 rounded-md bg-light-primary dark:bg-dark-primary text-black dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter widget title..." />
{/* Source URLs */}
{config.sources.map((source, index) => (
updateSource(index, 'url', e.target.value) } className="flex-1 px-3 py-2 border border-light-200 dark:border-dark-200 rounded-md bg-light-primary dark:bg-dark-primary text-black dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="https://example.com" /> {config.sources.length > 1 && ( )}
))}
{/* LLM Prompt */}