Files
ra-h-os/src/components/settings/SettingsModal.tsx
T
“BeeRad”andClaude Opus 4.5 9b2db68751 chore: codebase audit — remove dead code, unused deps, clean configs
Phase 1: Delete 19 dead component/service/tool files (4,200+ lines)
Phase 2: Remove 13 unused npm packages (Radix UI, shadcn utilities, ytdl-core, etc.)
Phase 3: Database schema — drop agent_delegations, add performance indexes
Phase 4: Remove 12 dead types from database.ts/analytics.ts/helpers.ts
Phase 5: Strip shadcn/ui theme from tailwind config
Phase 6: Clean env vars, fix broken imports (LocalKeyGate, MapViewer)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 08:52:15 +11:00

345 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import LogsViewer from './LogsViewer';
import ToolsViewer from './ToolsViewer';
import WorkflowsViewer from './WorkflowsViewer';
import ApiKeysViewer from './ApiKeysViewer';
import DatabaseViewer from './DatabaseViewer';
import ExternalAgentsPanel from './ExternalAgentsPanel';
import ContextViewer from './ContextViewer';
import { apiKeyService } from '@/services/storage/apiKeys';
export type SettingsTab =
| 'logs'
| 'tools'
| 'workflows'
| 'apikeys'
| 'database'
| 'context'
| 'agents';
interface SettingsModalProps {
isOpen: boolean;
onClose: () => void;
initialTab?: SettingsTab;
}
type TabType = SettingsTab;
export default function SettingsModal({ isOpen, onClose, initialTab }: SettingsModalProps) {
// Default to API Keys tab if no keys are configured, otherwise logs
const getDefaultTab = (): TabType => {
if (typeof window !== 'undefined') {
const hasKeys = apiKeyService.getOpenAiKey() || apiKeyService.getAnthropicKey();
return hasKeys ? 'logs' : 'apikeys';
}
return 'logs';
};
const [activeTab, setActiveTab] = useState<TabType>(getDefaultTab());
useEffect(() => {
if (!isOpen) return;
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
window.addEventListener('keydown', handleEscape);
return () => window.removeEventListener('keydown', handleEscape);
}, [isOpen, onClose]);
useEffect(() => {
if (!isOpen || !initialTab) return;
setActiveTab(initialTab);
}, [initialTab, isOpen]);
if (!isOpen) return null;
const modalContent = (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
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: '#0f0f0f',
border: '1px solid #2a2a2a',
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: '#0a0a0a',
borderRight: '1px solid #2a2a2a',
display: 'flex',
flexDirection: 'column',
padding: '24px 0'
}}
>
<div
style={{
padding: '0 24px',
marginBottom: '24px',
fontSize: '18px',
fontWeight: '600',
color: '#fff'
}}
>
Settings
</div>
<nav>
<div
onClick={() => setActiveTab('logs')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'logs' ? '#fff' : '#888',
background: activeTab === 'logs' ? '#1a3a2a' : 'transparent',
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' ? '#fff' : '#888',
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('workflows')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'workflows' ? '#fff' : '#888',
background: activeTab === 'workflows' ? '#1a3a2a' : 'transparent',
borderLeft: activeTab === 'workflows' ? '3px solid #22c55e' : '3px solid transparent',
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
Workflows
</div>
<div
onClick={() => setActiveTab('apikeys')}
style={{
padding: '12px 24px',
fontSize: '14px',
color: activeTab === 'apikeys' ? '#fff' : '#888',
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' ? '#fff' : '#888',
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' ? '#fff' : '#888',
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' ? '#fff' : '#888',
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: '#888',
opacity: 0.4,
cursor: 'not-allowed'
}}
>
Backups
</div>
<div
style={{
padding: '12px 24px',
fontSize: '14px',
color: '#888',
opacity: 0.4,
cursor: 'not-allowed'
}}
>
Preferences
</div>
</nav>
<div
style={{
marginTop: 'auto',
padding: '24px',
borderTop: '1px solid #1f2937',
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>
</div>
</div>
{/* Content Area */}
<div
style={{
flex: 1,
display: 'flex',
flexDirection: 'column'
}}
>
{/* Header */}
<div
style={{
padding: '16px 24px',
borderBottom: '1px solid #2a2a2a',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<h2
style={{
margin: 0,
fontSize: '16px',
fontWeight: '600',
color: '#fff'
}}
>
{activeTab === 'logs' && 'System Logs'}
{activeTab === 'tools' && 'Tools'}
{activeTab === 'workflows' && 'Workflows'}
{activeTab === 'apikeys' && 'API Keys'}
{activeTab === 'database' && 'Knowledge Database'}
{activeTab === 'context' && 'Auto-Context'}
{activeTab === 'agents' && 'External Agents'}
</h2>
<button
onClick={onClose}
style={{
background: 'transparent',
border: 'none',
color: '#888',
cursor: 'pointer',
fontSize: '24px',
lineHeight: 1,
padding: '4px 8px',
transition: 'color 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#fff';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = '#888';
}}
title="Close (ESC)"
>
×
</button>
</div>
{/* Content */}
<div style={{ flex: 1, overflow: 'hidden' }}>
{activeTab === 'logs' && <LogsViewer key={isOpen ? 'open' : 'closed'} />}
{activeTab === 'tools' && <ToolsViewer />}
{activeTab === 'workflows' && <WorkflowsViewer />}
{activeTab === 'apikeys' && <ApiKeysViewer />}
{activeTab === 'database' && <DatabaseViewer />}
{activeTab === 'context' && <ContextViewer />}
{activeTab === 'agents' && <ExternalAgentsPanel />}
</div>
</div>
</div>
</div>
);
return createPortal(modalContent, document.body);
}