import { useEffect, useState } from 'react';
import { Save, RefreshCw, AlertCircle, CheckCircle2, Eye, EyeOff, Trash2, Plus } from 'lucide-react';
import api from '../lib/api';
type TabType = 'system' | 'providers' | 'layers' | 'tasks' | 'features';
interface SystemConfig {
server_port?: number;
server_host?: string;
api_version?: string;
log_level?: string;
redis_host?: string;
redis_port?: number;
cache_ttl_seconds?: number;
}
interface Provider {
provider: string;
apiKey: string;
endpoint: string;
enabled: boolean;
configuration?: Record<string, any>;
}
interface LayerConfig {
layer: string;
models: string[];
priority: number;
enabled: boolean;
description?: string;
}
interface TaskConfig {
task: string;
preferredModel: string;
fallbackModels: string[];
enabled: boolean;
}
interface FeatureFlag {
flag: string;
enabled: boolean;
description?: string;
metadata?: Record<string, any>;
}
export default function Settings() {
const [activeTab, setActiveTab] = useState<TabType>('system');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle');
// Data states
const [systemConfig, setSystemConfig] = useState<SystemConfig>({});
const [providers, setProviders] = useState<Provider[]>([]);
const [layers, setLayers] = useState<LayerConfig[]>([]);
const [tasks, setTasks] = useState<TaskConfig[]>([]);
const [features, setFeatures] = useState<FeatureFlag[]>([]);
// UI states
const [showApiKeys, setShowApiKeys] = useState<Record<string, boolean>>({});
useEffect(() => {
loadAllSettings();
}, []);
async function loadAllSettings() {
setLoading(true);
try {
await Promise.all([
loadSystemConfig(),
loadProviders(),
loadLayers(),
loadTasks(),
loadFeatures()
]);
setError(null);
} catch (err) {
console.error('Failed to load settings:', err);
setError(err instanceof Error ? err.message : 'Failed to load settings');
} finally {
setLoading(false);
}
}
async function loadSystemConfig() {
try {
const response = await api.get('/config/system');
setSystemConfig(response.data || {});
} catch (err) {
console.error('Failed to load system config:', err);
}
}
async function loadProviders() {
try {
const response = await api.get('/config/providers');
setProviders(response.data || []);
} catch (err) {
console.error('Failed to load providers:', err);
}
}
async function loadLayers() {
try {
const response = await api.get('/config/layers');
setLayers(response.data || []);
} catch (err) {
console.error('Failed to load layers:', err);
}
}
async function loadTasks() {
try {
const response = await api.get('/config/tasks');
setTasks(response.data || []);
} catch (err) {
console.error('Failed to load tasks:', err);
}
}
async function loadFeatures() {
try {
const response = await api.get('/config/features');
setFeatures(response.data || []);
} catch (err) {
console.error('Failed to load features:', err);
}
}
async function saveSystemConfig() {
try {
for (const [key, value] of Object.entries(systemConfig)) {
await api.put(`/config/system/${key}`, { value });
}
setSaveStatus('success');
setTimeout(() => setSaveStatus('idle'), 3000);
} catch (err) {
console.error('Failed to save system config:', err);
setSaveStatus('error');
setTimeout(() => setSaveStatus('idle'), 3000);
}
}
async function saveProvider(provider: Provider) {
try {
await api.post(`/config/providers/${provider.provider}`, {
apiKey: provider.apiKey,
endpoint: provider.endpoint,
enabled: provider.enabled,
configuration: provider.configuration
});
setSaveStatus('success');
setTimeout(() => setSaveStatus('idle'), 3000);
await loadProviders();
} catch (err) {
console.error('Failed to save provider:', err);
setSaveStatus('error');
setTimeout(() => setSaveStatus('idle'), 3000);
}
}
async function deleteProvider(providerName: string) {
if (!confirm(`Are you sure you want to delete ${providerName}?`)) return;
try {
await api.delete(`/config/providers/${providerName}`);
await loadProviders();
} catch (err) {
console.error('Failed to delete provider:', err);
}
}
async function saveLayer(layer: LayerConfig) {
try {
await api.put(`/config/layers/${layer.layer}`, {
models: layer.models,
priority: layer.priority,
enabled: layer.enabled,
description: layer.description
});
setSaveStatus('success');
setTimeout(() => setSaveStatus('idle'), 3000);
await loadLayers();
} catch (err) {
console.error('Failed to save layer:', err);
setSaveStatus('error');
setTimeout(() => setSaveStatus('idle'), 3000);
}
}
async function saveTask(task: TaskConfig) {
try {
await api.put(`/config/tasks/${task.task}`, {
preferredModel: task.preferredModel,
fallbackModels: task.fallbackModels,
enabled: task.enabled
});
setSaveStatus('success');
setTimeout(() => setSaveStatus('idle'), 3000);
await loadTasks();
} catch (err) {
console.error('Failed to save task:', err);
setSaveStatus('error');
setTimeout(() => setSaveStatus('idle'), 3000);
}
}
async function saveFeature(feature: FeatureFlag) {
try {
await api.put(`/config/features/${feature.flag}`, {
enabled: feature.enabled,
description: feature.description,
metadata: feature.metadata
});
setSaveStatus('success');
setTimeout(() => setSaveStatus('idle'), 3000);
await loadFeatures();
} catch (err) {
console.error('Failed to save feature:', err);
setSaveStatus('error');
setTimeout(() => setSaveStatus('idle'), 3000);
}
}
async function clearCache() {
try {
await api.post('/config/cache/clear');
alert('Cache cleared successfully');
} catch (err) {
console.error('Failed to clear cache:', err);
alert('Failed to clear cache');
}
}
if (loading) {
return (
<div className="flex items-center justify-center h-96">
<div className="text-center">
<div className="w-12 h-12 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<p className="text-slate-400">Loading settings...</p>
</div>
</div>
);
}
const tabs: { id: TabType; label: string }[] = [
{ id: 'system', label: 'System' },
{ id: 'providers', label: 'Providers' },
{ id: 'layers', label: 'Layers' },
{ id: 'tasks', label: 'Tasks' },
{ id: 'features', label: 'Features' }
];
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold text-white">Settings</h1>
<div className="flex items-center gap-3">
{saveStatus === 'success' && (
<div className="flex items-center gap-2 text-green-400">
<CheckCircle2 className="w-5 h-5" />
<span>Saved successfully</span>
</div>
)}
{saveStatus === 'error' && (
<div className="flex items-center gap-2 text-red-400">
<AlertCircle className="w-5 h-5" />
<span>Failed to save</span>
</div>
)}
<button onClick={clearCache} className="btn-secondary flex items-center gap-2">
<RefreshCw className="w-4 h-4" />
Clear Cache
</button>
<button onClick={loadAllSettings} className="btn-secondary flex items-center gap-2">
<RefreshCw className="w-4 h-4" />
Reload
</button>
</div>
</div>
{error && (
<div className="bg-red-500/10 border border-red-500/50 rounded-lg p-4">
<div className="flex items-center gap-3">
<AlertCircle className="w-5 h-5 text-red-400" />
<div className="flex-1">
<h3 className="text-red-400 font-semibold">Error</h3>
<p className="text-red-300/80">{error}</p>
</div>
</div>
</div>
)}
{/* Tabs */}
<div className="border-b border-slate-700">
<nav className="flex space-x-4">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 font-medium transition-colors border-b-2 ${
activeTab === tab.id
? 'text-blue-400 border-blue-400'
: 'text-slate-400 border-transparent hover:text-slate-300'
}`}
>
{tab.label}
</button>
))}
</nav>
</div>
{/* Tab Content */}
<div className="min-h-[500px]">
{activeTab === 'system' && (
<SystemConfigTab
config={systemConfig}
onChange={setSystemConfig}
onSave={saveSystemConfig}
/>
)}
{activeTab === 'providers' && (
<ProvidersTab
providers={providers}
showApiKeys={showApiKeys}
onToggleApiKey={(provider) => setShowApiKeys({ ...showApiKeys, [provider]: !showApiKeys[provider] })}
onSave={saveProvider}
onDelete={deleteProvider}
/>
)}
{activeTab === 'layers' && (
<LayersTab
layers={layers}
onSave={saveLayer}
/>
)}
{activeTab === 'tasks' && (
<TasksTab
tasks={tasks}
onSave={saveTask}
/>
)}
{activeTab === 'features' && (
<FeaturesTab
features={features}
onSave={saveFeature}
/>
)}
</div>
</div>
);
}
// System Config Tab Component
function SystemConfigTab({
config,
onChange,
onSave
}: {
config: SystemConfig;
onChange: (config: SystemConfig) => void;
onSave: () => void;
}) {
return (
<div className="card space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-white">System Configuration</h2>
<button onClick={onSave} className="btn-primary flex items-center gap-2">
<Save className="w-4 h-4" />
Save Changes
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Server Host
</label>
<input
type="text"
value={config.server_host || ''}
onChange={(e) => onChange({ ...config, server_host: e.target.value })}
className="input w-full"
placeholder="0.0.0.0"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Server Port
</label>
<input
type="number"
value={config.server_port || ''}
onChange={(e) => onChange({ ...config, server_port: parseInt(e.target.value) || 3000 })}
className="input w-full"
placeholder="3000"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
API Version
</label>
<input
type="text"
value={config.api_version || ''}
onChange={(e) => onChange({ ...config, api_version: e.target.value })}
className="input w-full"
placeholder="v1"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Log Level
</label>
<select
value={config.log_level || 'info'}
onChange={(e) => onChange({ ...config, log_level: e.target.value })}
className="input w-full"
>
<option value="debug">Debug</option>
<option value="info">Info</option>
<option value="warn">Warning</option>
<option value="error">Error</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Redis Host
</label>
<input
type="text"
value={config.redis_host || ''}
onChange={(e) => onChange({ ...config, redis_host: e.target.value })}
className="input w-full"
placeholder="localhost"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Redis Port
</label>
<input
type="number"
value={config.redis_port || ''}
onChange={(e) => onChange({ ...config, redis_port: parseInt(e.target.value) || 6379 })}
className="input w-full"
placeholder="6379"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Cache TTL (seconds)
</label>
<input
type="number"
value={config.cache_ttl_seconds || ''}
onChange={(e) => onChange({ ...config, cache_ttl_seconds: parseInt(e.target.value) || 60 })}
className="input w-full"
placeholder="60"
/>
</div>
</div>
</div>
);
}
// Providers Tab Component
function ProvidersTab({
providers,
showApiKeys,
onToggleApiKey,
onSave,
onDelete
}: {
providers: Provider[];
showApiKeys: Record<string, boolean>;
onToggleApiKey: (provider: string) => void;
onSave: (provider: Provider) => void;
onDelete: (provider: string) => void;
}) {
const [editingProvider, setEditingProvider] = useState<Provider | null>(null);
const [newProvider, setNewProvider] = useState(false);
const handleEdit = (provider: Provider) => {
setEditingProvider({ ...provider });
setNewProvider(false);
};
const handleNew = () => {
setEditingProvider({
provider: '',
apiKey: '',
endpoint: '',
enabled: true
});
setNewProvider(true);
};
const handleSave = () => {
if (editingProvider) {
onSave(editingProvider);
setEditingProvider(null);
setNewProvider(false);
}
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-white">Provider Configuration</h2>
<button onClick={handleNew} className="btn-primary flex items-center gap-2">
<Plus className="w-4 h-4" />
Add Provider
</button>
</div>
{editingProvider && (
<div className="card space-y-4 border-2 border-blue-500/50">
<h3 className="text-lg font-semibold text-white">
{newProvider ? 'New Provider' : `Edit ${editingProvider.provider}`}
</h3>
<div className="grid grid-cols-1 gap-4">
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Provider Name
</label>
<input
type="text"
value={editingProvider.provider}
onChange={(e) => setEditingProvider({ ...editingProvider, provider: e.target.value })}
className="input w-full"
placeholder="openrouter"
disabled={!newProvider}
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
API Key
</label>
<input
type="text"
value={editingProvider.apiKey}
onChange={(e) => setEditingProvider({ ...editingProvider, apiKey: e.target.value })}
className="input w-full font-mono"
placeholder="sk-or-v1-..."
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Endpoint URL
</label>
<input
type="text"
value={editingProvider.endpoint}
onChange={(e) => setEditingProvider({ ...editingProvider, endpoint: e.target.value })}
className="input w-full"
placeholder="https://openrouter.ai/api/v1"
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="provider-enabled"
checked={editingProvider.enabled}
onChange={(e) => setEditingProvider({ ...editingProvider, enabled: e.target.checked })}
className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
/>
<label htmlFor="provider-enabled" className="text-sm font-medium text-slate-300">
Enabled
</label>
</div>
</div>
<div className="flex gap-3">
<button onClick={handleSave} className="btn-primary">
<Save className="w-4 h-4 mr-2" />
Save Provider
</button>
<button onClick={() => { setEditingProvider(null); setNewProvider(false); }} className="btn-secondary">
Cancel
</button>
</div>
</div>
)}
<div className="grid grid-cols-1 gap-4">
{providers.map(provider => (
<div key={provider.provider} className="card">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-lg font-semibold text-white">{provider.provider}</h3>
<span className={`px-2 py-1 rounded text-xs font-semibold ${
provider.enabled
? 'bg-green-500/20 text-green-400'
: 'bg-red-500/20 text-red-400'
}`}>
{provider.enabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<span className="text-slate-400">Endpoint:</span>
<span className="text-slate-300 font-mono">{provider.endpoint}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-400">API Key:</span>
<span className="text-slate-300 font-mono">
{showApiKeys[provider.provider]
? provider.apiKey
: provider.apiKey.substring(0, 10) + '...'
}
</span>
<button
onClick={() => onToggleApiKey(provider.provider)}
className="text-slate-400 hover:text-slate-300"
>
{showApiKeys[provider.provider] ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
</div>
<div className="flex gap-2">
<button
onClick={() => handleEdit(provider)}
className="btn-secondary text-sm"
>
Edit
</button>
<button
onClick={() => onDelete(provider.provider)}
className="btn-danger text-sm flex items-center gap-1"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
{providers.length === 0 && !editingProvider && (
<div className="card text-center py-12">
<p className="text-slate-400 mb-4">No providers configured</p>
<button onClick={handleNew} className="btn-primary">
<Plus className="w-4 h-4 mr-2" />
Add Your First Provider
</button>
</div>
)}
</div>
);
}
// Layers Tab Component
function LayersTab({
layers,
onSave
}: {
layers: LayerConfig[];
onSave: (layer: LayerConfig) => void;
}) {
const [editingLayer, setEditingLayer] = useState<LayerConfig | null>(null);
const handleEdit = (layer: LayerConfig) => {
setEditingLayer({ ...layer });
};
const handleSave = () => {
if (editingLayer) {
onSave(editingLayer);
setEditingLayer(null);
}
};
const handleModelChange = (index: number, value: string) => {
if (!editingLayer) return;
const newModels = [...editingLayer.models];
newModels[index] = value;
setEditingLayer({ ...editingLayer, models: newModels });
};
const handleAddModel = () => {
if (!editingLayer) return;
setEditingLayer({ ...editingLayer, models: [...editingLayer.models, ''] });
};
const handleRemoveModel = (index: number) => {
if (!editingLayer) return;
const newModels = editingLayer.models.filter((_, i) => i !== index);
setEditingLayer({ ...editingLayer, models: newModels });
};
return (
<div className="space-y-6">
<h2 className="text-xl font-semibold text-white">Layer Configuration</h2>
{editingLayer && (
<div className="card space-y-4 border-2 border-blue-500/50">
<h3 className="text-lg font-semibold text-white">Edit {editingLayer.layer}</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Description
</label>
<input
type="text"
value={editingLayer.description || ''}
onChange={(e) => setEditingLayer({ ...editingLayer, description: e.target.value })}
className="input w-full"
placeholder="Layer description"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Priority
</label>
<input
type="number"
value={editingLayer.priority}
onChange={(e) => setEditingLayer({ ...editingLayer, priority: parseInt(e.target.value) || 0 })}
className="input w-full"
/>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-slate-300">
Models
</label>
<button onClick={handleAddModel} className="text-blue-400 hover:text-blue-300 text-sm">
+ Add Model
</button>
</div>
<div className="space-y-2">
{editingLayer.models.map((model, index) => (
<div key={index} className="flex gap-2">
<input
type="text"
value={model}
onChange={(e) => handleModelChange(index, e.target.value)}
className="input flex-1 font-mono text-sm"
placeholder="provider/model-name"
/>
<button
onClick={() => handleRemoveModel(index)}
className="btn-danger"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))}
</div>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="layer-enabled"
checked={editingLayer.enabled}
onChange={(e) => setEditingLayer({ ...editingLayer, enabled: e.target.checked })}
className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
/>
<label htmlFor="layer-enabled" className="text-sm font-medium text-slate-300">
Enabled
</label>
</div>
</div>
<div className="flex gap-3">
<button onClick={handleSave} className="btn-primary">
<Save className="w-4 h-4 mr-2" />
Save Layer
</button>
<button onClick={() => setEditingLayer(null)} className="btn-secondary">
Cancel
</button>
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{layers.map(layer => (
<div key={layer.layer} className="card">
<div className="flex items-start justify-between mb-3">
<div>
<div className="flex items-center gap-3 mb-1">
<h3 className="text-lg font-semibold text-white">{layer.layer}</h3>
<span className={`px-2 py-1 rounded text-xs font-semibold ${
layer.enabled
? 'bg-green-500/20 text-green-400'
: 'bg-red-500/20 text-red-400'
}`}>
{layer.enabled ? 'Enabled' : 'Disabled'}
</span>
</div>
{layer.description && (
<p className="text-sm text-slate-400">{layer.description}</p>
)}
</div>
<button
onClick={() => handleEdit(layer)}
className="btn-secondary text-sm"
>
Edit
</button>
</div>
<div className="space-y-2">
<div className="text-sm">
<span className="text-slate-400">Priority:</span>
<span className="text-slate-300 ml-2 font-semibold">{layer.priority}</span>
</div>
<div>
<span className="text-sm text-slate-400">Models:</span>
<div className="mt-1 space-y-1">
{layer.models.map((model, idx) => (
<div key={idx} className="text-sm font-mono text-slate-300 bg-slate-800 px-2 py-1 rounded">
{model}
</div>
))}
</div>
</div>
</div>
</div>
))}
</div>
</div>
);
}
// Tasks Tab Component
function TasksTab({
tasks,
onSave
}: {
tasks: TaskConfig[];
onSave: (task: TaskConfig) => void;
}) {
const [editingTask, setEditingTask] = useState<TaskConfig | null>(null);
const handleEdit = (task: TaskConfig) => {
setEditingTask({ ...task });
};
const handleSave = () => {
if (editingTask) {
onSave(editingTask);
setEditingTask(null);
}
};
const handleFallbackChange = (index: number, value: string) => {
if (!editingTask) return;
const newFallbacks = [...editingTask.fallbackModels];
newFallbacks[index] = value;
setEditingTask({ ...editingTask, fallbackModels: newFallbacks });
};
const handleAddFallback = () => {
if (!editingTask) return;
setEditingTask({ ...editingTask, fallbackModels: [...editingTask.fallbackModels, ''] });
};
const handleRemoveFallback = (index: number) => {
if (!editingTask) return;
const newFallbacks = editingTask.fallbackModels.filter((_, i) => i !== index);
setEditingTask({ ...editingTask, fallbackModels: newFallbacks });
};
return (
<div className="space-y-6">
<h2 className="text-xl font-semibold text-white">Task-Specific Model Configuration</h2>
{editingTask && (
<div className="card space-y-4 border-2 border-blue-500/50">
<h3 className="text-lg font-semibold text-white">Edit {editingTask.task}</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Preferred Model
</label>
<input
type="text"
value={editingTask.preferredModel}
onChange={(e) => setEditingTask({ ...editingTask, preferredModel: e.target.value })}
className="input w-full font-mono text-sm"
placeholder="provider/model-name"
/>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-slate-300">
Fallback Models
</label>
<button onClick={handleAddFallback} className="text-blue-400 hover:text-blue-300 text-sm">
+ Add Fallback
</button>
</div>
<div className="space-y-2">
{editingTask.fallbackModels.map((model, index) => (
<div key={index} className="flex gap-2">
<input
type="text"
value={model}
onChange={(e) => handleFallbackChange(index, e.target.value)}
className="input flex-1 font-mono text-sm"
placeholder="provider/model-name"
/>
<button
onClick={() => handleRemoveFallback(index)}
className="btn-danger"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))}
</div>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="task-enabled"
checked={editingTask.enabled}
onChange={(e) => setEditingTask({ ...editingTask, enabled: e.target.checked })}
className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
/>
<label htmlFor="task-enabled" className="text-sm font-medium text-slate-300">
Enabled
</label>
</div>
</div>
<div className="flex gap-3">
<button onClick={handleSave} className="btn-primary">
<Save className="w-4 h-4 mr-2" />
Save Task
</button>
<button onClick={() => setEditingTask(null)} className="btn-secondary">
Cancel
</button>
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{tasks.map(task => (
<div key={task.task} className="card">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<h3 className="text-lg font-semibold text-white capitalize">{task.task}</h3>
<span className={`px-2 py-1 rounded text-xs font-semibold ${
task.enabled
? 'bg-green-500/20 text-green-400'
: 'bg-red-500/20 text-red-400'
}`}>
{task.enabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<button
onClick={() => handleEdit(task)}
className="btn-secondary text-sm"
>
Edit
</button>
</div>
<div className="space-y-2">
<div>
<span className="text-sm text-slate-400">Preferred:</span>
<div className="mt-1 text-sm font-mono text-slate-300 bg-slate-800 px-2 py-1 rounded">
{task.preferredModel}
</div>
</div>
{task.fallbackModels.length > 0 && (
<div>
<span className="text-sm text-slate-400">Fallbacks:</span>
<div className="mt-1 space-y-1">
{task.fallbackModels.map((model, idx) => (
<div key={idx} className="text-sm font-mono text-slate-300 bg-slate-800 px-2 py-1 rounded">
{model}
</div>
))}
</div>
</div>
)}
</div>
</div>
))}
</div>
</div>
);
}
// Features Tab Component
function FeaturesTab({
features,
onSave
}: {
features: FeatureFlag[];
onSave: (feature: FeatureFlag) => void;
}) {
const handleToggle = (feature: FeatureFlag) => {
onSave({ ...feature, enabled: !feature.enabled });
};
return (
<div className="space-y-6">
<h2 className="text-xl font-semibold text-white">Feature Flags</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{features.map(feature => (
<div key={feature.flag} className="card">
<div className="flex items-start justify-between">
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-2">{feature.flag}</h3>
{feature.description && (
<p className="text-sm text-slate-400 mb-3">{feature.description}</p>
)}
{feature.metadata && Object.keys(feature.metadata).length > 0 && (
<div className="text-xs text-slate-500 bg-slate-800 p-2 rounded font-mono">
{JSON.stringify(feature.metadata, null, 2)}
</div>
)}
</div>
<button
onClick={() => handleToggle(feature)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
feature.enabled ? 'bg-blue-600' : 'bg-slate-600'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
feature.enabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
</div>
))}
</div>
{features.length === 0 && (
<div className="card text-center py-12">
<p className="text-slate-400">No feature flags configured</p>
</div>
)}
</div>
);
}