diff --git a/app/components/settings/CHANGELOG.md b/app/components/settings/CHANGELOG.md new file mode 100644 index 00000000..80a20471 --- /dev/null +++ b/app/components/settings/CHANGELOG.md @@ -0,0 +1,189 @@ +# Settings Components Changelog + +All notable changes to the settings components will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- New Settings Dashboard with improved UI/UX +- Tab management system with drag-and-drop reordering +- Enhanced developer tools window +- Bulk update functionality for Ollama models +- System performance monitoring in Debug tab +- Data import/export functionality +- Enhanced event logging system +- Profile customization options +- Auto energy saver mode in TaskManager +- Energy savings tracking and statistics +- Persistent settings storage using localStorage + +### Changed + +- Removed green status indicators from TaskManagerTab for cleaner UI +- Changed connection status indicators to use neutral colors +- Updated energy saver mode indicator to use neutral colors +- Simplified process status display in TaskManager +- Improved tab organization with window-specific grouping +- Enhanced settings persistence with better localStorage handling + +### Fixed + +- Status indicator consistency across dark/light themes +- Process status updates during energy saver mode +- UI rendering issues in dark mode +- Tab visibility state management +- Settings import/export reliability + +## [1.0.0] - Initial Release + +### Added + +#### User Window Components + +- **Profile Tab** + + - User profile and account settings management + - Avatar customization + - Account preferences + +- **Settings Tab** + + - Application preferences configuration + - UI behavior customization + - General settings management + +- **Notifications Tab** + + - Real-time notification center + - Unread notification tracking + - Notification preferences + - Support for different notification types + - Integration with logStore + +- **Cloud Providers Tab** + + - Cloud-based AI provider configuration + - API key management + - Cloud model selection + - Provider-specific settings + - Status monitoring + +- **Local Providers Tab** + + - Local AI model management + - Ollama integration and model updates + - LM Studio configuration + - Local inference settings + - Model download and updates + +- **Task Manager Tab** + + - System resource monitoring + - Process management + - Performance metrics and graphs + - Battery status monitoring + - Energy saving features + - Alert configurations + +- **Connections Tab** + + - Network status monitoring + - GitHub integration + - Connection health metrics + - Secure token storage + - Auto-reconnect settings + +- **Debug Tab** + + - System diagnostics + - Performance monitoring + - Error tracking + - Provider status checks + +- **Event Logs Tab** + + - Comprehensive system logs + - Filtered log views + - Log management tools + - Error tracking + - Performance metrics + +- **Update Tab** + - Version management + - Update notifications + - Release notes + - Auto-update configuration + +### Technical Enhancements + +#### State Management + +- Implemented Nano Stores for efficient state handling +- Added persistent settings storage +- Real-time state synchronization +- Provider state management + +#### Performance + +- Lazy loading of tab contents +- Efficient DOM updates +- Optimized animations +- Resource monitoring + +#### Accessibility + +- Keyboard navigation support +- Screen reader compatibility +- Focus management +- ARIA attributes implementation + +#### UI/UX Features + +- Drag & Drop tab management +- Dynamic status updates +- Responsive design with Framer Motion +- Dark/Light mode support +- Enhanced provider management +- Resource monitoring dashboard + +### Dependencies + +- Radix UI for accessible components +- Framer Motion for animations +- React DnD for drag and drop +- Nano Stores for state management + +## Future Plans + +- Additional customization options +- Enhanced theme support +- Extended API integrations +- Advanced monitoring capabilities +- Custom provider plugins +- Enhanced resource management +- Advanced debugging features + +## Historical Changes + +### Task Manager + +- Added real-time system metrics monitoring +- Implemented process tracking functionality +- Added battery status monitoring +- Integrated energy saving features + +### Connections + +- Added GitHub integration +- Implemented secure token storage +- Added connection status indicators + +### Notifications + +- Implemented centralized notification system +- Added support for different notification types (error, warning, update) +- Integrated with logStore for persistent storage diff --git a/app/components/settings/connections/ConnectionsTab.tsx b/app/components/settings/connections/ConnectionsTab.tsx index 31883745..f3506f92 100644 --- a/app/components/settings/connections/ConnectionsTab.tsx +++ b/app/components/settings/connections/ConnectionsTab.tsx @@ -194,7 +194,7 @@ export default function ConnectionsTab() { )} {connection.user && ( - +
Connected to GitHub diff --git a/app/components/settings/debug/DebugTab.tsx b/app/components/settings/debug/DebugTab.tsx index 2d4879eb..6c28bd3f 100644 --- a/app/components/settings/debug/DebugTab.tsx +++ b/app/components/settings/debug/DebugTab.tsx @@ -4,13 +4,6 @@ import { classNames } from '~/utils/classNames'; import { logStore } from '~/lib/stores/logs'; import type { LogEntry } from '~/lib/stores/logs'; -interface ProviderStatus { - id: string; - name: string; - status: 'online' | 'offline' | 'error'; - error?: string; -} - interface SystemInfo { os: string; arch: string; @@ -90,14 +83,24 @@ interface SystemInfo { }; } +interface WebAppInfo { + name: string; + version: string; + description: string; + license: string; + nodeVersion: string; + dependencies: { [key: string]: string }; + devDependencies: { [key: string]: string }; +} + export default function DebugTab() { - const [providerStatuses, setProviderStatuses] = useState([]); const [systemInfo, setSystemInfo] = useState(null); + const [webAppInfo, setWebAppInfo] = useState(null); const [loading, setLoading] = useState({ systemInfo: false, - providers: false, performance: false, errors: false, + webAppInfo: false, }); const [errorLog, setErrorLog] = useState<{ errors: any[]; @@ -109,8 +112,8 @@ export default function DebugTab() { // Fetch initial data useEffect(() => { - checkProviderStatus(); getSystemInfo(); + getWebAppInfo(); }, []); // Set up error listeners when component mounts @@ -146,75 +149,6 @@ export default function DebugTab() { }; }, []); - const checkProviderStatus = async () => { - try { - setLoading((prev) => ({ ...prev, providers: true })); - - // Fetch real provider statuses - const providers: ProviderStatus[] = []; - - // Check OpenAI status - try { - const openaiResponse = await fetch('/api/providers/openai/status'); - providers.push({ - id: 'openai', - name: 'OpenAI', - status: openaiResponse.ok ? 'online' : 'error', - error: !openaiResponse.ok ? 'API Error' : undefined, - }); - } catch { - providers.push({ id: 'openai', name: 'OpenAI', status: 'offline' }); - } - - // Check Anthropic status - try { - const anthropicResponse = await fetch('/api/providers/anthropic/status'); - providers.push({ - id: 'anthropic', - name: 'Anthropic', - status: anthropicResponse.ok ? 'online' : 'error', - error: !anthropicResponse.ok ? 'API Error' : undefined, - }); - } catch { - providers.push({ id: 'anthropic', name: 'Anthropic', status: 'offline' }); - } - - // Check Local Models status - try { - const localResponse = await fetch('/api/providers/local/status'); - providers.push({ - id: 'local', - name: 'Local Models', - status: localResponse.ok ? 'online' : 'error', - error: !localResponse.ok ? 'API Error' : undefined, - }); - } catch { - providers.push({ id: 'local', name: 'Local Models', status: 'offline' }); - } - - // Check Ollama status - try { - const ollamaResponse = await fetch('/api/providers/ollama/status'); - providers.push({ - id: 'ollama', - name: 'Ollama', - status: ollamaResponse.ok ? 'online' : 'error', - error: !ollamaResponse.ok ? 'API Error' : undefined, - }); - } catch { - providers.push({ id: 'ollama', name: 'Ollama', status: 'offline' }); - } - - setProviderStatuses(providers); - toast.success('Provider status updated'); - } catch (error) { - toast.error('Failed to check provider status'); - console.error('Failed to check provider status:', error); - } finally { - setLoading((prev) => ({ ...prev, providers: false })); - } - }; - const getSystemInfo = async () => { try { setLoading((prev) => ({ ...prev, systemInfo: true })); @@ -360,6 +294,26 @@ export default function DebugTab() { } }; + const getWebAppInfo = async () => { + try { + setLoading((prev) => ({ ...prev, webAppInfo: true })); + + const response = await fetch('/api/system/app-info'); + + if (!response.ok) { + throw new Error('Failed to fetch webapp info'); + } + + const data = await response.json(); + setWebAppInfo(data as WebAppInfo); + } catch (error) { + console.error('Failed to fetch webapp info:', error); + toast.error('Failed to fetch webapp information'); + } finally { + setLoading((prev) => ({ ...prev, webAppInfo: false })); + } + }; + // Helper function to format bytes to human readable format const formatBytes = (bytes: number) => { const units = ['B', 'KB', 'MB', 'GB']; @@ -509,29 +463,40 @@ export default function DebugTab() { } }; + const exportDebugInfo = () => { + try { + const debugData = { + timestamp: new Date().toISOString(), + system: systemInfo, + webApp: webAppInfo, + errors: errorLog.errors, + performance: { + memory: (performance as any).memory || {}, + timing: performance.timing, + navigation: performance.navigation, + }, + }; + + const blob = new Blob([JSON.stringify(debugData, null, 2)], { type: 'application/json' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bolt-debug-info-${new Date().toISOString()}.json`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('Debug information exported successfully'); + } catch (error) { + console.error('Failed to export debug info:', error); + toast.error('Failed to export debug information'); + } + }; + return (
{/* Action Buttons */}
- -
{/* Error Log Display */} @@ -772,53 +750,6 @@ export default function DebugTab() { )}
- {/* Provider Status */} -
-
-
-
-

Provider Status

-
- -
-
- {providerStatuses.map((provider) => ( -
-
-
- {provider.name} -
- {provider.status} -
- ))} -
-
- {/* Performance Metrics */}
@@ -906,6 +837,82 @@ export default function DebugTab() { )}
+ {/* WebApp Information */} +
+
+
+
+

WebApp Information

+
+ +
+ {webAppInfo ? ( +
+
+
+
+ Name: + {webAppInfo.name} +
+
+
+ Version: + {webAppInfo.version} +
+
+
+ Description: + {webAppInfo.description} +
+
+
+ License: + {webAppInfo.license} +
+
+
+ Node Version: + {webAppInfo.nodeVersion} +
+
+
+
+
+
+ Key Dependencies: +
+
+ {Object.entries(webAppInfo.dependencies) + .filter(([key]) => ['react', '@remix-run/react', 'next', 'typescript'].includes(key)) + .map(([key, version]) => ( +
+ {key}: {version} +
+ ))} +
+
+
+
+ ) : ( +
+ {loading.webAppInfo ? 'Loading webapp information...' : 'No webapp information available'} +
+ )} +
+ {/* Error Check */}
diff --git a/app/components/settings/event-logs/EventLogsTab.tsx b/app/components/settings/event-logs/EventLogsTab.tsx index 0b790609..8120195e 100644 --- a/app/components/settings/event-logs/EventLogsTab.tsx +++ b/app/components/settings/event-logs/EventLogsTab.tsx @@ -1,515 +1,421 @@ -import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; -import { toast } from 'react-toastify'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { motion } from 'framer-motion'; import { Switch } from '~/components/ui/Switch'; import { logStore, type LogEntry } from '~/lib/stores/logs'; import { useStore } from '@nanostores/react'; import { classNames } from '~/utils/classNames'; -import { motion } from 'framer-motion'; interface SelectOption { value: string; label: string; - icon: string; + icon?: string; color?: string; } const logLevelOptions: SelectOption[] = [ - { value: 'all', label: 'All Levels', icon: 'i-ph:funnel' }, - { value: 'info', label: 'Info', icon: 'i-ph:info', color: 'text-blue-500' }, - { value: 'warning', label: 'Warning', icon: 'i-ph:warning', color: 'text-yellow-500' }, - { value: 'error', label: 'Error', icon: 'i-ph:x-circle', color: 'text-red-500' }, - { value: 'debug', label: 'Debug', icon: 'i-ph:bug', color: 'text-gray-500' }, + { + value: 'all', + label: 'All Levels', + icon: 'i-ph:funnel', + color: '#9333ea', + }, + { + value: 'info', + label: 'Info', + icon: 'i-ph:info', + color: '#3b82f6', + }, + { + value: 'warning', + label: 'Warning', + icon: 'i-ph:warning', + color: '#f59e0b', + }, + { + value: 'error', + label: 'Error', + icon: 'i-ph:x-circle', + color: '#ef4444', + }, + { + value: 'debug', + label: 'Debug', + icon: 'i-ph:bug', + color: '#6b7280', + }, ]; const logCategoryOptions: SelectOption[] = [ - { value: 'all', label: 'All Categories', icon: 'i-ph:squares-four' }, - { value: 'system', label: 'System', icon: 'i-ph:desktop' }, - { value: 'provider', label: 'Provider', icon: 'i-ph:plug' }, - { value: 'user', label: 'User', icon: 'i-ph:user' }, - { value: 'error', label: 'Error', icon: 'i-ph:warning-octagon' }, + { + value: 'all', + label: 'All Categories', + icon: 'i-ph:squares-four', + color: '#9333ea', + }, + { + value: 'api', + label: 'API', + icon: 'i-ph:cloud', + color: '#3b82f6', + }, + { + value: 'auth', + label: 'Auth', + icon: 'i-ph:key', + color: '#f59e0b', + }, + { + value: 'database', + label: 'Database', + icon: 'i-ph:database', + color: '#10b981', + }, + { + value: 'network', + label: 'Network', + icon: 'i-ph:wifi-high', + color: '#6366f1', + }, + { + value: 'performance', + label: 'Performance', + icon: 'i-ph:chart-line-up', + color: '#8b5cf6', + }, ]; -const SegmentedGroup = ({ - value, - onChange, - options, - className, -}: { - value: string; - onChange: (value: string) => void; - options: SelectOption[]; - className?: string; -}) => { - const [isExpanded, setIsExpanded] = useState(false); - const selectedOption = options.find((opt) => opt.value === value); - - if (!isExpanded) { - return ( - - ); - } - - return ( -
- {options.map((option) => ( - - ))} -
- ); -}; - -const LogEntryItem = ({ - log, - isExpanded: forceExpanded, - use24Hour, -}: { +interface LogEntryItemProps { log: LogEntry; isExpanded: boolean; use24Hour: boolean; -}) => { - const [isExpanded, setIsExpanded] = useState(forceExpanded); - const [isCopied, setIsCopied] = useState(false); + showTimestamp: boolean; +} +const LogEntryItem = ({ log, isExpanded: forceExpanded, use24Hour, showTimestamp }: LogEntryItemProps) => { + const [localExpanded, setLocalExpanded] = useState(forceExpanded); + + // Update local expanded state when forceExpanded changes useEffect(() => { - setIsExpanded(forceExpanded); + setLocalExpanded(forceExpanded); }, [forceExpanded]); - const handleCopy = useCallback(() => { - const logText = `[${log.level.toUpperCase()}] ${log.message}\nTimestamp: ${new Date( - log.timestamp, - ).toLocaleString()}\nCategory: ${log.category}\nDetails: ${JSON.stringify(log.details, null, 2)}`; - - navigator.clipboard.writeText(logText).then(() => { - setIsCopied(true); - toast.success('Log copied to clipboard'); - setTimeout(() => setIsCopied(false), 2000); - }); - }, [log]); - - const formattedTime = useMemo(() => { + const timestamp = useMemo(() => { const date = new Date(log.timestamp); - const now = new Date(); - const isToday = date.toDateString() === now.toDateString(); - const isYesterday = new Date(now.setDate(now.getDate() - 1)).toDateString() === date.toDateString(); - const timeStr = date.toLocaleTimeString(undefined, { - hour: '2-digit', - minute: '2-digit', - hour12: !use24Hour, - }); - - if (isToday) { - return { - primary: timeStr, - secondary: 'Today', - }; - } else if (isYesterday) { - return { - primary: timeStr, - secondary: 'Yesterday', - }; - } else { - const dateStr = date.toLocaleDateString(undefined, { - month: 'short', - day: 'numeric', - year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined, - }); - return { - primary: dateStr, - secondary: timeStr, - }; + if (use24Hour) { + return date.toLocaleTimeString('en-US', { hour12: false }); } + + return date.toLocaleTimeString('en-US', { hour12: true }); }, [log.timestamp, use24Hour]); - return ( -
-
-
- - {log.level} - -

{log.message}

-
- - {log.details && ( - - )} -
-
-
-
-
- {formattedTime.primary} - ยท - {formattedTime.secondary} -
- - {log.category} - -
-
+ const levelColor = useMemo(() => { + switch (log.level) { + case 'error': + return 'text-red-500 bg-red-50 dark:bg-red-500/10'; + case 'warning': + return 'text-yellow-500 bg-yellow-50 dark:bg-yellow-500/10'; + case 'debug': + return 'text-gray-500 bg-gray-50 dark:bg-gray-500/10'; + default: + return 'text-blue-500 bg-blue-50 dark:bg-blue-500/10'; + } + }, [log.level]); - {isExpanded && log.details && ( - -
+  return (
+    
+
+
{log.level}
+ {showTimestamp &&
{timestamp}
} +
+
{log.message}
+ {log.details && ( + + )} +
+ {log.category && ( +
+ {log.category} +
+ )} +
+ {localExpanded && log.details && ( +
+
             {JSON.stringify(log.details, null, 2)}
           
- +
)}
); }; -/** - * TODO: Future Enhancements - * - * 1. Advanced Features: - * - Add export to JSON/CSV functionality - * - Implement log retention policies - * - Add custom alert rules and notifications - * - Add pattern detection and highlighting - * - * 2. Visual Improvements: - * - Add dark/light mode specific styling - * - Implement collapsible JSON viewer - * - Add timeline view with zoom capabilities - * - * 3. Performance Optimizations: - * - Implement virtualized scrolling for large logs - * - Add lazy loading for log details - * - Optimize search with indexing - */ - export function EventLogsTab() { const logs = useStore(logStore.logs); - const [logLevel, setLogLevel] = useState('all'); - const [logCategory, setLogCategory] = useState('all'); - const [autoScroll, setAutoScroll] = useState(true); + const [selectedLevel, setSelectedLevel] = useState('all'); + const [selectedCategory, setSelectedCategory] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); - const [expandAll, setExpandAll] = useState(false); - const [use24Hour, setUse24Hour] = useState(true); + const [use24Hour, setUse24Hour] = useState(false); + const [autoExpand, setAutoExpand] = useState(false); + const [showTimestamps, setShowTimestamps] = useState(true); + const [showLevelFilter, setShowLevelFilter] = useState(false); + const [showCategoryFilter, setShowCategoryFilter] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const logsContainerRef = useRef(null); - const [isScrolledToBottom, setIsScrolledToBottom] = useState(true); - - // Add refresh function - const handleRefresh = useCallback(async () => { - setIsRefreshing(true); - - try { - // Since logStore doesn't have refresh, we'll re-fetch logs - await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate refresh - toast.success('Logs refreshed'); - } catch (err) { - console.error('Failed to refresh logs:', err); - toast.error('Failed to refresh logs'); - } finally { - setIsRefreshing(false); - } - }, []); + const levelFilterRef = useRef(null); + const categoryFilterRef = useRef(null); const filteredLogs = useMemo(() => { - const allLogs = Object.values(logs); - const filtered = allLogs.filter((log) => { - const matchesLevel = logLevel === 'all' || log.level === logLevel; - const matchesCategory = logCategory === 'all' || log.category === logCategory; - const matchesSearch = - !searchQuery || - log.message?.toLowerCase().includes(searchQuery.toLowerCase()) || - JSON.stringify(log.details)?.toLowerCase()?.includes(searchQuery?.toLowerCase()); - - return matchesLevel && matchesCategory && matchesSearch; - }); - - return filtered.reverse(); - }, [logs, logLevel, logCategory, searchQuery]); - - const handleClearLogs = useCallback(() => { - if (confirm('Are you sure you want to clear all logs?')) { - logStore.clearLogs(); - toast.success('Logs cleared successfully'); - } - }, []); + return logStore.getFilteredLogs( + selectedLevel === 'all' ? undefined : (selectedLevel as LogEntry['level']), + selectedCategory === 'all' ? undefined : (selectedCategory as LogEntry['category']), + searchQuery, + ); + }, [logs, selectedLevel, selectedCategory, searchQuery]); const handleExportLogs = useCallback(() => { - try { - const logText = logStore - .getLogs() - .map( - (log) => - `[${log.level.toUpperCase()}] ${log.timestamp} - ${log.message}${ - log.details ? '\nDetails: ' + JSON.stringify(log.details, null, 2) : '' - }`, - ) - .join('\n\n'); + const exportData = { + timestamp: new Date().toISOString(), + logs: filteredLogs, + filters: { + level: selectedLevel, + category: selectedCategory, + searchQuery, + }, + }; - const blob = new Blob([logText], { type: 'text/plain' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `event-logs-${new Date().toISOString()}.txt`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - toast.success('Logs exported successfully'); - } catch (error) { - toast.error('Failed to export logs'); - console.error('Export error:', error); - } + const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bolt-logs-${new Date().toISOString()}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, [filteredLogs, selectedLevel, selectedCategory, searchQuery]); + + const handleRefresh = useCallback(async () => { + setIsRefreshing(true); + await logStore.refreshLogs(); + setTimeout(() => setIsRefreshing(false), 500); // Keep animation visible for at least 500ms }, []); - const handleScroll = () => { - const container = logsContainerRef.current; - - if (!container) { - return; - } - - const { scrollTop, scrollHeight, clientHeight } = container; - const isBottom = Math.abs(scrollHeight - clientHeight - scrollTop) < 10; - setIsScrolledToBottom(isBottom); - }; - + // Close filters when clicking outside useEffect(() => { - const container = logsContainerRef.current; + const handleClickOutside = (event: MouseEvent) => { + if (levelFilterRef.current && !levelFilterRef.current.contains(event.target as Node)) { + setShowLevelFilter(false); + } - if (container && (autoScroll || isScrolledToBottom)) { - container.scrollTop = container.scrollHeight; - } - }, [filteredLogs, autoScroll, isScrolledToBottom]); + if (categoryFilterRef.current && !categoryFilterRef.current.contains(event.target as Node)) { + setShowCategoryFilter(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const selectedLevelOption = logLevelOptions.find((opt) => opt.value === selectedLevel); + const selectedCategoryOption = logCategoryOptions.find((opt) => opt.value === selectedCategory); return (
- {/* Header Section */} -
- {/* Title and Refresh */} -
-
-
-
-

Event Logs

-

Track system events and debug information

-
+ {/* Header */} +
+
+
+
+

Event Logs

+

Track system events and debug information

+
+
-
-
-
+
- - {/* Controls Section */} -
-
- - Auto-scroll - - - -
- - - 24h Time - - - -
- - - Expand All - - -
-
- {/* Header with Search */} -
-
-
-
-
+ {/* Top Controls */} +
+ {/* Search */} +
setSearchQuery(e.target.value)} + className="w-full px-3 py-1.5 pl-9 rounded-lg text-sm bg-white/50 dark:bg-gray-800/30 border border-gray-200/50 dark:border-gray-700/50" /> +
+
+
- {/* Filters Row */} -
- setLogLevel(value as LogEntry['level'] | 'all')} - options={logLevelOptions} - /> -
- setLogCategory(value as LogEntry['category'] | 'all')} - options={logCategoryOptions} - /> -
-
+ {/* Right Controls */} +
+
+ + Show Timestamps +
- {/* Logs Display */} -
-
- {filteredLogs.map((log) => ( - - ))} -
-
+
+ + 24h Time +
+ +
+ + Auto Expand +
- {/* Status Bar */} -
-
- {filteredLogs.length} logs displayed - {isScrolledToBottom ? 'Watching for new logs...' : 'Scroll to bottom to watch new logs'} -
-
-
- Export +
+ Export Logs - +
+ + {/* Filters */} +
+ {/* Level Filter */} +
+ + + {showLevelFilter && ( +
+
+ {logLevelOptions.map((option) => ( + + ))} +
+
+ )} +
+ +
+ + {/* Category Filter */} +
+ + + {showCategoryFilter && ( +
+
+ {logCategoryOptions.map((option) => ( + + ))} +
+
+ )} +
+
+ + {/* Logs Container */} +
+
+ {filteredLogs.map((log) => ( + + ))}
diff --git a/app/components/settings/task-manager/TaskManagerTab.tsx b/app/components/settings/task-manager/TaskManagerTab.tsx index 61d85cf5..3ed60463 100644 --- a/app/components/settings/task-manager/TaskManagerTab.tsx +++ b/app/components/settings/task-manager/TaskManagerTab.tsx @@ -11,6 +11,7 @@ import { Tooltip, Legend, } from 'chart.js'; +import { toast } from 'react-toastify'; // Import toast // Register ChartJS components ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); @@ -120,8 +121,18 @@ export default function TaskManagerTab() { metrics: false, processes: false, }); - const [energySaverMode, setEnergySaverMode] = useState(false); - const [autoEnergySaver, setAutoEnergySaver] = useState(true); + const [energySaverMode, setEnergySaverMode] = useState(() => { + // Initialize from localStorage, default to false + const saved = localStorage.getItem('energySaverMode'); + return saved ? JSON.parse(saved) : false; + }); + + const [autoEnergySaver, setAutoEnergySaver] = useState(() => { + // Initialize from localStorage, default to false + const saved = localStorage.getItem('autoEnergySaver'); + return saved ? JSON.parse(saved) : false; + }); + const [energySavings, setEnergySavings] = useState({ updatesReduced: 0, timeInSaverMode: 0, @@ -130,6 +141,26 @@ export default function TaskManagerTab() { const saverModeStartTime = useRef(null); + // Handle energy saver mode changes + const handleEnergySaverChange = (checked: boolean) => { + setEnergySaverMode(checked); + localStorage.setItem('energySaverMode', JSON.stringify(checked)); + toast.success(checked ? 'Energy Saver mode enabled' : 'Energy Saver mode disabled'); + }; + + // Handle auto energy saver changes + const handleAutoEnergySaverChange = (checked: boolean) => { + setAutoEnergySaver(checked); + localStorage.setItem('autoEnergySaver', JSON.stringify(checked)); + toast.success(checked ? 'Auto Energy Saver enabled' : 'Auto Energy Saver disabled'); + + if (!checked) { + // When disabling auto mode, also disable energy saver mode + setEnergySaverMode(false); + localStorage.setItem('energySaverMode', 'false'); + } + }; + // Calculate energy savings const updateEnergySavings = useCallback(() => { if (!energySaverMode) { @@ -163,17 +194,25 @@ export default function TaskManagerTab() { }, [energySaverMode]); useEffect((): (() => void) | undefined => { - if (energySaverMode) { - const savingsInterval = setInterval(updateEnergySavings, 1000); - return () => clearInterval(savingsInterval); + if (!energySaverMode) { + // Clear any existing intervals and reset savings when disabled + setEnergySavings({ + updatesReduced: 0, + timeInSaverMode: 0, + estimatedEnergySaved: 0, + }); + return undefined; } - return undefined; + const savingsInterval = setInterval(updateEnergySavings, 1000); + + return () => clearInterval(savingsInterval); }, [energySaverMode, updateEnergySavings]); - // Auto energy saver effect useEffect((): (() => void) | undefined => { if (!autoEnergySaver) { + // If auto mode is disabled, clear any forced energy saver state + setEnergySaverMode(false); return undefined; } @@ -194,18 +233,6 @@ export default function TaskManagerTab() { return () => clearInterval(batteryCheckInterval); }, [autoEnergySaver]); - const getStatusColor = (status: 'active' | 'idle' | 'suspended'): string => { - if (status === 'active') { - return 'text-green-500'; - } - - if (status === 'suspended') { - return 'text-yellow-500'; - } - - return 'text-gray-400'; - }; - const getUsageColor = (usage: number): string => { if (usage > 80) { return 'text-red-500'; @@ -215,7 +242,7 @@ export default function TaskManagerTab() { return 'text-yellow-500'; } - return 'text-green-500'; + return 'text-gray-500'; }; const getImpactColor = (impact: 'high' | 'medium' | 'low'): string => { @@ -227,7 +254,7 @@ export default function TaskManagerTab() { return 'text-yellow-500'; } - return 'text-green-500'; + return 'text-gray-500'; }; const renderUsageGraph = (data: number[], label: string, color: string) => { @@ -359,7 +386,7 @@ export default function TaskManagerTab() { type: 'Network', cpuUsage: Math.random() * 5, memoryUsage: Math.random() * 50, - status: 'active', + status: 'idle', lastUpdate: new Date().toISOString(), impact: 'high', }, @@ -368,7 +395,7 @@ export default function TaskManagerTab() { type: 'Animation', cpuUsage: Math.random() * 3, memoryUsage: Math.random() * 30, - status: 'active', + status: 'idle', lastUpdate: new Date().toISOString(), impact: 'medium', }, @@ -386,7 +413,7 @@ export default function TaskManagerTab() { type: 'Storage', cpuUsage: Math.random() * 1, memoryUsage: Math.random() * 15, - status: 'active', + status: 'idle', lastUpdate: new Date().toISOString(), impact: 'low', }, @@ -395,7 +422,7 @@ export default function TaskManagerTab() { type: 'Network', cpuUsage: Math.random() * 2, memoryUsage: Math.random() * 10, - status: 'active', + status: 'idle', lastUpdate: new Date().toISOString(), impact: 'medium', }, @@ -444,7 +471,7 @@ export default function TaskManagerTab() { type="checkbox" id="autoEnergySaver" checked={autoEnergySaver} - onChange={(e) => setAutoEnergySaver(e.target.checked)} + onChange={(e) => handleAutoEnergySaverChange(e.target.checked)} className="form-checkbox h-4 w-4 text-purple-600 rounded border-gray-300 dark:border-gray-700" />
@@ -502,7 +529,7 @@ export default function TaskManagerTab() {

{Math.round(metrics.battery.level)}% {metrics.battery.charging && ( - +

)} @@ -597,10 +624,9 @@ export default function TaskManagerTab() { -
-
- {process.status} -
+ + {process.status} + {process.impact} diff --git a/app/components/settings/update/UpdateTab.tsx b/app/components/settings/update/UpdateTab.tsx index afa9532a..64a6d264 100644 --- a/app/components/settings/update/UpdateTab.tsx +++ b/app/components/settings/update/UpdateTab.tsx @@ -707,7 +707,7 @@ const UpdateTab = () => {
{categorizeChangelog(updateInfo.changelog).map(([category, messages]) => (
-
+
{category} diff --git a/app/lib/stores/logs.ts b/app/lib/stores/logs.ts index d36caff9..e9adf138 100644 --- a/app/lib/stores/logs.ts +++ b/app/lib/stores/logs.ts @@ -10,10 +10,12 @@ export interface LogEntry { level: 'info' | 'warning' | 'error' | 'debug'; message: string; details?: Record; - category: 'system' | 'provider' | 'user' | 'error' | 'api' | 'auth' | 'database' | 'network'; + category: 'system' | 'provider' | 'user' | 'error' | 'api' | 'auth' | 'database' | 'network' | 'performance'; subCategory?: string; duration?: number; statusCode?: number; + source?: string; + stack?: string; } const MAX_LOGS = 1000; // Maximum number of logs to keep in memory @@ -109,6 +111,8 @@ class LogStore { level: LogEntry['level'] = 'info', category: LogEntry['category'] = 'system', details?: Record, + statusCode?: number, + duration?: number, ) { const id = this._generateId(); const entry: LogEntry = { @@ -118,6 +122,8 @@ class LogStore { message, details, category, + statusCode, + duration, }; this._logs.setKey(id, entry); @@ -262,6 +268,94 @@ class LogStore { this._readLogs.clear(); this._saveReadLogs(); } + + // Network request logging + logNetworkRequest( + method: string, + url: string, + statusCode: number, + duration: number, + requestData?: any, + responseData?: any, + ) { + this.addLog( + `${method} ${url}`, + statusCode >= 400 ? 'error' : 'info', + 'network', + { + method, + url, + statusCode, + duration, + request: requestData, + response: responseData, + }, + statusCode, + duration, + ); + } + + // Authentication events + logAuthEvent(event: string, success: boolean, details?: Record) { + this.addLog(`Auth ${event} ${success ? 'succeeded' : 'failed'}`, success ? 'info' : 'error', 'auth', details); + } + + // API interactions + logApiCall( + endpoint: string, + method: string, + statusCode: number, + duration: number, + requestData?: any, + responseData?: any, + ) { + this.addLog( + `API ${method} ${endpoint}`, + statusCode >= 400 ? 'error' : 'info', + 'api', + { + endpoint, + method, + statusCode, + duration, + request: requestData, + response: responseData, + }, + statusCode, + duration, + ); + } + + // Performance monitoring + logPerformance(operation: string, duration: number, details?: Record) { + this.addLog( + `Performance: ${operation}`, + duration > 1000 ? 'warning' : 'info', + 'performance', + { + operation, + duration, + ...details, + }, + undefined, + duration, + ); + } + + // Error logging with stack trace + logErrorWithStack(error: Error, category: LogEntry['category'] = 'error', details?: Record) { + this.addLog(error.message, 'error', category, { + ...details, + name: error.name, + stack: error.stack, + }); + } + + // Refresh logs (useful for real-time updates) + refreshLogs() { + const currentLogs = this._logs.get(); + this._logs.set({ ...currentLogs }); + } } export const logStore = new LogStore(); diff --git a/app/routes/api.system.app-info.ts b/app/routes/api.system.app-info.ts new file mode 100644 index 00000000..9e5a208b --- /dev/null +++ b/app/routes/api.system.app-info.ts @@ -0,0 +1,45 @@ +import type { ActionFunctionArgs } from '@remix-run/cloudflare'; +import { json } from '@remix-run/cloudflare'; + +interface PackageJson { + name: string; + version: string; + description: string; + license: string; + dependencies: Record; + devDependencies: Record; +} + +const packageJson = { + name: 'bolt.diy', + version: '0.1.0', + description: 'A DIY LLM interface', + license: 'MIT', + dependencies: { + '@remix-run/cloudflare': '^2.0.0', + react: '^18.0.0', + 'react-dom': '^18.0.0', + typescript: '^5.0.0', + }, + devDependencies: { + '@types/react': '^18.0.0', + '@types/react-dom': '^18.0.0', + }, +} as PackageJson; + +export const action = async ({ request: _request }: ActionFunctionArgs) => { + try { + return json({ + name: packageJson.name, + version: packageJson.version, + description: packageJson.description, + license: packageJson.license, + nodeVersion: process.version, + dependencies: packageJson.dependencies, + devDependencies: packageJson.devDependencies, + }); + } catch (error) { + console.error('Failed to get webapp info:', error); + return json({ error: 'Failed to get webapp information' }, { status: 500 }); + } +}; diff --git a/changelogUI.md b/changelogUI.md deleted file mode 100644 index 012bcf4d..00000000 --- a/changelogUI.md +++ /dev/null @@ -1,214 +0,0 @@ -# Bolt DIY UI Overhaul - -## New User Interface Features - -### ๐ŸŽจ Redesigned Control Panel - -The Bolt DIY interface has been completely redesigned with a modern, intuitive layout featuring two main components: - -1. **Users Window** - Main control panel for regular users -2. **Developer Window** - Advanced settings and debugging tools - -### ๐Ÿ’ก Core Features - -- **Drag & Drop Tab Management**: Customize tab order in both User and Developer windows -- **Dynamic Status Updates**: Real-time status indicators for updates, notifications, and system health -- **Responsive Design**: Beautiful transitions and animations using Framer Motion -- **Dark/Light Mode Support**: Full theme support with consistent styling -- **Improved Accessibility**: Using Radix UI primitives for better accessibility -- **Enhanced Provider Management**: Split view for local and cloud providers -- **Resource Monitoring**: New Task Manager for system performance tracking - -### ๐ŸŽฏ Tab Overview - -#### User Window Tabs - -1. **Profile** - - - Manage user profile and account settings - - Avatar customization - - Account preferences - -2. **Settings** - - - Configure application preferences - - Customize UI behavior - - Manage general settings - -3. **Notifications** - - - Real-time notification center - - Unread notification tracking - - Notification preferences - -4. **Features** - - - Explore new and upcoming features - - Feature preview toggles - - Early access options - -5. **Data** - - - Data management tools - - Storage settings - - Backup and restore options - -6. **Cloud Providers** - - - Configure cloud-based AI providers - - API key management - - Cloud model selection - - Provider-specific settings - - Status monitoring for each provider - -7. **Local Providers** - - - Manage local AI models - - Ollama integration and model updates - - LM Studio configuration - - Local inference settings - - Model download and updates - -8. **Task Manager** - - - System resource monitoring - - Process management - - Performance metrics - - Resource usage graphs - - Alert configurations - -9. **Connection** - - - Network status monitoring - - Connection health metrics - - Troubleshooting tools - - Latency tracking - - Auto-reconnect settings - -10. **Debug** - - - System diagnostics - - Performance monitoring - - Error tracking - - Provider status checks - - System information - -11. **Event Logs** - - - Comprehensive system logs - - Filtered log views - - Log management tools - - Error tracking - - Performance metrics - -12. **Update** - - Version management - - Update notifications - - Release notes - - Auto-update configuration - -#### Developer Window Enhancements - -- **Advanced Tab Management** - - - Fine-grained control over tab visibility - - Custom tab ordering - - Tab permission management - - Category-based organization - -- **Developer Tools** - - Enhanced debugging capabilities - - System metrics and monitoring - - Performance optimization tools - - Advanced logging features - -### ๐Ÿš€ UI Improvements - -1. **Enhanced Navigation** - - - Intuitive back navigation - - Breadcrumb-style header - - Context-aware menu system - - Improved tab organization - -2. **Status Indicators** - - - Dynamic update badges - - Real-time connection status - - System health monitoring - - Provider status tracking - -3. **Profile Integration** - - - Quick access profile menu - - Avatar support - - Fast settings access - - Personalization options - -4. **Accessibility Features** - - Keyboard navigation - - Screen reader support - - Focus management - - ARIA attributes - -### ๐Ÿ›  Technical Enhancements - -- **State Management** - - - Nano Stores for efficient state handling - - Persistent settings storage - - Real-time state synchronization - - Provider state management - -- **Performance Optimizations** - - - Lazy loading of tab contents - - Efficient DOM updates - - Optimized animations - - Resource monitoring - -- **Developer Experience** - - Improved error handling - - Better debugging tools - - Enhanced logging system - - Performance profiling - -### ๐ŸŽฏ Future Roadmap - -- [ ] Additional customization options -- [ ] Enhanced theme support -- [ ] More developer tools -- [ ] Extended API integrations -- [ ] Advanced monitoring capabilities -- [ ] Custom provider plugins -- [ ] Enhanced resource management -- [ ] Advanced debugging features - -## ๐Ÿ”ง Technical Details - -### Dependencies - -- Radix UI for accessible components -- Framer Motion for animations -- React DnD for drag and drop -- Nano Stores for state management - -### Browser Support - -- Modern browsers (Chrome, Firefox, Safari, Edge) -- Progressive enhancement for older browsers - -### Performance - -- Optimized bundle size -- Efficient state updates -- Minimal re-renders -- Resource-aware operations - -## ๐Ÿ“ Contributing - -We welcome contributions! Please see our contributing guidelines for more information. - -## ๐Ÿ“„ License - -MIT License - see LICENSE for details