Settings UI enhancement
Date & Time Display Added a real-time clock component in the sidebar Event Logs System Implemented an EventLogsTab component for system monitoring Provides a structured way to: Track user interactions Monitor system events Display activity history
This commit is contained in:
@@ -1,12 +1,20 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { isDebugMode, isLocalModelsEnabled, LOCAL_PROVIDERS, providersStore } from '~/lib/stores/settings';
|
||||
import {
|
||||
isDebugMode,
|
||||
isEventLogsEnabled,
|
||||
isLocalModelsEnabled,
|
||||
LOCAL_PROVIDERS,
|
||||
providersStore,
|
||||
} from '~/lib/stores/settings';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import Cookies from 'js-cookie';
|
||||
import type { IProviderSetting, ProviderInfo } from '~/types/model';
|
||||
import { logStore } from '~/lib/stores/logs'; // assuming logStore is imported from this location
|
||||
|
||||
export function useSettings() {
|
||||
const providers = useStore(providersStore);
|
||||
const debug = useStore(isDebugMode);
|
||||
const eventLogs = useStore(isEventLogsEnabled);
|
||||
const isLocalModel = useStore(isLocalModelsEnabled);
|
||||
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
|
||||
|
||||
@@ -39,6 +47,13 @@ export function useSettings() {
|
||||
isDebugMode.set(savedDebugMode === 'true');
|
||||
}
|
||||
|
||||
// load event logs from cookies
|
||||
const savedEventLogs = Cookies.get('isEventLogsEnabled');
|
||||
|
||||
if (savedEventLogs) {
|
||||
isEventLogsEnabled.set(savedEventLogs === 'true');
|
||||
}
|
||||
|
||||
// load local models from cookies
|
||||
const savedLocalModels = Cookies.get('isLocalModelsEnabled');
|
||||
|
||||
@@ -80,11 +95,19 @@ export function useSettings() {
|
||||
|
||||
const enableDebugMode = useCallback((enabled: boolean) => {
|
||||
isDebugMode.set(enabled);
|
||||
logStore.logSystem(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('isDebugEnabled', String(enabled));
|
||||
}, []);
|
||||
|
||||
const enableEventLogs = useCallback((enabled: boolean) => {
|
||||
isEventLogsEnabled.set(enabled);
|
||||
logStore.logSystem(`Event logs ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('isEventLogsEnabled', String(enabled));
|
||||
}, []);
|
||||
|
||||
const enableLocalModels = useCallback((enabled: boolean) => {
|
||||
isLocalModelsEnabled.set(enabled);
|
||||
logStore.logSystem(`Local models ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('isLocalModelsEnabled', String(enabled));
|
||||
}, []);
|
||||
|
||||
@@ -94,6 +117,8 @@ export function useSettings() {
|
||||
updateProviderSettings,
|
||||
debug,
|
||||
enableDebugMode,
|
||||
eventLogs,
|
||||
enableEventLogs,
|
||||
isLocalModel,
|
||||
enableLocalModels,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { atom } from 'nanostores';
|
||||
import type { Message } from 'ai';
|
||||
import { toast } from 'react-toastify';
|
||||
import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { logStore } from '~/lib/stores/logs'; // Import logStore
|
||||
import {
|
||||
getMessages,
|
||||
getNextId,
|
||||
@@ -43,6 +44,8 @@ export function useChatHistory() {
|
||||
setReady(true);
|
||||
|
||||
if (persistenceEnabled) {
|
||||
const error = new Error('Chat persistence is unavailable');
|
||||
logStore.logError('Chat persistence initialization failed', error);
|
||||
toast.error('Chat persistence is unavailable');
|
||||
}
|
||||
|
||||
@@ -69,6 +72,7 @@ export function useChatHistory() {
|
||||
setReady(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
logStore.logError('Failed to load chat messages', error);
|
||||
toast.error(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
149
app/lib/stores/logs.ts
Normal file
149
app/lib/stores/logs.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { atom, map } from 'nanostores';
|
||||
import Cookies from 'js-cookie';
|
||||
import { createScopedLogger } from '~/utils/logger';
|
||||
|
||||
const logger = createScopedLogger('LogStore');
|
||||
|
||||
export interface LogEntry {
|
||||
id: string;
|
||||
timestamp: string;
|
||||
level: 'info' | 'warning' | 'error' | 'debug';
|
||||
message: string;
|
||||
details?: Record<string, any>;
|
||||
category: 'system' | 'provider' | 'user' | 'error';
|
||||
}
|
||||
|
||||
const MAX_LOGS = 1000; // Maximum number of logs to keep in memory
|
||||
|
||||
class LogStore {
|
||||
private _logs = map<Record<string, LogEntry>>({});
|
||||
showLogs = atom(false);
|
||||
|
||||
constructor() {
|
||||
// Load saved logs from cookies on initialization
|
||||
this._loadLogs();
|
||||
}
|
||||
|
||||
private _loadLogs() {
|
||||
const savedLogs = Cookies.get('eventLogs');
|
||||
|
||||
if (savedLogs) {
|
||||
try {
|
||||
const parsedLogs = JSON.parse(savedLogs);
|
||||
this._logs.set(parsedLogs);
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse logs from cookies:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _saveLogs() {
|
||||
const currentLogs = this._logs.get();
|
||||
Cookies.set('eventLogs', JSON.stringify(currentLogs));
|
||||
}
|
||||
|
||||
private _generateId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
private _trimLogs() {
|
||||
const currentLogs = Object.entries(this._logs.get());
|
||||
|
||||
if (currentLogs.length > MAX_LOGS) {
|
||||
const sortedLogs = currentLogs.sort(
|
||||
([, a], [, b]) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
|
||||
);
|
||||
const newLogs = Object.fromEntries(sortedLogs.slice(0, MAX_LOGS));
|
||||
this._logs.set(newLogs);
|
||||
}
|
||||
}
|
||||
|
||||
addLog(
|
||||
message: string,
|
||||
level: LogEntry['level'] = 'info',
|
||||
category: LogEntry['category'] = 'system',
|
||||
details?: Record<string, any>,
|
||||
) {
|
||||
const id = this._generateId();
|
||||
const entry: LogEntry = {
|
||||
id,
|
||||
timestamp: new Date().toISOString(),
|
||||
level,
|
||||
message,
|
||||
details,
|
||||
category,
|
||||
};
|
||||
|
||||
this._logs.setKey(id, entry);
|
||||
this._trimLogs();
|
||||
this._saveLogs();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// System events
|
||||
logSystem(message: string, details?: Record<string, any>) {
|
||||
return this.addLog(message, 'info', 'system', details);
|
||||
}
|
||||
|
||||
// Provider events
|
||||
logProvider(message: string, details?: Record<string, any>) {
|
||||
return this.addLog(message, 'info', 'provider', details);
|
||||
}
|
||||
|
||||
// User actions
|
||||
logUserAction(message: string, details?: Record<string, any>) {
|
||||
return this.addLog(message, 'info', 'user', details);
|
||||
}
|
||||
|
||||
// Error events
|
||||
logError(message: string, error?: Error | unknown, details?: Record<string, any>) {
|
||||
const errorDetails = {
|
||||
...(details || {}),
|
||||
error:
|
||||
error instanceof Error
|
||||
? {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
}
|
||||
: error,
|
||||
};
|
||||
return this.addLog(message, 'error', 'error', errorDetails);
|
||||
}
|
||||
|
||||
// Warning events
|
||||
logWarning(message: string, details?: Record<string, any>) {
|
||||
return this.addLog(message, 'warning', 'system', details);
|
||||
}
|
||||
|
||||
// Debug events
|
||||
logDebug(message: string, details?: Record<string, any>) {
|
||||
return this.addLog(message, 'debug', 'system', details);
|
||||
}
|
||||
|
||||
clearLogs() {
|
||||
this._logs.set({});
|
||||
this._saveLogs();
|
||||
}
|
||||
|
||||
getLogs() {
|
||||
return Object.values(this._logs.get()).sort(
|
||||
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
|
||||
);
|
||||
}
|
||||
|
||||
getFilteredLogs(level?: LogEntry['level'], category?: LogEntry['category'], searchQuery?: string) {
|
||||
return this.getLogs().filter((log) => {
|
||||
const matchesLevel = !level || level === 'debug' || log.level === level;
|
||||
const matchesCategory = !category || log.category === category;
|
||||
const matchesSearch =
|
||||
!searchQuery ||
|
||||
log.message.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
JSON.stringify(log.details).toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
return matchesLevel && matchesCategory && matchesSearch;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const logStore = new LogStore();
|
||||
@@ -43,4 +43,6 @@ export const providersStore = map<ProviderSetting>(initialProviderSettings);
|
||||
|
||||
export const isDebugMode = atom(false);
|
||||
|
||||
export const isEventLogsEnabled = atom(false);
|
||||
|
||||
export const isLocalModelsEnabled = atom(true);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { atom } from 'nanostores';
|
||||
import { logStore } from './logs';
|
||||
|
||||
export type Theme = 'dark' | 'light';
|
||||
|
||||
@@ -26,10 +27,8 @@ function initStore() {
|
||||
export function toggleTheme() {
|
||||
const currentTheme = themeStore.get();
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
|
||||
themeStore.set(newTheme);
|
||||
|
||||
logStore.logSystem(`Theme changed to ${newTheme} mode`);
|
||||
localStorage.setItem(kTheme, newTheme);
|
||||
|
||||
document.querySelector('html')?.setAttribute('data-theme', newTheme);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user