NEXON / frontend /src /views /SettingsView.jsx
ashishMenon05
fix(frontend): correct syntax errors in SettingsView for Tailwind v4 compatibility
68fba27
import React, { useState, useEffect } from 'react';
import { config } from '../config';
const OllamaModelPicker = ({ value, onChange, accentColor }) => {
const [models, setModels] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchModels = async () => {
setLoading(true);
setError(null);
try {
const res = await fetch(`${config.API_BASE}/models`);
if (!res.ok) throw new Error('Backend not reachable');
const data = await res.json();
setModels((data.local_models || []).map(m => ({ name: m })));
} catch (e) {
setError('Backend offline or Ollama disconnected');
setModels([]);
} finally {
setLoading(false);
}
};
useEffect(() => { fetchModels(); }, []);
useEffect(() => {
if (models.length > 0 && value !== undefined) {
const isInstalled = models.some(m => m.name === value);
if (!isInstalled) {
onChange(models[0].name);
}
}
}, [models, value, onChange]);
const borderClass = accentColor === 'primary' ? 'border-primary/30 focus:border-primary' : 'border-secondary/30 focus:border-secondary';
const textClass = accentColor === 'primary' ? 'text-primary' : 'text-secondary';
return (
<div className="space-y-2">
<div className="flex justify-between items-center">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Local Ollama Models</label>
<button
onClick={fetchModels}
className={`flex items-center gap-1 text-[9px] font-mono ${textClass} hover:opacity-70 transition-opacity`}
>
<span className={`material-symbols-outlined text-xs ${loading ? 'animate-spin' : ''}`}>refresh</span>
{loading ? 'Scanning...' : 'Refresh'}
</button>
</div>
{error ? (
<div className="flex items-center gap-2 py-2 border-b border-error/30">
<span className="material-symbols-outlined text-error text-sm">wifi_off</span>
<span className="text-[10px] font-mono text-error">{error}</span>
</div>
) : (
<select
value={value}
onChange={e => onChange(e.target.value)}
className={`w-full bg-surface-container-lowest border-b ${borderClass} py-2 font-mono text-sm text-on-surface cursor-pointer focus:outline-none transition-all`}
>
{models.length === 0 && <option value={value}>{value || "No models found"}</option>}
{models.length > 0 && !models.find(m => m.name === value) && value && <option value={value}>{value} (Not installed locally)</option>}
{models.length > 0 && !value && <option value="" disabled>Select a model...</option>}
{models.map(m => (
<option key={m.name} value={m.name}>{m.name}</option>
))}
</select>
)}
{models.length > 0 && (
<p className={`text-[9px] font-mono ${textClass} opacity-50 text-right mt-1`}>{models.length} model{models.length !== 1 ? 's' : ''} available locally</p>
)}
</div>
);
};
const HFModelPicker = ({ value, onChange, accentColor }) => {
const [models, setModels] = useState([]);
const [loading, setLoading] = useState(true);
const [expanded, setExpanded] = useState(false);
useEffect(() => {
const fetchModels = async () => {
try {
const res = await fetch(`${config.API_BASE}/models/hf`);
if (res.ok) {
const data = await res.json();
setModels(data.models || []);
} else {
setModels([]);
}
} catch (e) {
setModels([]);
} finally {
setLoading(false);
}
};
fetchModels();
}, []);
const borderClass = accentColor === 'primary' ? 'border-primary/30 focus:border-primary' : 'border-secondary/30 focus:border-secondary';
const textClass = accentColor === 'primary' ? 'text-primary' : 'text-secondary';
const groupedModels = models.reduce((acc, model) => {
const org = model.split('/')[0];
if (!acc[org]) acc[org] = [];
acc[org].push(model);
return acc;
}, {});
return (
<div className="space-y-2">
<div className="flex justify-between items-center">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">HuggingFace Models</label>
<button
onClick={() => setExpanded(!expanded)}
className={`text-[9px] font-mono ${textClass} hover:opacity-70 transition-opacity`}
>
{expanded ? '▲ Collapse' : '▼ Expand'}
</button>
</div>
{loading ? (
<div className="flex items-center gap-2 py-2">
<span className="material-symbols-outlined text-sm animate-spin">sync</span>
<span className="text-[10px] font-mono text-slate-500">Loading models...</span>
</div>
) : expanded ? (
<div className="max-h-48 overflow-y-auto bg-surface-container-lowest rounded border border-white/5">
{Object.entries(groupedModels).map(([org, orgModels]) => (
<div key={org}>
<div className="px-3 py-1 bg-surface-container-highest text-[9px] font-mono text-slate-400 uppercase sticky top-0">
{org}
</div>
{orgModels.map(model => (
<button
key={model}
onClick={() => onChange(model)}
className={`w-full text-left px-3 py-1.5 text-[10px] font-mono transition-all hover:bg-surface-container-high ${
value === model ? `${textClass} bg-primary/10` : 'text-on-surface'
}`}
>
{model.split('/')[1]}
</button>
))}
</div>
))}
</div>
) : (
<select
value={value}
onChange={e => onChange(e.target.value)}
className={`w-full bg-surface-container-lowest border-b ${borderClass} py-2 font-mono text-xs text-on-surface cursor-pointer focus:outline-none transition-all`}
>
{models.map(m => (
<option key={m} value={m}>{m}</option>
))}
</select>
)}
<p className={`text-[9px] font-mono ${textClass} opacity-50`}>{models.length} models available</p>
</div>
);
};
const ROLES = ["INVESTIGATOR", "VALIDATOR", "FORENSIC_ANALYST", "NETWORK_ENGINEER", "SYSTEM_ADMIN", "SECURITY_ARCHITECT", "COMPLIANCE_OFFICER", "CUSTOM_ROLE"];
const SettingsView = () => {
const [agents, setAgents] = useState([]);
const [openaiKey, setOpenaiKey] = useState('');
const [maxSteps, setMaxSteps] = useState(12);
const [complexity, setComplexity] = useState('LEVEL_02: ADVERSARIAL');
const [saved, setSaved] = useState(false);
const [executionMode, setExecutionMode] = useState('simulated');
const [sshConfig, setSshConfig] = useState({ host: '', port: 22, user: '', password: '' });
const [sshTestStatus, setSshTestStatus] = useState(null);
useEffect(() => {
const fetchConfig = async () => {
try {
const res = await fetch(`${config.API_BASE}/config`);
const data = await res.json();
if (data.models && data.models.agents) {
setAgents(data.models.agents.map(a => ({
id: a.id,
provider: a.provider || 'hf',
model: a.model,
hfModel: (a.provider === 'hf' && a.model?.includes('/')) ? a.model : 'meta-llama/Llama-3.1-8B-Instruct',
openaiModel: a.provider === 'openai' ? a.model : 'gpt-4o-mini',
temp: a.temperature || 0.7,
role: a.role?.startsWith('CUSTOM_') ? 'CUSTOM_ROLE' : a.role || 'INVESTIGATOR',
customRoleName: a.role?.startsWith('CUSTOM_') ? a.role.replace('CUSTOM_', '').replace(/_/g, ' ') : '',
customPrompt: a.system_prompt || ''
})));
} else {
setAgents([{
id: 'agent_a', provider: 'hf', model: '', hfModel: 'meta-llama/Llama-3.1-8B-Instruct', openaiModel: 'gpt-4o', temp: 0.7, role: 'INVESTIGATOR', customRoleName: '', customPrompt: ''
}]);
}
if (data.models.openai_api_key) setOpenaiKey(data.models.openai_api_key);
setMaxSteps(data.episode.max_steps);
if (data.execution) {
setExecutionMode(data.execution.mode || 'simulated');
setSshConfig({
host: data.execution.ssh_host || '',
port: data.execution.ssh_port || 22,
user: data.execution.ssh_user || '',
password: data.execution.ssh_password || ''
});
}
} catch (e) {
console.error("Failed to fetch initial config", e);
}
};
fetchConfig();
}, []);
const handleSave = async () => {
try {
const agentPayload = agents.map(a => ({
id: a.id,
model: a.provider === 'ollama' ? a.model : (a.provider === 'openai' ? a.openaiModel : a.hfModel),
provider: a.provider,
role: a.role === 'CUSTOM_ROLE' ? `CUSTOM_${a.customRoleName.replace(/ /g, '_').toUpperCase()}` : a.role,
system_prompt: a.customPrompt,
temperature: a.temp
}));
await fetch(`${config.API_BASE}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
MAX_STEPS: maxSteps,
AGENTS: agentPayload,
EXECUTION_MODE: executionMode,
SSH_HOST: sshConfig.host,
SSH_PORT: sshConfig.port,
SSH_USER: sshConfig.user,
SSH_PASSWORD: sshConfig.password,
OPENAI_API_KEY: openaiKey
})
});
setSaved(true);
setTimeout(() => setSaved(false), 2000);
} catch (e) {
console.error("Save failed", e);
}
};
useEffect(() => {
if (agents.length === 0) return;
const agentPayload = agents.map(a => ({
id: a.id,
model: a.provider === 'ollama' ? a.model : (a.provider === 'openai' ? a.openaiModel : a.hfModel),
provider: a.provider,
role: a.role === 'CUSTOM_ROLE' ? `CUSTOM_${a.customRoleName.replace(/ /g, '_').toUpperCase()}` : a.role,
system_prompt: a.customPrompt,
temperature: a.temp
}));
fetch(`${config.API_BASE}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
MAX_STEPS: maxSteps,
AGENTS: agentPayload,
EXECUTION_MODE: executionMode,
SSH_HOST: sshConfig.host,
SSH_PORT: sshConfig.port,
SSH_USER: sshConfig.user,
SSH_PASSWORD: sshConfig.password,
OPENAI_API_KEY: openaiKey
})
}).catch(e => { });
}, [agents, maxSteps, executionMode, sshConfig, openaiKey]);
const handleUpdateAgent = (index, updater) => {
setAgents(prev => {
const next = [...prev];
next[index] = typeof updater === 'function' ? updater(next[index]) : updater;
return next;
});
};
const addAgent = () => {
const newId = `agent_${Date.now()}`;
const roles = ['INVESTIGATOR', 'VALIDATOR', 'FORENSIC_ANALYST', 'NETWORK_ENGINEER', 'SYSTEM_ADMIN', 'SECURITY_ARCHITECT', 'COMPLIANCE_OFFICER'];
const role = roles[agents.length % roles.length];
setAgents(prev => [...prev, {
id: newId, provider: 'hf', model: '', hfModel: 'meta-llama/Llama-3.2-1B-Instruct', openaiModel: 'gpt-4o-mini', temp: Math.max(0.3, 0.7 - agents.length * 0.05), role, customRoleName: '', customPrompt: ''
}]);
};
const removeAgent = (index) => {
if (agents.length <= 1) return;
setAgents(prev => prev.filter((_, i) => i !== index));
};
const ProviderToggle = ({ agent, index }) => {
const getButtonClass = (p) => {
if (agent.provider === p) {
return index % 2 === 0 ? 'flex-1 py-1 px-3 rounded text-[10px] font-mono font-bold uppercase transition-all bg-primary text-black' : 'flex-1 py-1 px-3 rounded text-[10px] font-mono font-bold uppercase transition-all bg-secondary text-black';
}
return 'flex-1 py-1 px-3 rounded text-[10px] font-mono font-bold uppercase transition-all text-outline-variant hover:text-white';
};
const getProviderLabel = (p) => {
if (p === 'ollama') return 'Local Ollama';
if (p === 'hf') return 'Hugging Face';
return 'OpenAI';
};
return (
<div className="flex gap-2 p-1 bg-surface-container-highest rounded-lg border border-white/5">
{['ollama', 'hf', 'openai'].map(p => (
<button
key={p}
onClick={() => handleUpdateAgent(index, a => ({ ...a, provider: p }))}
className={getButtonClass(p)}
>
{getProviderLabel(p)}
</button>
))}
</div>
);
};
return (
<div className="space-y-12 animate-in fade-in duration-500">
<section className="flex flex-col md:flex-row justify-between items-end gap-6">
<div className="max-w-2xl">
<div className="flex items-center gap-3 mb-2">
<div className="w-1 h-6 bg-primary"></div>
<span className="font-mono text-xs tracking-[0.3em] text-primary uppercase">NEXUS_CORE_V2.0</span>
</div>
<h1 className="text-5xl font-headline font-bold text-on-surface tracking-tight leading-none mb-4 uppercase">
Settings <span className="text-primary/40">Configuration</span>
</h1>
<p className="text-on-surface-variant font-body leading-relaxed max-w-lg">
Calibrate the investigation environment for active agents. Adjust neural temperature, step limits, and procedural complexity.
</p>
</div>
</section>
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 items-stretch">
{/* N-Agents Render */}
{agents.map((agent, index) => {
const isPrimary = index % 2 === 0;
const accentColor = isPrimary ? 'primary' : 'secondary';
const titleColor = isPrimary ? 'text-primary' : 'text-secondary';
const bgColor = isPrimary ? 'bg-primary/10' : 'bg-secondary/10';
const borderColor = isPrimary ? 'border-primary/20' : 'border-secondary/20';
return (
<div key={agent.id} className="md:col-span-6 glass-panel rounded-xl p-8 relative overflow-hidden group refractive-edge h-full flex flex-col">
<div className="flex items-center gap-4 mb-8">
<div className={`w-10 h-10 rounded-lg ${bgColor} flex items-center justify-center border ${borderColor}`}>
<span className={`material-symbols-outlined ${titleColor}`}>smart_toy</span>
</div>
<div className="flex-1">
<div className="flex justify-between items-start">
<div>
<h3 className="font-headline text-xl font-bold uppercase">{agent.role.replace(/_/g, ' ')} <span className={`${titleColor} text-sm ml-2 tracking-tighter`}>[{agent.id.toUpperCase()}]</span></h3>
<p className="font-mono text-[10px] text-slate-500 uppercase">Node ID: {agent.id}</p>
</div>
<div className="flex gap-2 items-center">
<ProviderToggle agent={agent} index={index} />
{agents.length > 1 && (
<button onClick={() => removeAgent(index)} className="text-error hover:text-red-400 p-1 bg-surface-container-highest rounded border border-white/5" title="Remove Agent">
<span className="material-symbols-outlined text-[14px]">delete</span>
</button>
)}
</div>
</div>
</div>
</div>
<div className="space-y-8 flex-1">
{agent.provider === 'ollama' ? (
<OllamaModelPicker
value={agent.model}
onChange={v => handleUpdateAgent(index, a => ({ ...a, model: v }))}
accentColor={accentColor}
/>
) : agent.provider === 'hf' ? (
<HFModelPicker
value={agent.hfModel}
onChange={v => handleUpdateAgent(index, a => ({ ...a, hfModel: v }))}
accentColor={accentColor}
/>
) : (
<div className="space-y-4">
<div className="space-y-2">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">OpenAI API Key</label>
<input
className={isPrimary ? 'w-full bg-transparent border-0 border-b border-primary/30 py-2 font-mono text-on-surface focus:outline-none focus:border-primary transition-all placeholder:text-slate-700' : 'w-full bg-transparent border-0 border-b border-secondary/30 py-2 font-mono text-on-surface focus:outline-none focus:border-secondary transition-all placeholder:text-slate-700'}
placeholder="sk-..."
type="password"
value={openaiKey}
onChange={e => setOpenaiKey(e.target.value)}
/>
</div>
<div className="space-y-2">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">OpenAI Model Name</label>
<input
className={isPrimary ? 'w-full bg-transparent border-0 border-b border-primary/30 py-2 font-mono text-on-surface focus:outline-none focus:border-primary transition-all placeholder:text-slate-700' : 'w-full bg-transparent border-0 border-b border-secondary/30 py-2 font-mono text-on-surface focus:outline-none focus:border-secondary transition-all placeholder:text-slate-700'}
placeholder="gpt-4o"
type="text"
value={agent.openaiModel}
onChange={e => handleUpdateAgent(index, a => ({ ...a, openaiModel: e.target.value }))}
/>
</div>
</div>
)}
<div className="space-y-4">
<div className="flex justify-between items-center">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Neural Temperature</label>
<span className={`font-mono text-xs ${titleColor} font-bold`}>{agent.temp.toFixed(1)}</span>
</div>
<input
className="w-full h-1.5 rounded-lg appearance-none cursor-pointer bg-surface-container-highest"
max="1" min="0" step="0.1" type="range"
value={agent.temp}
onChange={e => handleUpdateAgent(index, a => ({ ...a, temp: parseFloat(e.target.value) }))}
/>
</div>
<div className="space-y-4 pt-4 border-t border-white/5">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Operational Role</label>
<select
className={isPrimary ? 'w-full bg-surface-container-lowest border-b border-primary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-primary transition-all cursor-pointer' : 'w-full bg-surface-container-lowest border-b border-secondary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-secondary transition-all cursor-pointer'}
value={agent.role}
onChange={e => handleUpdateAgent(index, a => ({ ...a, role: e.target.value }))}
>
{ROLES.map(r => <option key={r} value={r}>{r.replace(/_/g, ' ')}</option>)}
</select>
{agent.role === 'CUSTOM_ROLE' && (
<div className="space-y-4 pt-2 animate-in fade-in slide-in-from-top-2">
<div className="space-y-2">
<label className={`font-mono text-[9px] tracking-widest ${titleColor} uppercase`}>Custom Role Title</label>
<input
type="text"
placeholder="e.g. DATABASE NINJA"
value={agent.customRoleName}
onChange={e => handleUpdateAgent(index, a => ({ ...a, customRoleName: e.target.value }))}
className={isPrimary ? 'w-full bg-transparent border-0 border-b border-primary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-primary transition-all placeholder:text-slate-700' : 'w-full bg-transparent border-0 border-b border-secondary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-secondary transition-all placeholder:text-slate-700'}
/>
</div>
<div className="space-y-2">
<label className={`font-mono text-[9px] tracking-widest ${titleColor} uppercase flex justify-between`}>
<span>System Prompt Configuration</span>
</label>
<textarea
placeholder="You are an elite expert... Your objective is to..."
value={agent.customPrompt}
onChange={e => handleUpdateAgent(index, a => ({ ...a, customPrompt: e.target.value }))}
className={`w-full h-32 bg-surface-container-lowest ${titleColor} font-mono text-[10px] p-3 rounded border border-white/5 focus:outline-none leading-relaxed`}
/>
</div>
</div>
)}
</div>
</div>
</div>
);
})}
{(
<div className="md:col-span-12 flex justify-center mt-4">
<button onClick={addAgent} className="flex items-center gap-2 px-8 py-3 rounded-xl border border-dashed border-outline-variant/30 text-outline-variant font-mono text-xs uppercase hover:bg-surface-container-highest hover:text-white transition-all">
<span className="material-symbols-outlined text-[16px]">add</span>
<span>Add Agent Node</span>
</button>
</div>
)}
{/* Execution Environment */}
<div className="md:col-span-12 glass-panel rounded-xl p-8 refractive-edge">
<div className="flex items-center gap-4 mb-6">
<div className="w-10 h-10 rounded-lg bg-tertiary/10 flex items-center justify-center border border-tertiary/20">
<span className="material-symbols-outlined text-tertiary">lan</span>
</div>
<div>
<h3 className="font-headline text-xl font-bold uppercase tracking-tight">Execution Environment</h3>
<p className="font-mono text-[10px] text-slate-500 uppercase">Agent Tool Execution Mode</p>
</div>
</div>
{/* Mode Toggle */}
<div className="flex gap-2 p-1 bg-surface-container-highest rounded-lg border border-white/5 mb-6 max-w-sm">
{[{ id: 'simulated', label: 'Simulated', icon: 'psychology' }, { id: 'ssh', label: 'SSH Lab Node', icon: 'terminal' }].map(m => (
<button
key={m.id}
id={`exec-mode-${m.id}`}
onClick={() => setExecutionMode(m.id)}
title={m.id === 'ssh' ? 'Connects to a live Linux server via SSH to execute raw commands (Destructive)' : 'Uses Sandbox constraints'}
className={`flex-1 flex items-center justify-center gap-2 py-2 px-3 rounded text-[11px] font-mono font-bold uppercase transition-all ${executionMode === m.id ? 'bg-tertiary text-black' : 'text-outline-variant hover:text-white'
}`}
>
<span className="material-symbols-outlined text-sm">{m.icon}</span>
{m.label}
</button>
))}
</div>
{executionMode === 'simulated' && (
<p className="font-mono text-xs text-slate-500">
Agents use pre-scripted scenario data (clue maps). No real system is touched.
</p>
)}
{executionMode === 'ssh' && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-1">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Host / IP</label>
<input id="ssh-host" type="text" placeholder="192.168.1.100"
className="w-full bg-transparent border-0 border-b border-tertiary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-tertiary transition-all placeholder:text-slate-700"
value={sshConfig.host} onChange={e => setSshConfig(s => ({ ...s, host: e.target.value }))} />
</div>
<div className="space-y-1">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Port</label>
<input id="ssh-port" type="number" placeholder="22"
className="w-full bg-transparent border-0 border-b border-tertiary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-tertiary transition-all placeholder:text-slate-700"
value={sshConfig.port} onChange={e => setSshConfig(s => ({ ...s, port: parseInt(e.target.value) || 22 }))} />
</div>
<div className="space-y-1">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Username</label>
<input id="ssh-user" type="text" placeholder="sandbox"
className="w-full bg-transparent border-0 border-b border-tertiary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-tertiary transition-all placeholder:text-slate-700"
value={sshConfig.user} onChange={e => setSshConfig(s => ({ ...s, user: e.target.value }))} />
</div>
<div className="space-y-1">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Password</label>
<input id="ssh-password" type="password" placeholder="••••••••"
className="w-full bg-transparent border-0 border-b border-tertiary/30 py-2 font-mono text-sm text-on-surface focus:outline-none focus:border-tertiary transition-all placeholder:text-slate-700"
value={sshConfig.password} onChange={e => setSshConfig(s => ({ ...s, password: e.target.value }))} />
</div>
<div className="md:col-span-2 flex items-center gap-4 pt-2">
<button
id="ssh-test-btn"
onClick={async () => {
setSshTestStatus('testing');
try {
const res = await fetch(`${config.API_BASE}/config/ssh-test`, { method: 'POST' });
const data = await res.json();
setSshTestStatus(data.success ? 'ok' : 'fail');
} catch { setSshTestStatus('fail'); }
setTimeout(() => setSshTestStatus(null), 4000);
}}
className="flex items-center gap-2 px-6 py-2 border border-tertiary/40 rounded text-tertiary font-mono text-xs uppercase hover:bg-tertiary/10 transition-all"
>
<span className="material-symbols-outlined text-sm">{sshTestStatus === 'testing' ? 'sync' : 'cable'}</span>
{sshTestStatus === 'testing' ? 'Testing...' : 'Test Connection'}
</button>
{sshTestStatus === 'ok' && <span className="font-mono text-xs text-success">✓ Connected successfully</span>}
{sshTestStatus === 'fail' && <span className="font-mono text-xs text-error">✗ Connection failed — check credentials</span>}
</div>
</div>
)}
</div>
{/* Environmental Parameters */}
<div className="md:col-span-8 glass-panel rounded-xl p-8 refractive-edge">
<div className="flex items-center gap-4 mb-8">
<div className="w-10 h-10 rounded-lg bg-surface-container-highest flex items-center justify-center border border-white/5">
<span className="material-symbols-outlined text-on-surface-variant">settings_suggest</span>
</div>
<h3 className="font-headline text-xl font-bold uppercase tracking-tight">Environmental Parameters</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
<div className="space-y-4">
<div className="flex justify-between items-center">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Max Inference Steps</label>
<div className="px-3 py-1 bg-primary/10 border border-primary/20 rounded font-mono text-sm text-primary font-bold min-w-[40px] text-center">{maxSteps}</div>
</div>
<input
className="w-full h-1.5 bg-surface-container-highest rounded-lg appearance-none cursor-pointer accent-primary"
max="16" min="4" step="1" type="range"
value={maxSteps}
onChange={e => setMaxSteps(parseInt(e.target.value))}
/>
</div>
<div className="space-y-4">
<label className="font-mono text-[10px] tracking-widest text-slate-400 uppercase">Complexity Level</label>
<select
className="w-full bg-surface-container-lowest border border-outline-variant/20 rounded-lg px-4 py-3 appearance-none font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all text-on-surface cursor-pointer"
value={complexity}
onChange={e => setComplexity(e.target.value)}
>
<option>LEVEL_01: NOMINAL</option>
<option>LEVEL_02: ADVERSARIAL</option>
<option>LEVEL_03: CHAOS_SIM</option>
<option>LEVEL_04: BLACK_BOX</option>
</select>
</div>
</div>
</div>
{/* Sync Status */}
<div className="md:col-span-4 h-full">
<div className="glass-panel rounded-xl p-8 border-l-2 border-primary refractive-edge h-full flex flex-col justify-center">
<h4 className="font-mono text-[10px] tracking-[0.2em] text-primary uppercase mb-4">Sync Status</h4>
<div className="space-y-2 font-mono text-[10px] text-slate-500 uppercase">
<p>Persistence: ACTIVE</p>
<p>Node: NY-SOC-04</p>
<p>Version: 2.0.4-STABLE</p>
</div>
</div>
</div>
</div>
<div className="mt-12 flex flex-col md:flex-row items-center justify-between gap-6 py-10 border-t border-white/5">
<div className="font-mono text-[10px] text-slate-500 uppercase tracking-tight">
System parameters are synchronized with NEXUS-CORE-API.
</div>
<div className="flex items-center gap-4">
<button
onClick={() => {
setAgents([{ id: 'agent_a', provider: 'ollama', model: '', temp: 0.7, role: 'INVESTIGATOR' }]);
setMaxSteps(12);
}}
className="px-8 py-3 bg-surface-container-high text-on-surface-variant font-headline font-bold text-sm tracking-widest rounded hover:bg-surface-container-highest hover:text-white transition-all uppercase"
>
Reset
</button>
<button
onClick={handleSave}
className={`px-10 py-3 font-headline font-bold text-sm tracking-[0.2em] rounded transition-all uppercase ${saved ? 'bg-tertiary/20 border border-tertiary text-tertiary' : 'bg-primary text-black hover:bg-primary/80'}`}
>
{saved ? '✓ Saved' : 'Save Changes'}
</button>
</div>
</div>
</div>
);
};
export default SettingsView;