import { useState, useEffect } from 'react'; import { Bot, PlusSquare, Settings, Database, Plug, FileText, Plus, Monitor, ArrowUp, X, Play, SkipForward, Save, Trash2, Eye, EyeOff, ChevronDown, Search } from 'lucide-react'; // ─── Storage ────────────────────────────────────────────────────────────────── const STORAGE_KEY = 'manus_config_v2'; const defaultConfig = { providers: { OpenAI: [], Anthropic: [], Groq: [], HuggingFace: [], OpenRouter: [], Nvidia: [], Mistral: [], Cohere: [] }, activeProvider: 'Groq', activeKey: '', selectedModel: 'llama-3.1-8b-instant', ragConfig: { vectorDbUrl: '', embeddingModel: 'text-embedding-3-small (OpenAI)', systemPrompt: '' }, mcpServers: [''], }; function loadConfig() { try { const s = localStorage.getItem(STORAGE_KEY); return s ? { ...defaultConfig, ...JSON.parse(s) } : defaultConfig; } catch { return defaultConfig; } } function saveConfig(cfg) { localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg)); } // ─── Full Model Catalog ─────────────────────────────────────────────────────── const MODELS = { OpenAI: [ 'gpt-4o', 'gpt-4o-2024-11-20', 'gpt-4o-2024-08-06', 'gpt-4o-2024-05-13', 'gpt-4o-mini', 'gpt-4o-mini-2024-07-18', 'o1', 'o1-2024-12-17', 'o1-mini', 'o1-mini-2024-09-12', 'o3-mini', 'o3-mini-2025-01-31', 'gpt-4-turbo', 'gpt-4-turbo-2024-04-09', 'gpt-4-turbo-preview', 'gpt-4-0125-preview', 'gpt-4-1106-preview', 'gpt-4', 'gpt-4-0613', 'gpt-4-0314', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0125', 'gpt-3.5-turbo-1106' ], Anthropic: [ 'claude-3-7-sonnet-20250219', 'claude-3-5-sonnet-20241022', 'claude-3-5-sonnet-20240620', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307' ], Groq: [ 'llama-3.1-8b-instant', 'llama-3.3-70b-versatile', 'llama3-8b-8192', 'llama3-70b-8192', 'mixtral-8x7b-32768', 'qwen/qwen3-32b', 'qwen/qwen3.6-27b', 'groq/compound', 'groq/compound-mini', 'deepseek-r1-distill-llama-70b', 'meta-llama/llama-4-scout-17b-16e-instruct' ], HuggingFace: [ 'meta-llama/Llama-3.3-70B-Instruct', 'meta-llama/Llama-3.1-8B-Instruct', 'meta-llama/Llama-3.1-70B-Instruct', 'meta-llama/Meta-Llama-3-8B-Instruct', 'mistralai/Mixtral-8x7B-Instruct-v0.1', 'mistralai/Mistral-7B-Instruct-v0.3', 'google/gemma-2-9b-it', 'google/gemma-2-27b-it', 'Qwen/Qwen2.5-72B-Instruct', 'Qwen/Qwen2.5-Coder-32B-Instruct', 'microsoft/Phi-3.5-mini-instruct', 'microsoft/Phi-3-medium-128k-instruct' ], OpenRouter: [ 'openai/gpt-4o', 'openai/gpt-4o-mini', 'openai/o1', 'openai/o1-mini', 'openai/o3-mini', 'anthropic/claude-3.7-sonnet', 'anthropic/claude-3.5-sonnet', 'anthropic/claude-3.5-haiku', 'anthropic/claude-3-opus', 'google/gemini-2.0-pro-exp-02-05', 'google/gemini-2.0-flash-exp', 'google/gemini-1.5-pro', 'google/gemini-1.5-flash', 'deepseek/deepseek-chat', 'deepseek/deepseek-coder', 'deepseek/deepseek-r1', 'meta-llama/llama-3.3-70b-instruct', 'meta-llama/llama-3.1-405b-instruct', 'x-ai/grok-2-1212', 'x-ai/grok-2-vision-1212', 'mistralai/mistral-large', 'mistralai/mixtral-8x22b-instruct', 'qwen/qwq-32b' ], Nvidia: [ 'minimaxai/minimax-m3', 'moonshotai/kimi-k2.6', 'deepseek-ai/deepseek-v4-pro', 'z-ai/glm-5.1', 'minimaxai/minimax-m1-40k', 'moonshotai/kimi-k2', 'deepseek-ai/deepseek-r1-0528', 'z-ai/glm-4.5', 'nvidia/llama-3.1-nemotron-70b-instruct', 'nvidia/nemotron-4-340b-instruct', 'meta/llama-3.1-405b-instruct', 'meta/llama-3.1-70b-instruct', 'mistralai/mistral-large-2-instruct', 'mistralai/mixtral-8x22b-instruct', 'google/gemma-2-27b-it', 'microsoft/phi-3-medium-128k-instruct' ], Mistral: [ 'mistral-large-latest', 'mistral-large-2411', 'mistral-large-2407', 'pixtral-large-latest', 'pixtral-large-2411', 'ministral-3b-latest', 'ministral-8b-latest', 'mistral-small-latest', 'mistral-small-2409', 'codestral-latest', 'codestral-2501', 'open-mistral-nemo', 'open-mixtral-8x22b' ], Cohere: [ 'command-r-plus-08-2024', 'command-r-08-2024', 'command-r-plus', 'command-r', 'command', 'command-light' ], }; const EMBEDDING_MODELS = [ 'text-embedding-3-small (OpenAI)', 'text-embedding-3-large (OpenAI)', 'nomic-embed-text (Nomic)', 'bge-m3 (BAAI)', 'e5-mistral-7b-instruct', ]; // ─── Key Row ───────────────────────────────────────────────────────────────── function KeyRow({ value, isActive, onToggleActive, onChange, onDelete, index }) { const [show, setShow] = useState(!value); // auto-show when empty/new const masked = value.length > 12 ? value.slice(0, 6) + '••••••••••••' + value.slice(-4) : '••••'; return (
{/* Active dot */} {/* Delete */}
); } // ─── Model Picker ──────────────────────────────────────────────────────────── function ModelPicker({ provider, value, onChange }) { const [query, setQuery] = useState(''); const allModels = MODELS[provider] || []; const filtered = query ? allModels.filter(m => m.toLowerCase().includes(query.toLowerCase())) : allModels; return (
setQuery(e.target.value)} placeholder={`Search ${allModels.length} models...`} className="bg-transparent text-white text-xs outline-none flex-grow placeholder-gray-600" /> {query && }
{filtered.length === 0 &&

No models match "{query}"

} {filtered.map(m => ( ))}
{value &&
Selected: {value}
}
); } // ─── Main App ──────────────────────────────────────────────────────────────── const App = () => { const [config, setConfig] = useState(loadConfig); const [showComputer, setShowComputer] = useState(true); const [messages, setMessages] = useState([]); const [inputText, setInputText] = useState(''); const [loading, setLoading] = useState(false); const [activeModal, setActiveModal] = useState(null); const [draft, setDraft] = useState(null); const [providerTab, setProviderTab] = useState('Groq'); const openModal = (name) => { setDraft(JSON.parse(JSON.stringify(config))); setProviderTab(config.activeProvider || 'Groq'); setActiveModal(name); }; const commitSave = () => { setConfig(draft); saveConfig(draft); setActiveModal(null); }; // Switch tab and update selected model if current one isn't in new provider const switchTab = (tab) => { setProviderTab(tab); setDraft(d => { const tabModels = MODELS[tab] || []; const modelValid = tabModels.includes(d.selectedModel); return modelValid ? d : { ...d, selectedModel: tabModels[0] || d.selectedModel }; }); }; const handleSend = async () => { if (!inputText.trim() || loading) return; setMessages(prev => [...prev, { role: 'user', text: inputText }]); const prompt = inputText; setInputText(''); setLoading(true); try { const res = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, model: config.selectedModel, api_key: config.activeKey, provider: config.activeProvider }), }); const data = await res.json(); if (!res.ok) { throw new Error(data.detail || `HTTP ${res.status}`); } setMessages(prev => [...prev, { role: 'agent', text: data.response, doc: true }]); } catch (e) { setMessages(prev => [...prev, { role: 'agent', text: `Error: ${e.message}` }]); } finally { setLoading(false); } }; // ── Providers Modal ─────────────────────────────────────────────────────────── const renderProviders = () => { if (!draft || activeModal !== 'providers') return null; const keys = draft.providers[providerTab] || []; return (
{/* Header */}

LLM Providers

{/* Provider tabs */}
{Object.keys(MODELS).map(p => { const count = (draft.providers[p] || []).filter(k => k.trim()).length; return ( ); })}
{/* Scrollable body */}
{/* API Keys section */}

Add multiple keys — click ● to set active

{keys.length === 0 ?
No keys yet — click Add Key above
: keys.map((k, i) => ( { if (k) setDraft(d => ({ ...d, activeProvider: providerTab, activeKey: k })); }} onChange={val => setDraft(d => { const nk = [...(d.providers[providerTab] || [])]; nk[i] = val; return { ...d, providers: { ...d.providers, [providerTab]: nk } }; })} onDelete={() => setDraft(d => { const nk = (d.providers[providerTab] || []).filter((_, idx) => idx !== i); return { ...d, providers: { ...d.providers, [providerTab]: nk }, activeKey: d.activeKey === k ? '' : d.activeKey }; })} /> )) }
{/* Model picker */}
setDraft(d => { // Auto-switch activeProvider to the tab where model was selected const hasKeyForTab = (d.providers[providerTab] || []).some(k => k.trim()); return { ...d, selectedModel: m, activeProvider: hasKeyForTab ? providerTab : d.activeProvider, // If switching provider, clear active key so user picks one activeKey: hasKeyForTab ? (d.activeProvider === providerTab ? d.activeKey : (d.providers[providerTab] || [])[0] || '') : d.activeKey, }; })} />
{/* Active config summary */} {draft.activeKey ? (
{draft.activeProvider} · {draft.selectedModel} · key ends in …{draft.activeKey.slice(-4)}
) : (
No active key — add a key and click the ● dot to activate it
)}
{/* Footer */}
); }; // ── RAG Modal ──────────────────────────────────────────────────────────────── const renderRag = () => { if (!draft || activeModal !== 'rag') return null; return (

RAG Pipeline

{[ { label: 'Vector Database URL', key: 'vectorDbUrl', placeholder: 'https://your-pinecone-or-weaviate-url...' }, ].map(({ label, key, placeholder }) => (
setDraft(d => ({ ...d, ragConfig: { ...d.ragConfig, [key]: e.target.value } }))} className="w-full bg-white/5 border border-white/10 rounded-xl p-3 text-white text-sm outline-none focus:border-blue-500 transition-colors" />
))}
{EMBEDDING_MODELS.map(m => ( ))}