Merge branch 'feature/settings-panel-overhaul'

This commit is contained in:
“BeeRad”
2026-03-21 16:07:24 +11:00
5 changed files with 347 additions and 375 deletions
+26
View File
@@ -66,6 +66,19 @@ html[data-theme="dark"] {
--rah-backdrop: rgba(0, 0, 0, 0.85); --rah-backdrop: rgba(0, 0, 0, 0.85);
--rah-shadow-modal: 0 8px 32px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05); --rah-shadow-modal: 0 8px 32px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05);
--rah-shadow-floating: 0 4px 12px rgba(0, 0, 0, 0.4); --rah-shadow-floating: 0 4px 12px rgba(0, 0, 0, 0.4);
--settings-bg: rgba(10, 10, 10, 0.95);
--settings-sidebar-bg: rgba(0, 0, 0, 0.3);
--settings-card-bg: rgba(255, 255, 255, 0.02);
--settings-code-bg: rgba(0, 0, 0, 0.24);
--settings-border: rgba(255, 255, 255, 0.06);
--settings-border-strong: rgba(255, 255, 255, 0.14);
--settings-text: #e5e7eb;
--settings-subtext: #d1d5db;
--settings-muted: #6b7280;
--settings-active-bg: rgba(255, 255, 255, 0.04);
--settings-active-border: rgba(255, 255, 255, 0.12);
--settings-chip-bg: rgba(255, 255, 255, 0.04);
--settings-danger: #f87171;
} }
html[data-theme="light"] { html[data-theme="light"] {
@@ -97,6 +110,19 @@ html[data-theme="light"] {
--rah-backdrop: rgba(0, 0, 0, 0.5); --rah-backdrop: rgba(0, 0, 0, 0.5);
--rah-shadow-modal: 0 10px 36px rgba(0, 0, 0, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.04); --rah-shadow-modal: 0 10px 36px rgba(0, 0, 0, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.04);
--rah-shadow-floating: 0 6px 20px rgba(0, 0, 0, 0.16); --rah-shadow-floating: 0 6px 20px rgba(0, 0, 0, 0.16);
--settings-bg: #ffffff;
--settings-sidebar-bg: #f5f5f5;
--settings-card-bg: #fafafa;
--settings-code-bg: #f3f4f6;
--settings-border: rgba(0, 0, 0, 0.08);
--settings-border-strong: rgba(0, 0, 0, 0.16);
--settings-text: #111111;
--settings-subtext: #333333;
--settings-muted: #6b7280;
--settings-active-bg: rgba(0, 0, 0, 0.06);
--settings-active-border: rgba(0, 0, 0, 0.08);
--settings-chip-bg: rgba(0, 0, 0, 0.03);
--settings-danger: #b91c1c;
} }
/* Modern Clean Design System - RA-H */ /* Modern Clean Design System - RA-H */
+25 -21
View File
@@ -37,8 +37,11 @@ export default function ApiKeysViewer() {
<span style={cardTitleStyle}>OpenAI API Key</span> <span style={cardTitleStyle}>OpenAI API Key</span>
<span style={{ <span style={{
fontSize: 12, fontSize: 12,
color: status === 'configured' ? '#22c55e' : color: status === 'checking'
status === 'checking' ? '#6b7280' : '#ef4444' ? 'var(--settings-muted)'
: status === 'configured'
? 'var(--settings-text)'
: 'var(--settings-danger)'
}}> }}>
{status === 'configured' ? 'Configured' : {status === 'configured' ? 'Configured' :
status === 'checking' ? 'Checking...' : 'Not configured'} status === 'checking' ? 'Checking...' : 'Not configured'}
@@ -52,7 +55,7 @@ export default function ApiKeysViewer() {
<div style={codeBlockStyle}> <div style={codeBlockStyle}>
<code>OPENAI_API_KEY=sk-your-key-here</code> <code>OPENAI_API_KEY=sk-your-key-here</code>
</div> </div>
<p style={{ margin: 0, fontSize: 12, color: '#6b7280' }}> <p style={{ margin: 0, fontSize: 12, color: 'var(--settings-muted)' }}>
Restart the app after changing the key. Restart the app after changing the key.
</p> </p>
</div> </div>
@@ -84,8 +87,8 @@ const containerStyle: CSSProperties = {
}; };
const featuresBoxStyle: CSSProperties = { const featuresBoxStyle: CSSProperties = {
background: 'rgba(34, 197, 94, 0.08)', background: 'var(--settings-card-bg)',
border: '1px solid rgba(34, 197, 94, 0.2)', border: '1px solid var(--settings-border)',
borderRadius: 8, borderRadius: 8,
padding: 16, padding: 16,
marginBottom: 20, marginBottom: 20,
@@ -94,7 +97,7 @@ const featuresBoxStyle: CSSProperties = {
const featuresHeaderStyle: CSSProperties = { const featuresHeaderStyle: CSSProperties = {
fontSize: 13, fontSize: 13,
fontWeight: 500, fontWeight: 500,
color: '#22c55e', color: 'var(--settings-text)',
marginBottom: 8, marginBottom: 8,
}; };
@@ -102,20 +105,20 @@ const featuresListStyle: CSSProperties = {
margin: 0, margin: 0,
paddingLeft: 20, paddingLeft: 20,
fontSize: 13, fontSize: 13,
color: '#d1d5db', color: 'var(--settings-subtext)',
lineHeight: 1.6, lineHeight: 1.6,
}; };
const noteStyle: CSSProperties = { const noteStyle: CSSProperties = {
marginTop: 12, marginTop: 12,
fontSize: 12, fontSize: 12,
color: '#6b7280', color: 'var(--settings-muted)',
fontStyle: 'italic', fontStyle: 'italic',
}; };
const cardStyle: CSSProperties = { const cardStyle: CSSProperties = {
background: 'rgba(255, 255, 255, 0.02)', background: 'var(--settings-card-bg)',
border: '1px solid rgba(255, 255, 255, 0.06)', border: '1px solid var(--settings-border)',
borderRadius: 8, borderRadius: 8,
padding: 16, padding: 16,
marginBottom: 12, marginBottom: 12,
@@ -131,46 +134,47 @@ const cardHeaderStyle: CSSProperties = {
const cardTitleStyle: CSSProperties = { const cardTitleStyle: CSSProperties = {
fontSize: 13, fontSize: 13,
fontWeight: 500, fontWeight: 500,
color: '#e5e7eb', color: 'var(--settings-text)',
}; };
const instructionsStyle: CSSProperties = { const instructionsStyle: CSSProperties = {
fontSize: 13, fontSize: 13,
color: '#d1d5db', color: 'var(--settings-subtext)',
lineHeight: 1.5, lineHeight: 1.5,
}; };
const codeInlineStyle: CSSProperties = { const codeInlineStyle: CSSProperties = {
background: 'rgba(255, 255, 255, 0.08)', background: 'var(--settings-code-bg)',
padding: '2px 6px', padding: '2px 6px',
borderRadius: 4, borderRadius: 4,
fontSize: 12, fontSize: 12,
fontFamily: 'monospace', fontFamily: 'monospace',
color: '#22c55e', color: 'var(--settings-text)',
}; };
const codeBlockStyle: CSSProperties = { const codeBlockStyle: CSSProperties = {
background: 'rgba(0, 0, 0, 0.4)', background: 'var(--settings-code-bg)',
border: '1px solid rgba(255, 255, 255, 0.08)', border: '1px solid var(--settings-border)',
borderRadius: 6, borderRadius: 6,
padding: '10px 12px', padding: '10px 12px',
fontSize: 13, fontSize: 13,
fontFamily: 'monospace', fontFamily: 'monospace',
color: '#e5e7eb', color: 'var(--settings-text)',
marginBottom: 8, marginBottom: 8,
}; };
const helpStyle: CSSProperties = { const helpStyle: CSSProperties = {
fontSize: 12, fontSize: 12,
color: '#6b7280', color: 'var(--settings-muted)',
}; };
const linkStyle: CSSProperties = { const linkStyle: CSSProperties = {
color: '#22c55e', color: 'var(--settings-text)',
textDecoration: 'none', textDecoration: 'none',
background: 'transparent', background: 'transparent',
border: 'none', border: '1px solid var(--settings-border-strong)',
padding: 0, borderRadius: 6,
padding: '10px 14px',
cursor: 'pointer', cursor: 'pointer',
font: 'inherit', font: 'inherit',
}; };
+16 -15
View File
@@ -90,7 +90,7 @@ export default function ContextViewer() {
disabled={loadingSettings || saving} disabled={loadingSettings || saving}
style={{ style={{
...toggleStyle, ...toggleStyle,
background: enabled ? '#22c55e' : 'rgba(255, 255, 255, 0.1)', background: enabled ? 'var(--settings-active-bg)' : 'var(--settings-code-bg)',
}} }}
> >
<span style={{ <span style={{
@@ -131,25 +131,25 @@ export default function ContextViewer() {
} }
const containerStyle: CSSProperties = { padding: 24, height: '100%', overflow: 'auto' }; const containerStyle: CSSProperties = { padding: 24, height: '100%', overflow: 'auto' };
const descStyle: CSSProperties = { fontSize: 13, color: '#6b7280', marginBottom: 20, lineHeight: 1.5 }; const descStyle: CSSProperties = { fontSize: 13, color: 'var(--settings-muted)', marginBottom: 20, lineHeight: 1.5 };
const cardStyle: CSSProperties = { const cardStyle: CSSProperties = {
background: 'rgba(255, 255, 255, 0.02)', background: 'var(--settings-card-bg)',
border: '1px solid rgba(255, 255, 255, 0.06)', border: '1px solid var(--settings-border)',
borderRadius: 8, borderRadius: 8,
padding: 16, padding: 16,
marginBottom: 24, marginBottom: 24,
}; };
const labelStyle: CSSProperties = { fontSize: 13, fontWeight: 500, color: '#e5e7eb', marginBottom: 8 }; const labelStyle: CSSProperties = { fontSize: 13, fontWeight: 500, color: 'var(--settings-text)', marginBottom: 8 };
const subLabelStyle: CSSProperties = { fontSize: 12, color: '#6b7280' }; const subLabelStyle: CSSProperties = { fontSize: 12, color: 'var(--settings-muted)' };
const mutedStyle: CSSProperties = { fontSize: 13, color: '#6b7280' }; const mutedStyle: CSSProperties = { fontSize: 13, color: 'var(--settings-muted)' };
const toggleStyle: CSSProperties = { const toggleStyle: CSSProperties = {
width: 48, width: 48,
height: 26, height: 26,
borderRadius: 13, borderRadius: 13,
border: 'none', border: '1px solid var(--settings-border)',
cursor: 'pointer', cursor: 'pointer',
position: 'relative', position: 'relative',
transition: 'background 0.15s', transition: 'background 0.15s',
@@ -161,24 +161,25 @@ const toggleKnobStyle: CSSProperties = {
width: 18, width: 18,
height: 18, height: 18,
borderRadius: '50%', borderRadius: '50%',
background: '#fff', background: 'var(--settings-text)',
transition: 'left 0.15s', transition: 'left 0.15s',
}; };
const nodeCardStyle: CSSProperties = { const nodeCardStyle: CSSProperties = {
padding: '12px 14px', padding: '12px 14px',
background: 'rgba(255, 255, 255, 0.02)', background: 'var(--settings-card-bg)',
border: '1px solid rgba(255, 255, 255, 0.06)', border: '1px solid var(--settings-border)',
borderRadius: 6, borderRadius: 6,
}; };
const nodeTitleStyle: CSSProperties = { fontSize: 13, fontWeight: 500, color: '#e5e7eb' }; const nodeTitleStyle: CSSProperties = { fontSize: 13, fontWeight: 500, color: 'var(--settings-text)' };
const edgeCountStyle: CSSProperties = { fontSize: 12, color: '#22c55e' }; const edgeCountStyle: CSSProperties = { fontSize: 12, color: 'var(--settings-muted)' };
const dimTagStyle: CSSProperties = { const dimTagStyle: CSSProperties = {
padding: '2px 8px', padding: '2px 8px',
borderRadius: 4, borderRadius: 4,
fontSize: 11, fontSize: 11,
background: 'rgba(34, 197, 94, 0.1)', background: 'var(--settings-chip-bg)',
color: '#22c55e', color: 'var(--settings-subtext)',
border: '1px solid var(--settings-border)',
}; };
+106 -59
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState, type CSSProperties } from 'react';
interface McpStatus { interface McpStatus {
enabled: boolean; enabled: boolean;
@@ -64,30 +64,21 @@ export default function ExternalAgentsPanel() {
}, [connectorUrl]); }, [connectorUrl]);
return ( return (
<div style={{ padding: '32px', color: '#e2e8f0', overflowY: 'auto' }}> <div style={containerStyle}>
<h2 style={{ fontSize: '20px', fontWeight: 600, marginBottom: '8px' }}>External Agents</h2> <p style={descStyle}>
<p style={{ color: '#94a3b8', marginBottom: '24px', lineHeight: 1.5 }}>
Connect Claude, ChatGPT, Gemini, or any MCP-compatible assistant to your local RA-H database. Connect Claude, ChatGPT, Gemini, or any MCP-compatible assistant to your local RA-H database.
Everything stays on devicetools simply call this connector to add or search nodes. Everything stays on device. External tools only talk to the local MCP connector you expose here.
</p> </p>
<div <div style={cardStyle}>
style={{
border: '1px solid #1f2937',
borderRadius: '10px',
padding: '20px',
marginBottom: '24px',
background: '#090909'
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div> <div>
<div style={{ fontSize: '14px', color: '#94a3b8' }}>Connector URL</div> <div style={labelStyle}>Connector URL</div>
<div style={{ fontSize: '18px', color: connectorUrl ? '#fff' : '#64748b', marginTop: '4px' }}> <div style={{ ...statusTextStyle, color: connectorUrl ? 'var(--settings-text)' : 'var(--settings-muted)' }}>
{loading ? 'Loading…' : connectorUrl ?? 'Unavailable (MCP server not running)'} {loading ? 'Loading…' : connectorUrl ?? 'Unavailable (MCP server not running)'}
</div> </div>
{status.last_updated && ( {status.last_updated && (
<div style={{ fontSize: '12px', color: '#475569', marginTop: '6px' }}> <div style={microcopyStyle}>
Updated {new Date(status.last_updated).toLocaleTimeString()} Updated {new Date(status.last_updated).toLocaleTimeString()}
</div> </div>
)} )}
@@ -97,36 +88,24 @@ export default function ExternalAgentsPanel() {
onClick={handleCopy} onClick={handleCopy}
disabled={!connectorUrl} disabled={!connectorUrl}
style={{ style={{
background: connectorUrl ? '#22c55e' : '#1f2937', ...buttonStyle,
color: connectorUrl ? '#000' : '#475569',
border: 'none',
borderRadius: '6px',
padding: '10px 16px',
cursor: connectorUrl ? 'pointer' : 'not-allowed', cursor: connectorUrl ? 'pointer' : 'not-allowed',
fontWeight: 600 opacity: connectorUrl ? 1 : 0.4,
}} }}
> >
{copyState === 'copied' ? 'Copied' : 'Copy URL'} {copyState === 'copied' ? 'Copied' : 'Copy URL'}
</button> </button>
</div> </div>
{status.last_error && ( {status.last_error && (
<div style={{ marginTop: '12px', fontSize: '13px', color: '#fbbf24' }}> <div style={{ ...helperTextStyle, color: 'var(--settings-danger)', marginTop: 12 }}>
{status.last_error} {status.last_error}
</div> </div>
)} )}
</div> </div>
<div <div style={cardStyle}>
style={{ <div style={labelStyle}>How to use in Claude or ChatGPT</div>
border: '1px solid #1f2937', <ol style={stepsStyle}>
borderRadius: '10px',
padding: '20px',
marginBottom: '24px',
background: '#0c111d'
}}
>
<h3 style={{ marginBottom: '12px', fontSize: '16px', fontWeight: 600 }}>How to use in Claude or ChatGPT</h3>
<ol style={{ paddingLeft: '20px', lineHeight: 1.6, color: '#cbd5f5' }}>
<li>Open the MCP / connectors settings in your assistant.</li> <li>Open the MCP / connectors settings in your assistant.</li>
<li>Select Add connector choose HTTP paste the URL above.</li> <li>Select Add connector choose HTTP paste the URL above.</li>
<li>Give the connector a friendly name (e.g., RA-H).</li> <li>Give the connector a friendly name (e.g., RA-H).</li>
@@ -134,23 +113,15 @@ export default function ExternalAgentsPanel() {
</ol> </ol>
</div> </div>
<div <div style={cardStyle}>
style={{ <div style={helperTextStyle}>
border: '1px solid #3f1d1d',
borderRadius: '10px',
padding: '16px',
background: '#170e0e',
color: '#fca5a5',
marginBottom: '24px',
lineHeight: 1.5
}}
>
External agents can edit your local graph. Only enable trusted connectors and monitor their output. External agents can edit your local graph. Only enable trusted connectors and monitor their output.
Disconnect the connector or close RA-H if anything unexpected happens. Disconnect the connector or close RA-H if anything unexpected happens.
</div> </div>
</div>
{error && ( {error && (
<div style={{ color: '#f87171', marginBottom: '16px', fontSize: '14px' }}>{error}</div> <div style={{ ...helperTextStyle, color: 'var(--settings-danger)', marginBottom: 16 }}>{error}</div>
)} )}
<div style={{ display: 'grid', gap: '16px' }}> <div style={{ display: 'grid', gap: '16px' }}>
@@ -173,16 +144,92 @@ export default function ExternalAgentsPanel() {
function HelperCard({ title, body }: { title: string; body: string }) { function HelperCard({ title, body }: { title: string; body: string }) {
return ( return (
<div <div style={helperCardStyle}>
style={{ <div style={helperCardTitleStyle}>{title}</div>
border: '1px solid #1f2937', <div style={helperCardBodyStyle}>{body}</div>
borderRadius: '8px',
padding: '14px',
background: '#0f172a'
}}
>
<div style={{ fontWeight: 600, marginBottom: '6px', color: '#f1f5f9' }}>{title}</div>
<div style={{ color: '#cbd5f5', fontSize: '14px', lineHeight: 1.5 }}>{body}</div>
</div> </div>
); );
} }
const containerStyle: CSSProperties = {
padding: 24,
height: '100%',
overflow: 'auto',
};
const descStyle: CSSProperties = {
fontSize: 13,
color: 'var(--settings-muted)',
marginBottom: 20,
lineHeight: 1.5,
};
const cardStyle: CSSProperties = {
background: 'var(--settings-card-bg)',
border: '1px solid var(--settings-border)',
borderRadius: 8,
padding: 16,
marginBottom: 16,
};
const labelStyle: CSSProperties = {
fontSize: 12,
fontWeight: 500,
color: 'var(--settings-text)',
marginBottom: 8,
};
const statusTextStyle: CSSProperties = {
fontSize: 13,
marginTop: 4,
};
const microcopyStyle: CSSProperties = {
fontSize: 11,
color: 'var(--settings-muted)',
marginTop: 6,
};
const buttonStyle: CSSProperties = {
padding: '10px 16px',
background: 'transparent',
color: 'var(--settings-text)',
border: '1px solid var(--settings-border-strong)',
borderRadius: 6,
fontSize: 12,
fontWeight: 500,
};
const stepsStyle: CSSProperties = {
paddingLeft: 20,
lineHeight: 1.6,
color: 'var(--settings-subtext)',
fontSize: 13,
margin: 0,
};
const helperTextStyle: CSSProperties = {
fontSize: 12,
color: 'var(--settings-subtext)',
lineHeight: 1.5,
};
const helperCardStyle: CSSProperties = {
border: '1px solid var(--settings-border)',
borderRadius: 8,
padding: 14,
background: 'var(--settings-card-bg)',
};
const helperCardTitleStyle: CSSProperties = {
fontWeight: 600,
marginBottom: 6,
color: 'var(--settings-text)',
fontSize: 13,
};
const helperCardBodyStyle: CSSProperties = {
color: 'var(--settings-subtext)',
fontSize: 13,
lineHeight: 1.5,
};
+166 -272
View File
@@ -1,22 +1,12 @@
"use client"; "use client";
import { useEffect, useState } from 'react'; import { useEffect, useState, type CSSProperties } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import LogsViewer from './LogsViewer';
import ToolsViewer from './ToolsViewer';
import ApiKeysViewer from './ApiKeysViewer'; import ApiKeysViewer from './ApiKeysViewer';
import DatabaseViewer from './DatabaseViewer';
import ExternalAgentsPanel from './ExternalAgentsPanel'; import ExternalAgentsPanel from './ExternalAgentsPanel';
import ContextViewer from './ContextViewer'; import ContextViewer from './ContextViewer';
import SkillsViewer from './GuidesViewer';
export type SettingsTab = export type SettingsTab = 'apikeys' | 'context' | 'agents';
| 'logs'
| 'tools'
| 'guides'
| 'apikeys'
| 'database'
| 'context'
| 'agents';
interface SettingsModalProps { interface SettingsModalProps {
isOpen: boolean; isOpen: boolean;
@@ -24,17 +14,26 @@ interface SettingsModalProps {
initialTab?: SettingsTab; initialTab?: SettingsTab;
} }
type TabType = SettingsTab; const DEFAULT_TAB: SettingsTab = 'apikeys';
const TAB_ORDER: SettingsTab[] = ['apikeys', 'context', 'agents'];
const TAB_LABELS: Record<SettingsTab, string> = {
apikeys: 'API Keys',
context: 'Context',
agents: 'Agents',
};
function isSettingsTab(value: unknown): value is SettingsTab {
return typeof value === 'string' && TAB_ORDER.includes(value as SettingsTab);
}
export default function SettingsModal({ isOpen, onClose, initialTab }: SettingsModalProps) { export default function SettingsModal({ isOpen, onClose, initialTab }: SettingsModalProps) {
const [activeTab, setActiveTab] = useState<TabType>('logs'); const [activeTab, setActiveTab] = useState<SettingsTab>(DEFAULT_TAB);
useEffect(() => { useEffect(() => {
if (!isOpen) return; if (!isOpen) return;
const handleEscape = (e: KeyboardEvent) => { const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') { if (e.key === 'Escape') onClose();
onClose();
}
}; };
window.addEventListener('keydown', handleEscape); window.addEventListener('keydown', handleEscape);
@@ -42,271 +41,55 @@ export default function SettingsModal({ isOpen, onClose, initialTab }: SettingsM
}, [isOpen, onClose]); }, [isOpen, onClose]);
useEffect(() => { useEffect(() => {
if (!isOpen || !initialTab) return; if (!isOpen) return;
setActiveTab(initialTab); setActiveTab(isSettingsTab(initialTab) ? initialTab : DEFAULT_TAB);
}, [initialTab, isOpen]); }, [initialTab, isOpen]);
if (!isOpen) return null; if (!isOpen) return null;
const modalContent = ( return createPortal(
<div style={backdropStyle} onClick={onClose}>
<div style={modalStyle} onClick={(e) => e.stopPropagation()}>
<div style={sidebarStyle}>
<div style={logoStyle}>Settings</div>
<nav style={navStyle}>
{TAB_ORDER.map((tab) => (
<div <div
key={tab}
onClick={() => setActiveTab(tab)}
style={{ style={{
position: 'fixed', ...navItemStyle,
top: 0, color: activeTab === tab ? 'var(--settings-text)' : 'var(--settings-muted)',
left: 0, background: activeTab === tab ? 'var(--settings-active-bg)' : 'transparent',
right: 0, border: activeTab === tab ? '1px solid var(--settings-active-border)' : '1px solid transparent',
bottom: 0,
background: 'rgba(0, 0, 0, 0.8)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000
}}
onClick={onClose}
>
<div
style={{
width: '80vw',
height: '85vh',
background: 'var(--rah-bg-surface)',
border: '1px solid var(--rah-border-strong)',
borderRadius: '8px',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.6)',
display: 'flex',
overflow: 'hidden'
}}
onClick={(e) => e.stopPropagation()}
>
{/* Sidebar */}
<div
style={{
width: '20%',
background: 'var(--rah-bg-base)',
borderRight: '1px solid var(--rah-border-strong)',
display: 'flex',
flexDirection: 'column',
padding: '24px 0'
}} }}
> >
<div {TAB_LABELS[tab]}
style={{
padding: '0 24px',
marginBottom: '24px',
fontSize: '18px',
fontWeight: '600',
color: 'var(--rah-text-active)'
}}
>
Settings
</div>
<nav>
<div
onClick={() => setActiveTab('logs')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'logs' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: activeTab === 'logs' ? '#1a3a2a' : 'transparent', // semantic active state kept
borderLeft: activeTab === 'logs' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
Logs
</div>
<div
onClick={() => setActiveTab('tools')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'tools' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: activeTab === 'tools' ? '#1a3a2a' : 'transparent',
borderLeft: activeTab === 'tools' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
Tools
</div>
<div
onClick={() => setActiveTab('guides')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'guides' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: activeTab === 'guides' ? '#1a3a2a' : 'transparent',
borderLeft: activeTab === 'guides' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
Skills
</div>
<div
onClick={() => setActiveTab('apikeys')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'apikeys' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: activeTab === 'apikeys' ? '#1a3a2a' : 'transparent',
borderLeft: activeTab === 'apikeys' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
API Keys
</div>
<div
onClick={() => setActiveTab('database')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'database' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: activeTab === 'database' ? '#1a3a2a' : 'transparent',
borderLeft: activeTab === 'database' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
Database
</div>
<div
onClick={() => setActiveTab('context')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'context' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: activeTab === 'context' ? '#1a3a2a' : 'transparent',
borderLeft: activeTab === 'context' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
Context
</div>
<div
onClick={() => setActiveTab('agents')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'agents' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: activeTab === 'agents' ? '#1a3a2a' : 'transparent',
borderLeft: activeTab === 'agents' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
External Agents
</div>
<div
style={{
padding: '12px 24px',
fontSize: '14px',
color: 'var(--rah-text-muted)',
opacity: 0.4,
cursor: 'not-allowed'
}}
>
Backups
</div>
<div
style={{
padding: '12px 24px',
fontSize: '14px',
color: 'var(--rah-text-muted)',
opacity: 0.4,
cursor: 'not-allowed'
}}
>
Preferences
</div> </div>
))}
</nav> </nav>
<div <div style={footerStyle}>
style={{ <div style={footerLabelStyle}>Local Mode</div>
marginTop: 'auto', <p style={footerTextStyle}>
padding: '24px', This build runs entirely on your machine. Add your API key here to unlock descriptions,
borderTop: '1px solid var(--rah-border)', embeddings, and agent-assisted workflows.
display: 'flex',
flexDirection: 'column',
gap: '12px'
}}
>
<div
style={{
fontSize: '11px',
fontWeight: 600,
letterSpacing: '0.08em',
textTransform: 'uppercase',
color: '#64748b'
}}
>
Local Mode
</div>
<p
style={{
fontSize: '13px',
color: '#94a3b8',
margin: 0,
lineHeight: 1.5
}}
>
This open-source build runs entirely on your machine. Add keys via the API Keys tab to unlock every agent.
</p> </p>
</div> </div>
</div> </div>
{/* Content Area */} <div style={contentStyle}>
<div <div style={headerStyle}>
style={{ <h2 style={titleStyle}>{TAB_LABELS[activeTab]}</h2>
flex: 1,
display: 'flex',
flexDirection: 'column'
}}
>
{/* Header */}
<div
style={{
padding: '16px 24px',
borderBottom: '1px solid var(--rah-border-strong)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<h2
style={{
margin: 0,
fontSize: '16px',
fontWeight: '600',
color: 'var(--rah-text-active)'
}}
>
{activeTab === 'logs' && 'System Logs'}
{activeTab === 'tools' && 'Tools'}
{activeTab === 'guides' && 'Skills'}
{activeTab === 'apikeys' && 'API Keys'}
{activeTab === 'database' && 'Knowledge Database'}
{activeTab === 'context' && 'Auto-Context'}
{activeTab === 'agents' && 'External Agents'}
</h2>
<button <button
onClick={onClose} onClick={onClose}
style={{ style={closeBtnStyle}
background: 'transparent',
border: 'none',
color: 'var(--rah-text-muted)',
cursor: 'pointer',
fontSize: '24px',
lineHeight: 1,
padding: '4px 8px',
transition: 'color 0.2s'
}}
onMouseEnter={(e) => { onMouseEnter={(e) => {
e.currentTarget.style.color = 'var(--rah-text-active)'; e.currentTarget.style.color = 'var(--settings-text)';
}} }}
onMouseLeave={(e) => { onMouseLeave={(e) => {
e.currentTarget.style.color = 'var(--rah-text-muted)'; e.currentTarget.style.color = 'var(--settings-muted)';
}} }}
title="Close (ESC)" title="Close (ESC)"
> >
@@ -314,20 +97,131 @@ export default function SettingsModal({ isOpen, onClose, initialTab }: SettingsM
</button> </button>
</div> </div>
{/* Content */} <div style={contentAreaStyle}>
<div style={{ flex: 1, overflow: 'hidden' }}>
{activeTab === 'logs' && <LogsViewer key={isOpen ? 'open' : 'closed'} />}
{activeTab === 'tools' && <ToolsViewer />}
{activeTab === 'guides' && <SkillsViewer />}
{activeTab === 'apikeys' && <ApiKeysViewer />} {activeTab === 'apikeys' && <ApiKeysViewer />}
{activeTab === 'database' && <DatabaseViewer />}
{activeTab === 'context' && <ContextViewer />} {activeTab === 'context' && <ContextViewer />}
{activeTab === 'agents' && <ExternalAgentsPanel />} {activeTab === 'agents' && <ExternalAgentsPanel />}
</div> </div>
</div> </div>
</div> </div>
</div> </div>,
document.body
); );
return createPortal(modalContent, document.body);
} }
const backdropStyle: CSSProperties = {
position: 'fixed',
inset: 0,
background: 'var(--rah-backdrop)',
backdropFilter: 'blur(8px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
};
const modalStyle: CSSProperties = {
width: '88vw',
height: '90vh',
maxWidth: '1400px',
background: 'var(--settings-bg)',
border: '1px solid var(--settings-border)',
borderRadius: '12px',
boxShadow: 'var(--rah-shadow-modal)',
display: 'flex',
overflow: 'hidden',
};
const sidebarStyle: CSSProperties = {
width: '220px',
background: 'var(--settings-sidebar-bg)',
borderRight: '1px solid var(--settings-border)',
display: 'flex',
flexDirection: 'column',
padding: '20px 0',
};
const logoStyle: CSSProperties = {
padding: '0 20px 20px',
fontSize: '15px',
fontWeight: 600,
color: 'var(--settings-text)',
};
const navStyle: CSSProperties = {
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: '6px',
padding: '0 12px',
};
const navItemStyle: CSSProperties = {
padding: '10px 12px',
fontSize: '13px',
cursor: 'pointer',
transition: 'all 0.15s ease',
borderRadius: '8px',
};
const footerStyle: CSSProperties = {
marginTop: 'auto',
padding: '16px 20px',
borderTop: '1px solid var(--settings-border)',
display: 'flex',
flexDirection: 'column',
gap: '8px',
};
const footerLabelStyle: CSSProperties = {
fontSize: '10px',
fontWeight: 600,
letterSpacing: '0.06em',
textTransform: 'uppercase',
color: 'var(--settings-muted)',
};
const footerTextStyle: CSSProperties = {
fontSize: '12px',
color: 'var(--settings-subtext)',
margin: 0,
lineHeight: 1.5,
};
const contentStyle: CSSProperties = {
flex: 1,
display: 'flex',
flexDirection: 'column',
minWidth: 0,
};
const headerStyle: CSSProperties = {
padding: '16px 24px',
borderBottom: '1px solid var(--settings-border)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
};
const titleStyle: CSSProperties = {
margin: 0,
fontSize: '14px',
fontWeight: 500,
color: 'var(--settings-text)',
};
const closeBtnStyle: CSSProperties = {
background: 'transparent',
border: 'none',
color: 'var(--settings-muted)',
cursor: 'pointer',
fontSize: '24px',
lineHeight: 1,
padding: '4px 8px',
transition: 'color 0.15s ease',
};
const contentAreaStyle: CSSProperties = {
flex: 1,
overflow: 'hidden',
};