'use client'
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { ArrowLeft, Settings as SettingsIcon, Save, RefreshCw, AlertCircle, CheckCircle2, Cpu, Database, Globe, Key } from 'lucide-react'
import { llmService } from '../../services/llmService'
import { mcpService } from '../../services/mcpService'
import MCPStatusBanner from '../../components/MCPStatusBanner'
export default function SettingsPage() {
const [activeTab, setActiveTab] = useState<'llm' | 'mcp' | 'general'>('llm')
const [llmSettings, setLLMSettings] = useState({
defaultModel: '',
defaultPersonality: 'assistant',
temperature: 0.7,
maxTokens: 2000,
ollamaUrl: 'http://localhost:11434',
lmstudioUrl: 'http://localhost:1234',
openaiApiKey: '',
anthropicApiKey: '',
googleApiKey: ''
})
const [mcpSettings, setMCPSettings] = useState<Record<string, { enabled: boolean; url: string }>>({})
const [saving, setSaving] = useState(false)
const [saved, setSaved] = useState(false)
const [availableModels, setAvailableModels] = useState<string[]>([])
const [personalities, setPersonalities] = useState<Record<string, any>>({})
useEffect(() => {
loadSettings()
loadModels()
loadPersonalities()
loadMCPSettings()
}, [])
const loadSettings = () => {
// Load from localStorage
const saved = localStorage.getItem('llmSettings')
if (saved) {
setLLMSettings(JSON.parse(saved))
}
}
const loadModels = async () => {
const models = await llmService.listModels()
setAvailableModels(models.map(m => m.id))
}
const loadPersonalities = async () => {
const pers = await llmService.getPersonalities()
setPersonalities(pers)
}
const loadMCPSettings = async () => {
const servers = await mcpService.getServers()
const settings: Record<string, { enabled: boolean; url: string }> = {}
Object.entries(servers).forEach(([name, status]) => {
settings[name] = {
enabled: status.enabled,
url: status.base_url
}
})
setMCPSettings(settings)
}
const saveSettings = async () => {
setSaving(true)
setSaved(false)
try {
// Save LLM settings to localStorage
localStorage.setItem('llmSettings', JSON.stringify(llmSettings))
// Save MCP settings (would typically go to backend)
// For now, just save to localStorage
localStorage.setItem('mcpSettings', JSON.stringify(mcpSettings))
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500))
setSaved(true)
setTimeout(() => setSaved(false), 3000)
} catch (error) {
console.error('Failed to save settings:', error)
} finally {
setSaving(false)
}
}
return (
<div style={{ minHeight: '100vh', backgroundColor: '#f8fafc', padding: '24px' }}>
{/* Header */}
<div style={{ marginBottom: '24px' }}>
<Link
to="/"
style={{
display: 'inline-flex',
alignItems: 'center',
color: '#2563eb',
textDecoration: 'none',
marginBottom: '16px'
}}
>
<ArrowLeft style={{ width: '16px', height: '16px', marginRight: '8px' }} />
Back to Home
</Link>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
<SettingsIcon style={{ width: '32px', height: '32px', color: '#2563eb' }} />
<div>
<h1 style={{ fontSize: '32px', fontWeight: '700', margin: 0, color: '#1e293b' }}>
Settings
</h1>
<p style={{ fontSize: '16px', color: '#64748b', margin: '4px 0 0 0' }}>
Configure LLM providers, MCP servers, and application preferences
</p>
</div>
</div>
</div>
{/* Tabs */}
<div style={{
backgroundColor: 'white',
borderRadius: '12px',
padding: '8px',
marginBottom: '24px',
display: 'flex',
gap: '8px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
}}>
{[
{ id: 'llm' as const, label: 'LLM Settings', icon: Cpu },
{ id: 'mcp' as const, label: 'MCP Servers', icon: Database },
{ id: 'general' as const, label: 'General', icon: SettingsIcon }
].map(tab => {
const Icon = tab.icon
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
style={{
flex: 1,
padding: '12px 16px',
borderRadius: '8px',
border: 'none',
backgroundColor: activeTab === tab.id ? '#2563eb' : 'transparent',
color: activeTab === tab.id ? 'white' : '#64748b',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
fontWeight: activeTab === tab.id ? '600' : '400',
fontSize: '14px'
}}
>
<Icon style={{ width: '18px', height: '18px' }} />
{tab.label}
</button>
)
})}
</div>
{/* Save Button */}
<div style={{ marginBottom: '24px', display: 'flex', justifyContent: 'flex-end', gap: '12px' }}>
{saved && (
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '8px 16px',
backgroundColor: '#dcfce7',
color: '#16a34a',
borderRadius: '6px',
fontSize: '14px'
}}>
<CheckCircle2 style={{ width: '16px', height: '16px' }} />
Settings saved!
</div>
)}
<button
onClick={saveSettings}
disabled={saving}
style={{
padding: '10px 20px',
borderRadius: '8px',
border: 'none',
backgroundColor: '#2563eb',
color: 'white',
cursor: saving ? 'not-allowed' : 'pointer',
display: 'flex',
alignItems: 'center',
gap: '8px',
fontWeight: '500',
fontSize: '14px',
opacity: saving ? 0.6 : 1
}}
>
{saving ? (
<>
<RefreshCw style={{ width: '16px', height: '16px', animation: 'spin 1s linear infinite' }} />
Saving...
</>
) : (
<>
<Save style={{ width: '16px', height: '16px' }} />
Save Settings
</>
)}
</button>
</div>
{/* LLM Settings */}
{activeTab === 'llm' && (
<div style={{
backgroundColor: 'white',
borderRadius: '12px',
padding: '24px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
}}>
<MCPStatusBanner serverName="local_llm" displayName="Local LLM MCP" />
<h2 style={{ fontSize: '20px', fontWeight: '600', marginBottom: '24px', color: '#1e293b' }}>
LLM Configuration
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
{/* Default Model */}
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
Default Model
</label>
<select
value={llmSettings.defaultModel}
onChange={(e) => setLLMSettings({ ...llmSettings, defaultModel: e.target.value })}
style={{
width: '100%',
padding: '10px 12px',
borderRadius: '6px',
border: '1px solid #e2e8f0',
fontSize: '14px',
color: '#1e293b'
}}
>
<option value="">Select a model...</option>
{availableModels.map(model => (
<option key={model} value={model}>{model}</option>
))}
</select>
</div>
{/* Default Personality */}
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
Default Personality
</label>
<select
value={llmSettings.defaultPersonality}
onChange={(e) => setLLMSettings({ ...llmSettings, defaultPersonality: e.target.value })}
style={{
width: '100%',
padding: '10px 12px',
borderRadius: '6px',
border: '1px solid #e2e8f0',
fontSize: '14px',
color: '#1e293b'
}}
>
{Object.entries(personalities).map(([key, persona]) => (
<option key={key} value={key}>{persona.name}</option>
))}
</select>
</div>
{/* Temperature */}
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
Default Temperature: {llmSettings.temperature}
</label>
<input
type="range"
min="0"
max="2"
step="0.1"
value={llmSettings.temperature}
onChange={(e) => setLLMSettings({ ...llmSettings, temperature: parseFloat(e.target.value) })}
style={{ width: '100%' }}
/>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px', color: '#64748b', marginTop: '4px' }}>
<span>Conservative (0.0)</span>
<span>Balanced (1.0)</span>
<span>Creative (2.0)</span>
</div>
</div>
{/* Max Tokens */}
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
Max Tokens: {llmSettings.maxTokens.toLocaleString()}
</label>
<input
type="range"
min="256"
max="8000"
step="256"
value={llmSettings.maxTokens}
onChange={(e) => setLLMSettings({ ...llmSettings, maxTokens: parseInt(e.target.value) })}
style={{ width: '100%' }}
/>
</div>
{/* Provider URLs */}
<div style={{ borderTop: '1px solid #e2e8f0', paddingTop: '24px' }}>
<h3 style={{ fontSize: '16px', fontWeight: '600', marginBottom: '16px', color: '#1e293b' }}>
Provider URLs
</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
Ollama URL
</label>
<input
type="text"
value={llmSettings.ollamaUrl}
onChange={(e) => setLLMSettings({ ...llmSettings, ollamaUrl: e.target.value })}
style={{
width: '100%',
padding: '10px 12px',
borderRadius: '6px',
border: '1px solid #e2e8f0',
fontSize: '14px',
color: '#1e293b'
}}
placeholder="http://localhost:11434"
/>
</div>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
LM Studio URL
</label>
<input
type="text"
value={llmSettings.lmstudioUrl}
onChange={(e) => setLLMSettings({ ...llmSettings, lmstudioUrl: e.target.value })}
style={{
width: '100%',
padding: '10px 12px',
borderRadius: '6px',
border: '1px solid #e2e8f0',
fontSize: '14px',
color: '#1e293b'
}}
placeholder="http://localhost:1234"
/>
</div>
</div>
</div>
{/* API Keys */}
<div style={{ borderTop: '1px solid #e2e8f0', paddingTop: '24px' }}>
<h3 style={{ fontSize: '16px', fontWeight: '600', marginBottom: '16px', color: '#1e293b', display: 'flex', alignItems: 'center', gap: '8px' }}>
<Key style={{ width: '18px', height: '18px' }} />
API Keys (Optional)
</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
OpenAI API Key
</label>
<input
type="password"
value={llmSettings.openaiApiKey}
onChange={(e) => setLLMSettings({ ...llmSettings, openaiApiKey: e.target.value })}
style={{
width: '100%',
padding: '10px 12px',
borderRadius: '6px',
border: '1px solid #e2e8f0',
fontSize: '14px',
color: '#1e293b'
}}
placeholder="sk-..."
/>
</div>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
Anthropic API Key
</label>
<input
type="password"
value={llmSettings.anthropicApiKey}
onChange={(e) => setLLMSettings({ ...llmSettings, anthropicApiKey: e.target.value })}
style={{
width: '100%',
padding: '10px 12px',
borderRadius: '6px',
border: '1px solid #e2e8f0',
fontSize: '14px',
color: '#1e293b'
}}
placeholder="sk-ant-..."
/>
</div>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', color: '#1e293b', marginBottom: '8px' }}>
Google API Key
</label>
<input
type="password"
value={llmSettings.googleApiKey}
onChange={(e) => setLLMSettings({ ...llmSettings, googleApiKey: e.target.value })}
style={{
width: '100%',
padding: '10px 12px',
borderRadius: '6px',
border: '1px solid #e2e8f0',
fontSize: '14px',
color: '#1e293b'
}}
placeholder="AIza..."
/>
</div>
</div>
</div>
</div>
</div>
)}
{/* MCP Settings */}
{activeTab === 'mcp' && (
<div style={{
backgroundColor: 'white',
borderRadius: '12px',
padding: '24px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
}}>
<h2 style={{ fontSize: '20px', fontWeight: '600', marginBottom: '24px', color: '#1e293b' }}>
MCP Server Configuration
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
{Object.entries(mcpSettings).map(([name, config]) => (
<div
key={name}
style={{
padding: '16px',
border: '1px solid #e2e8f0',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
gap: '16px'
}}
>
<input
type="checkbox"
checked={config.enabled}
onChange={(e) => setMCPSettings({
...mcpSettings,
[name]: { ...config, enabled: e.target.checked }
})}
style={{ width: '20px', height: '20px' }}
/>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: '600', color: '#1e293b', marginBottom: '4px' }}>
{name}
</div>
<input
type="text"
value={config.url}
onChange={(e) => setMCPSettings({
...mcpSettings,
[name]: { ...config, url: e.target.value }
})}
style={{
width: '100%',
padding: '6px 10px',
borderRadius: '4px',
border: '1px solid #e2e8f0',
fontSize: '13px',
color: '#64748b'
}}
/>
</div>
</div>
))}
</div>
</div>
)}
{/* General Settings */}
{activeTab === 'general' && (
<div style={{
backgroundColor: 'white',
borderRadius: '12px',
padding: '24px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
}}>
<h2 style={{ fontSize: '20px', fontWeight: '600', marginBottom: '24px', color: '#1e293b' }}>
General Settings
</h2>
<p style={{ color: '#64748b' }}>General application settings coming soon...</p>
</div>
)}
<style>{`
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`}</style>
</div>
)
}