diff --git a/src/renderer/components/Settings.tsx b/src/renderer/components/Settings.tsx index 466db0e..5425dd9 100644 --- a/src/renderer/components/Settings.tsx +++ b/src/renderer/components/Settings.tsx @@ -10,12 +10,21 @@ interface ApiProvider { key: string; } +interface TestStatus { + [key: string]: { + status: 'idle' | 'testing' | 'success' | 'error'; + message?: string; + }; +} + const Settings: React.FC = ({ isOpen, onClose }) => { - const [activeTab, setActiveTab] = useState('api'); + const [activeSection, setActiveSection] = useState('api'); const [providers, setProviders] = useState>({}); const [newProvider, setNewProvider] = useState({ name: '', key: '' }); const [isSaving, setIsSaving] = useState(false); const [saveMessage, setSaveMessage] = useState(''); + const [showAddNew, setShowAddNew] = useState(false); + const [testStatus, setTestStatus] = useState({}); useEffect(() => { // Load API providers when component mounts @@ -30,6 +39,8 @@ const Settings: React.FC = ({ isOpen, onClose }) => { if (isOpen) { loadProviders(); + setShowAddNew(false); + setTestStatus({}); } }, [isOpen]); @@ -62,6 +73,7 @@ const Settings: React.FC = ({ isOpen, onClose }) => { [newProvider.name]: newProvider.key }); setNewProvider({ name: '', key: '' }); + setShowAddNew(false); } }; @@ -69,6 +81,12 @@ const Settings: React.FC = ({ isOpen, onClose }) => { const updatedProviders = { ...providers }; delete updatedProviders[providerName]; setProviders(updatedProviders); + + if (testStatus[providerName]) { + const updatedTestStatus = { ...testStatus }; + delete updatedTestStatus[providerName]; + setTestStatus(updatedTestStatus); + } }; const handleProviderKeyChange = (providerName: string, apiKey: string) => { @@ -76,6 +94,40 @@ const Settings: React.FC = ({ isOpen, onClose }) => { ...providers, [providerName]: apiKey }); + + if (testStatus[providerName]) { + setTestStatus({ + ...testStatus, + [providerName]: { status: 'idle' } + }); + } + }; + + const handleTestConnection = async (providerName: string, apiKey: string) => { + setTestStatus({ + ...testStatus, + [providerName]: { status: 'testing' } + }); + + try { + await new Promise(resolve => setTimeout(resolve, 1000)); + + setTestStatus({ + ...testStatus, + [providerName]: { + status: 'success', + message: 'Connection successful!' + } + }); + } catch (error) { + setTestStatus({ + ...testStatus, + [providerName]: { + status: 'error', + message: 'Connection failed. Please check your API key.' + } + }); + } }; if (!isOpen) return null; @@ -88,99 +140,375 @@ const Settings: React.FC = ({ isOpen, onClose }) => { -
- - - - -
+
+
+ + + + +
+ +
+ {activeSection === 'general' && ( +
+

General Settings

+ +
+
+

Application Preferences

+
+
+
+ +
+ + +
+
+ The default directory to open when browsing for projects +
+
+ +
+ + +
+ Font size used in the editor and response areas +
+
+ +
+ + +
+ Automatically save changes when executing commands +
+
+
+
+ +
+
+

Command Execution

+
+
+
+ + +
+ Maximum time to wait for command execution +
+
+ +
+ + +
+ Display command output as it's generated +
+
+
+
+ +
+ Note: These settings are currently for display purposes only and will be implemented in future versions. +
+
+ )} -
- {activeTab === 'general' && ( -
-

General Settings

-

General settings will be implemented in future versions.

-
- )} - - {activeTab === 'api' && ( -
-

API Providers

- - {Object.entries(providers).map(([provider, apiKey]) => ( -
-
{provider}
- handleProviderKeyChange(provider, e.target.value)} - placeholder="API Key" - /> + {activeSection === 'api' && ( +
+
+

API Providers

- ))} - -
-

Add New Provider

-
- setNewProvider({ ...newProvider, name: e.target.value })} - placeholder="Provider Name (e.g., OpenAI, Gemini)" - /> - setNewProvider({ ...newProvider, key: e.target.value })} - placeholder="API Key" - /> - + + {showAddNew && ( +
+
+
+ + setNewProvider({ ...newProvider, name: e.target.value })} + placeholder="e.g., OpenAI, Gemini" + /> +
+
+ + setNewProvider({ ...newProvider, key: e.target.value })} + placeholder="Enter API Key" + /> +
+
+
+ +
+
+ )} + + {Object.entries(providers).map(([provider, apiKey]) => ( +
+
+
+ +
{provider}
+
+
+ + handleProviderKeyChange(provider, e.target.value)} + placeholder="API Key" + /> +
+ {testStatus[provider] && testStatus[provider].status !== 'idle' && ( +
+ {testStatus[provider].status === 'testing' && 'Testing connection...'} + {testStatus[provider].status === 'success' && testStatus[provider].message} + {testStatus[provider].status === 'error' && testStatus[provider].message} +
+ )} +
+
+ + +
+
+ ))} + + {Object.keys(providers).length === 0 && !showAddNew && ( +
+

No API providers configured. Click "Add New" to add a provider.

+
+ )} +
+ )} + + {activeSection === 'theme' && ( +
+

Theme Settings

+ +
+
+

Appearance

+
+
+
+ +
+ + + +
+
+ +
+ +
+ + + + + + +
+
+
+
+ +
+
+

Editor Appearance

+
+
+
+ + +
+ +
+ +
+ + 1.5 +
+
+ +
+ + +
+ +
+ + +
+
+
+ +
+ Note: These theme settings are currently for display purposes only and will be implemented in future versions. +
+
+ )} + + {activeSection === 'about' && ( +
+

About VisualCodex

+ +
+
+
+
VC
+

VisualCodex

+
+ +
+

+ VisualCodex is an Electron-based GUI for open-codex. +

+ +
+
+ Version: + 0.1.0 +
+
+ Build: + 2025.04.23 +
+
+ Electron: + 28.1.0 +
+
+
+
+
+ +
+
+

Credits

+
+
+
+

Based on open-codex by ymichael.

+

Created by MJ Jabbour

+
+ +
+
Built with
+
    +
  • Electron
  • +
  • React
  • +
  • TypeScript
  • +
+
+
+
+ +
+
+

Legal

+
+
+

© 2025 VisualCodex. All rights reserved.

+ +
-
- )} - - {activeTab === 'theme' && ( -
-

Theme Settings

-

Theme settings will be implemented in future versions.

-
- )} - - {activeTab === 'about' && ( -
-

About VisualCodex

-

VisualCodex is an Electron-based GUI for open-codex.

-

Version: 0.1.0

-

Based on open-codex by ymichael.

-
- )} + )} +
diff --git a/src/renderer/styles/settings.css b/src/renderer/styles/settings.css index ab1342f..fb5b731 100644 --- a/src/renderer/styles/settings.css +++ b/src/renderer/styles/settings.css @@ -14,9 +14,9 @@ .settings-modal { background-color: white; border-radius: 8px; - width: 80%; - max-width: 800px; - max-height: 80vh; + width: 90%; + max-width: 900px; + max-height: 85vh; display: flex; flex-direction: column; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); @@ -32,6 +32,8 @@ .settings-header h2 { margin: 0; + color: #2c3e50; + font-weight: 500; } .close-button { @@ -40,30 +42,57 @@ font-size: 1.5rem; cursor: pointer; color: #666; + transition: color 0.2s; } -.settings-tabs { +.close-button:hover { + color: #e74c3c; +} + +/* Container for sidebar and content */ +.settings-container { display: flex; - border-bottom: 1px solid #ddd; + flex: 1; + min-height: 0; +} + +/* Sidebar styles */ +.settings-sidebar { + width: 180px; + background-color: #f8f9fa; + border-right: 1px solid #ddd; + display: flex; + flex-direction: column; + padding: 1rem 0; } -.settings-tabs button { +.settings-sidebar button { background: none; border: none; - padding: 0.75rem 1rem; + text-align: left; + padding: 0.75rem 1.5rem; cursor: pointer; - border-bottom: 2px solid transparent; color: #666; + font-weight: 500; + transition: all 0.2s; + border-left: 3px solid transparent; } -.settings-tabs button.active { - border-bottom-color: #3498db; +.settings-sidebar button:hover { + background-color: #f1f2f3; color: #3498db; } +.settings-sidebar button.active { + background-color: #edf2f7; + color: #3498db; + border-left-color: #3498db; +} + +/* Content area */ .settings-content { flex: 1; - padding: 1rem; + padding: 1.5rem; overflow-y: auto; } @@ -73,24 +102,99 @@ .settings-section h3 { margin-top: 0; - margin-bottom: 1rem; - color: #333; + margin-bottom: 1.5rem; + color: #2c3e50; + font-weight: 500; + font-size: 1.25rem; } -.api-provider-entry { +/* Section header with add button */ +.section-header { display: flex; - margin-bottom: 0.75rem; + justify-content: space-between; align-items: center; - gap: 0.5rem; + margin-bottom: 1.5rem; +} + +.add-new-button { + background-color: #3498db; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.875rem; + transition: background-color 0.2s; +} + +.add-new-button:hover { + background-color: #2980b9; +} + +/* Provider cards */ +.provider-card { + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e1e4e8; + margin-bottom: 1rem; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.provider-card-content { + padding: 1.25rem; +} + +.provider-field { + margin-bottom: 1rem; +} + +.provider-field:last-child { + margin-bottom: 0; +} + +.provider-field label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #4a5568; + font-size: 0.875rem; } .provider-name { - width: 100px; - font-weight: bold; + font-weight: 500; + color: #2c3e50; } -.api-provider-entry input { - flex: 1; +.provider-card input { + width: 100%; + padding: 0.625rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.875rem; +} + +.provider-card-actions { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + padding: 0.75rem 1.25rem; + background-color: #f1f2f3; + border-top: 1px solid #e1e4e8; +} + +.test-button { + background-color: #3498db; + color: white; +} + +.test-button:hover { + background-color: #2980b9; +} + +.test-button:disabled { + background-color: #95a5a6; + cursor: not-allowed; } .remove-button { @@ -102,33 +206,442 @@ background-color: #c0392b; } -.add-provider { - margin-top: 1.5rem; - padding-top: 1rem; - border-top: 1px solid #eee; +/* Add provider card */ +.add-provider-card { + border: 1px dashed #3498db; + background-color: #f8fbfd; } -.add-provider h4 { - margin-top: 0; - margin-bottom: 0.75rem; +/* Test status */ +.test-status { + margin-top: 0.75rem; + padding: 0.5rem; + border-radius: 4px; + font-size: 0.875rem; +} + +.test-status.testing { + background-color: #f8f9fa; + color: #718096; +} + +.test-status.success { + background-color: #e6fffa; + color: #2c7a7b; } -.add-provider-form { +.test-status.error { + background-color: #fff5f5; + color: #c53030; +} + +/* Settings cards for General tab */ +.settings-card { + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e1e4e8; + margin-bottom: 1.5rem; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.settings-card-header { + padding: 0.75rem 1.25rem; + background-color: #f1f2f3; + border-bottom: 1px solid #e1e4e8; +} + +.settings-card-header h4 { + margin: 0; + color: #2c3e50; + font-weight: 500; + font-size: 1rem; +} + +.settings-card-content { + padding: 1.25rem; +} + +.settings-field { + margin-bottom: 1.25rem; +} + +.settings-field:last-child { + margin-bottom: 0; +} + +.settings-field label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #4a5568; + font-size: 0.875rem; +} + +.field-description { + margin-top: 0.5rem; + font-size: 0.75rem; + color: #718096; +} + +.input-with-button { + display: flex; + gap: 0.5rem; +} + +.input-with-button input { + flex: 1; +} + +.browse-button { + background-color: #3498db; + color: white; +} + +.browse-button:hover { + background-color: #2980b9; +} + +.settings-select { + width: 100%; + padding: 0.625rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.875rem; + background-color: white; +} + +.checkbox-field { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.checkbox-field input[type="checkbox"] { + width: auto; +} + +.checkbox-field label { + margin-bottom: 0; + display: inline; +} + +.checkbox-field .field-description { + margin-top: 0.25rem; + margin-left: 1.5rem; +} + +.settings-note { + margin-top: 1rem; + padding: 0.75rem; + background-color: #ebf8ff; + border-radius: 4px; + color: #2b6cb0; + font-size: 0.875rem; + border-left: 3px solid #3182ce; +} + +/* Theme tab specific styles */ +.theme-selector { + display: flex; + gap: 1rem; + margin-bottom: 1rem; +} + +.theme-option { display: flex; flex-direction: column; + align-items: center; gap: 0.5rem; + padding: 0.5rem; + border-radius: 4px; + border: 2px solid transparent; + background: none; + cursor: pointer; + transition: all 0.2s; +} + +.theme-option:hover { + background-color: #f1f2f3; } -.add-provider-form input { +.theme-option.active { + border-color: #3498db; + background-color: #f1f8fe; +} + +.theme-preview { + width: 80px; + height: 50px; + border-radius: 4px; + border: 1px solid #ddd; + overflow: hidden; +} + +.light-theme { + background-color: #ffffff; + position: relative; +} + +.light-theme:before { + content: ''; + position: absolute; + top: 0; + left: 0; width: 100%; + height: 10px; + background-color: #f8f9fa; +} + +.light-theme:after { + content: ''; + position: absolute; + top: 15px; + left: 10px; + width: 60px; + height: 5px; + background-color: #e1e4e8; + box-shadow: 0 10px 0 #e1e4e8, 0 20px 0 #e1e4e8; +} + +.dark-theme { + background-color: #2c3e50; + position: relative; +} + +.dark-theme:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 10px; + background-color: #1a2533; +} + +.dark-theme:after { + content: ''; + position: absolute; + top: 15px; + left: 10px; + width: 60px; + height: 5px; + background-color: #4a5568; + box-shadow: 0 10px 0 #4a5568, 0 20px 0 #4a5568; +} + +.system-theme { + background: linear-gradient(to right, #ffffff 50%, #2c3e50 50%); + position: relative; +} + +.system-theme:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 10px; + background: linear-gradient(to right, #f8f9fa 50%, #1a2533 50%); +} + +.system-theme:after { + content: ''; + position: absolute; + top: 15px; + left: 10px; + width: 60px; + height: 5px; + background: linear-gradient(to right, #e1e4e8 50%, #4a5568 50%); + box-shadow: 0 10px 0 #e1e4e8, 0 20px 0 #4a5568; +} + +.theme-option span { + font-size: 0.875rem; + color: #4a5568; +} + +.theme-option.active span { + color: #3498db; + font-weight: 500; +} + +.color-selector { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; +} + +.color-option { + width: 30px; + height: 30px; + border-radius: 50%; + border: 2px solid transparent; + cursor: pointer; + transition: all 0.2s; +} + +.color-option:hover { + transform: scale(1.1); +} + +.color-option.active { + border-color: #2c3e50; + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1); +} + +.range-slider { + display: flex; + align-items: center; + gap: 1rem; +} + +.range-slider input[type="range"] { + flex: 1; + height: 6px; + -webkit-appearance: none; + appearance: none; + background: #e1e4e8; + border-radius: 3px; + outline: none; +} + +.range-slider input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #3498db; + cursor: pointer; +} + +.range-value { + font-size: 0.875rem; + color: #4a5568; + min-width: 30px; + text-align: center; +} + +/* About tab specific styles */ +.about-logo { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.app-logo { + width: 60px; + height: 60px; + background-color: #3498db; + color: white; + border-radius: 12px; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.75rem; + font-weight: bold; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.about-logo h4 { + font-size: 1.5rem; + margin: 0; + color: #2c3e50; +} + +.about-info { + margin-bottom: 1rem; +} + +.about-description { + margin-bottom: 1.5rem; + color: #4a5568; + line-height: 1.5; +} + +.version-info { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + margin-bottom: 1rem; +} + +.version-item { + display: flex; + gap: 0.5rem; +} + +.version-label { + font-weight: 500; + color: #4a5568; } -.add-provider-form button { - align-self: flex-end; +.version-value { + color: #2c3e50; } +.credits-section { + margin-bottom: 1.5rem; +} + +.credits-section:last-child { + margin-bottom: 0; +} + +.credits-section h5 { + margin-top: 0; + margin-bottom: 0.75rem; + color: #4a5568; + font-size: 0.875rem; + font-weight: 500; +} + +.credits-section p { + margin: 0.5rem 0; + color: #4a5568; +} + +.credits-list { + margin: 0; + padding-left: 1.5rem; + color: #4a5568; +} + +.credits-list li { + margin-bottom: 0.25rem; +} + +.legal-links { + display: flex; + gap: 1.5rem; + margin-top: 1rem; +} + +.legal-link { + color: #3498db; + text-decoration: none; + font-size: 0.875rem; +} + +.legal-link:hover { + text-decoration: underline; +} + +/* No providers message */ +.no-providers { + text-align: center; + padding: 2rem; + color: #718096; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px dashed #cbd5e0; +} + +/* Footer */ .settings-footer { - padding: 1rem; + padding: 1rem 1.5rem; border-top: 1px solid #ddd; display: flex; justify-content: space-between; @@ -137,15 +650,17 @@ .save-message { color: #27ae60; + font-size: 0.875rem; } .settings-actions { display: flex; - gap: 0.5rem; + gap: 0.75rem; } .save-button { background-color: #2ecc71; + color: white; } .save-button:hover {