/** * Bot Edit Page * * Edit existing bot configuration using the setup wizard. * * Requirements: 1.3, 4.6 */ 'use client'; import { useState, useEffect } from 'react'; import { useRouter, useParams } from 'next/navigation'; import Link from 'next/link'; import { ArrowLeftIcon } from '@/components/Icons'; import { UserStorageImageUpload } from '@/components/UserStorageImageUpload'; import { Bot, Sparkles, Rss, Clock, Trash2 } from 'lucide-react'; interface ContentSource { id?: string; type: 'rss' | 'reddit' | 'news_api' | 'brave_news' | 'youtube'; url: string; subreddit?: string; apiKey?: string; fetchIntervalMinutes?: number; isActive?: boolean; // Brave News config braveQuery?: string; braveFreshness?: 'pd' | 'pw' | 'pm' | 'py'; braveCountry?: string; // News API config newsProvider?: 'newsapi' | 'gnews' | 'newsdata'; newsQuery?: string; newsCategory?: string; newsCountry?: string; newsLanguage?: string; // YouTube config youtubeChannelId?: string; youtubePlaylistId?: string; } export default function EditBotPage() { const router = useRouter(); const params = useParams(); const botId = params.id as string; const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const [step, setStep] = useState<'identity' | 'personality' | 'sources' | 'schedule'>('identity'); const [formData, setFormData] = useState({ name: '', handle: '', bio: '', avatarUrl: '', headerUrl: '', systemPrompt: '', llmProvider: 'openai', llmModel: 'gpt-4', llmApiKey: '', autonomousMode: false, postingFrequency: 'every_4_hours', customIntervalMinutes: 240, }); const [sources, setSources] = useState([]); const [newSource, setNewSource] = useState({ type: 'rss', url: '', fetchIntervalMinutes: 30, braveQuery: '', braveFreshness: 'pw', newsProvider: 'newsapi', newsQuery: '', }); const [sourcesToDelete, setSourcesToDelete] = useState([]); useEffect(() => { fetchBot(); }, [botId]); const fetchBot = async () => { try { const [botRes, sourcesRes] = await Promise.all([ fetch(`/api/bots/${botId}`), fetch(`/api/bots/${botId}/sources`), ]); if (!botRes.ok) { setError('Bot not found'); setLoading(false); return; } const botData = await botRes.json(); const bot = botData.bot; const personalityConfig = typeof bot.personalityConfig === 'string' ? JSON.parse(bot.personalityConfig) : bot.personalityConfig || {}; const scheduleConfig = typeof bot.scheduleConfig === 'string' ? JSON.parse(bot.scheduleConfig) : bot.scheduleConfig || {}; // Determine posting frequency from interval let postingFrequency = 'every_4_hours'; let customIntervalMinutes = 240; if (scheduleConfig?.intervalMinutes) { const interval = scheduleConfig.intervalMinutes; if (interval === 120) postingFrequency = 'every_2_hours'; else if (interval === 240) postingFrequency = 'every_4_hours'; else if (interval === 360) postingFrequency = 'every_6_hours'; else if (interval === 1440) postingFrequency = 'daily'; else { // If it was hourly (60) or custom, default to every_4_hours (240) postingFrequency = 'every_4_hours'; customIntervalMinutes = 240; } } setFormData({ name: bot.name || '', handle: bot.handle || '', bio: bot.bio || '', avatarUrl: bot.avatarUrl || '', headerUrl: bot.headerUrl || '', systemPrompt: personalityConfig.systemPrompt || '', llmProvider: bot.llmProvider || 'openai', llmModel: bot.llmModel || 'gpt-4', llmApiKey: '', // Don't pre-fill API key for security autonomousMode: bot.autonomousMode || false, postingFrequency, customIntervalMinutes, }); if (sourcesRes.ok) { const sourcesData = await sourcesRes.json(); setSources(sourcesData.sources || []); } } catch (err) { console.error('Failed to fetch bot:', err); setError('Failed to load bot data'); } finally { setLoading(false); } }; const handleAddSource = () => { // Validate based on type if (newSource.type === 'brave_news') { if (!newSource.braveQuery || !newSource.apiKey) return; // Build URL from config const url = new URL('https://api.search.brave.com/res/v1/news/search'); url.searchParams.set('q', newSource.braveQuery); if (newSource.braveFreshness) url.searchParams.set('freshness', newSource.braveFreshness); if (newSource.braveCountry) url.searchParams.set('country', newSource.braveCountry); setSources([...sources, { ...newSource, url: url.toString() }]); } else if (newSource.type === 'news_api') { if (!newSource.newsQuery || !newSource.apiKey) return; // Build URL from config let baseUrl: string; const params = new URLSearchParams(); switch (newSource.newsProvider) { case 'gnews': baseUrl = 'https://gnews.io/api/v4/search'; params.set('q', newSource.newsQuery); if (newSource.newsCountry) params.set('country', newSource.newsCountry); if (newSource.newsLanguage) params.set('lang', newSource.newsLanguage); if (newSource.newsCategory) params.set('topic', newSource.newsCategory); break; case 'newsdata': baseUrl = 'https://newsdata.io/api/1/news'; params.set('q', newSource.newsQuery); if (newSource.newsCountry) params.set('country', newSource.newsCountry); if (newSource.newsLanguage) params.set('language', newSource.newsLanguage); if (newSource.newsCategory) params.set('category', newSource.newsCategory); break; default: baseUrl = 'https://newsapi.org/v2/everything'; params.set('q', newSource.newsQuery); if (newSource.newsLanguage) params.set('language', newSource.newsLanguage); } setSources([...sources, { ...newSource, url: `${baseUrl}?${params.toString()}` }]); } else { if (!newSource.url) return; setSources([...sources, { ...newSource }]); } setNewSource({ type: 'rss', url: '', fetchIntervalMinutes: 30, braveQuery: '', braveFreshness: 'pw', newsProvider: 'newsapi', newsQuery: '', }); }; const handleRemoveSource = (index: number) => { const source = sources[index]; if (source.id) { setSourcesToDelete([...sourcesToDelete, source.id]); } setSources(sources.filter((_, i) => i !== index)); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSaving(true); setError(''); try { // Calculate interval minutes let intervalMinutes = 240; if (formData.postingFrequency === 'every_2_hours') intervalMinutes = 120; else if (formData.postingFrequency === 'every_4_hours') intervalMinutes = 240; else if (formData.postingFrequency === 'every_6_hours') intervalMinutes = 360; else if (formData.postingFrequency === 'daily') intervalMinutes = 1440; // Update bot const updatePayload: Record = { name: formData.name, bio: formData.bio, avatarUrl: formData.avatarUrl || null, headerUrl: formData.headerUrl || null, personality: { systemPrompt: formData.systemPrompt, temperature: 0.7, maxTokens: 500, }, llmProvider: formData.llmProvider, llmModel: formData.llmModel, autonomousMode: formData.autonomousMode, schedule: formData.autonomousMode ? { type: 'interval', intervalMinutes, } : undefined, }; // Only include API key if user entered a new one if (formData.llmApiKey) { updatePayload.llmApiKey = formData.llmApiKey; } const response = await fetch(`/api/bots/${botId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatePayload), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || 'Failed to update bot'); } // Delete removed sources for (const sourceId of sourcesToDelete) { await fetch(`/api/bots/${botId}/sources/${sourceId}`, { method: 'DELETE' }); } // Add new sources (ones without id) for (const source of sources) { if (!source.id) { const sourcePayload: Record = { type: source.type, url: source.url, subreddit: source.subreddit, apiKey: source.apiKey, }; // Add config for brave_news if (source.type === 'brave_news' && source.braveQuery) { sourcePayload.braveNewsConfig = { query: source.braveQuery, freshness: source.braveFreshness, country: source.braveCountry, }; } // Add config for news_api if (source.type === 'news_api' && source.newsQuery) { sourcePayload.newsApiConfig = { provider: source.newsProvider, query: source.newsQuery, category: source.newsCategory, country: source.newsCountry, language: source.newsLanguage, }; } await fetch(`/api/bots/${botId}/sources`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(sourcePayload), }); } } router.push(`/bots/${botId}`); } catch (err) { console.error('Update bot error:', err); setError(err instanceof Error ? err.message : 'Failed to update bot'); } finally { setSaving(false); } }; const renderStep = () => { switch (step) { case 'identity': return (
setFormData({ ...formData, name: e.target.value })} className="input" placeholder="My Awesome Bot" required />
@

Handle cannot be changed after creation