ui refactor
This commit is contained in:
18
app/lib/api/connection.ts
Normal file
18
app/lib/api/connection.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface ConnectionStatus {
|
||||
connected: boolean;
|
||||
latency: number;
|
||||
lastChecked: string;
|
||||
}
|
||||
|
||||
export const checkConnection = async (): Promise<ConnectionStatus> => {
|
||||
/*
|
||||
* TODO: Implement actual connection check logic
|
||||
* This is a mock implementation
|
||||
*/
|
||||
const connected = Math.random() > 0.1; // 90% chance of being connected
|
||||
return {
|
||||
connected,
|
||||
latency: connected ? Math.floor(Math.random() * 1500) : 0, // Random latency between 0-1500ms
|
||||
lastChecked: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
65
app/lib/api/debug.ts
Normal file
65
app/lib/api/debug.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export interface DebugWarning {
|
||||
id: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface DebugError {
|
||||
id: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
export interface DebugStatus {
|
||||
warnings: DebugWarning[];
|
||||
errors: DebugError[];
|
||||
lastChecked: string;
|
||||
}
|
||||
|
||||
export interface DebugIssue {
|
||||
id: string;
|
||||
type: 'warning' | 'error';
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const getDebugStatus = async (): Promise<DebugStatus> => {
|
||||
/*
|
||||
* TODO: Implement actual debug status logic
|
||||
* This is a mock implementation
|
||||
*/
|
||||
return {
|
||||
warnings: [
|
||||
{
|
||||
id: 'warn-1',
|
||||
message: 'High memory usage detected',
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 'MEM_HIGH',
|
||||
},
|
||||
],
|
||||
errors: [
|
||||
{
|
||||
id: 'err-1',
|
||||
message: 'Failed to connect to database',
|
||||
timestamp: new Date().toISOString(),
|
||||
stack: 'Error: Connection timeout',
|
||||
},
|
||||
],
|
||||
lastChecked: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
export const acknowledgeWarning = async (warningId: string): Promise<void> => {
|
||||
/*
|
||||
* TODO: Implement actual warning acknowledgment logic
|
||||
*/
|
||||
console.log(`Acknowledging warning ${warningId}`);
|
||||
};
|
||||
|
||||
export const acknowledgeError = async (errorId: string): Promise<void> => {
|
||||
/*
|
||||
* TODO: Implement actual error acknowledgment logic
|
||||
*/
|
||||
console.log(`Acknowledging error ${errorId}`);
|
||||
};
|
||||
35
app/lib/api/features.ts
Normal file
35
app/lib/api/features.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface Feature {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
viewed: boolean;
|
||||
releaseDate: string;
|
||||
}
|
||||
|
||||
export const getFeatureFlags = async (): Promise<Feature[]> => {
|
||||
/*
|
||||
* TODO: Implement actual feature flags logic
|
||||
* This is a mock implementation
|
||||
*/
|
||||
return [
|
||||
{
|
||||
id: 'feature-1',
|
||||
name: 'Dark Mode',
|
||||
description: 'Enable dark mode for better night viewing',
|
||||
viewed: true,
|
||||
releaseDate: '2024-03-15',
|
||||
},
|
||||
{
|
||||
id: 'feature-2',
|
||||
name: 'Tab Management',
|
||||
description: 'Customize your tab layout',
|
||||
viewed: false,
|
||||
releaseDate: '2024-03-20',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const markFeatureViewed = async (featureId: string): Promise<void> => {
|
||||
/* TODO: Implement actual feature viewed logic */
|
||||
console.log(`Marking feature ${featureId} as viewed`);
|
||||
};
|
||||
40
app/lib/api/notifications.ts
Normal file
40
app/lib/api/notifications.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export interface Notification {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
type: 'info' | 'warning' | 'error' | 'success';
|
||||
read: boolean;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export const getNotifications = async (): Promise<Notification[]> => {
|
||||
/*
|
||||
* TODO: Implement actual notifications logic
|
||||
* This is a mock implementation
|
||||
*/
|
||||
return [
|
||||
{
|
||||
id: 'notif-1',
|
||||
title: 'Welcome to Bolt',
|
||||
message: 'Get started by exploring the features',
|
||||
type: 'info',
|
||||
read: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'notif-2',
|
||||
title: 'New Update Available',
|
||||
message: 'Version 1.0.1 is now available',
|
||||
type: 'info',
|
||||
read: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const markNotificationRead = async (notificationId: string): Promise<void> => {
|
||||
/*
|
||||
* TODO: Implement actual notification read logic
|
||||
*/
|
||||
console.log(`Marking notification ${notificationId} as read`);
|
||||
};
|
||||
28
app/lib/api/updates.ts
Normal file
28
app/lib/api/updates.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export interface UpdateCheckResult {
|
||||
available: boolean;
|
||||
version: string;
|
||||
releaseNotes?: string;
|
||||
}
|
||||
|
||||
export const checkForUpdates = async (): Promise<UpdateCheckResult> => {
|
||||
/*
|
||||
* TODO: Implement actual update check logic
|
||||
* This is a mock implementation
|
||||
*/
|
||||
return {
|
||||
available: Math.random() > 0.7, // 30% chance of update
|
||||
version: '1.0.1',
|
||||
releaseNotes: 'Bug fixes and performance improvements',
|
||||
};
|
||||
};
|
||||
|
||||
export const acknowledgeUpdate = async (version: string): Promise<void> => {
|
||||
/*
|
||||
* TODO: Implement actual update acknowledgment logic
|
||||
* This is a mock implementation that would typically:
|
||||
* 1. Store the acknowledged version in a persistent store
|
||||
* 2. Update the UI state
|
||||
* 3. Potentially send analytics
|
||||
*/
|
||||
console.log(`Acknowledging update version ${version}`);
|
||||
};
|
||||
@@ -4,3 +4,8 @@ export * from './useShortcuts';
|
||||
export * from './useSnapScroll';
|
||||
export * from './useEditChatDescription';
|
||||
export { default } from './useViewport';
|
||||
export { useUpdateCheck } from './useUpdateCheck';
|
||||
export { useFeatures } from './useFeatures';
|
||||
export { useNotifications } from './useNotifications';
|
||||
export { useConnectionStatus } from './useConnectionStatus';
|
||||
export { useDebugStatus } from './useDebugStatus';
|
||||
|
||||
61
app/lib/hooks/useConnectionStatus.ts
Normal file
61
app/lib/hooks/useConnectionStatus.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { checkConnection } from '~/lib/api/connection';
|
||||
|
||||
const ACKNOWLEDGED_CONNECTION_ISSUE_KEY = 'bolt_acknowledged_connection_issue';
|
||||
|
||||
type ConnectionIssueType = 'disconnected' | 'high-latency' | null;
|
||||
|
||||
const getAcknowledgedIssue = (): string | null => {
|
||||
try {
|
||||
return localStorage.getItem(ACKNOWLEDGED_CONNECTION_ISSUE_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const useConnectionStatus = () => {
|
||||
const [hasConnectionIssues, setHasConnectionIssues] = useState(false);
|
||||
const [currentIssue, setCurrentIssue] = useState<ConnectionIssueType>(null);
|
||||
const [acknowledgedIssue, setAcknowledgedIssue] = useState<string | null>(() => getAcknowledgedIssue());
|
||||
|
||||
const checkStatus = async () => {
|
||||
try {
|
||||
const status = await checkConnection();
|
||||
const issue = !status.connected ? 'disconnected' : status.latency > 1000 ? 'high-latency' : null;
|
||||
|
||||
setCurrentIssue(issue);
|
||||
|
||||
// Only show issues if they're new or different from the acknowledged one
|
||||
setHasConnectionIssues(issue !== null && issue !== acknowledgedIssue);
|
||||
} catch (error) {
|
||||
console.error('Failed to check connection:', error);
|
||||
|
||||
// Show connection issues if we can't even check the status
|
||||
setCurrentIssue('disconnected');
|
||||
setHasConnectionIssues(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Check immediately and then every 10 seconds
|
||||
checkStatus();
|
||||
|
||||
const interval = setInterval(checkStatus, 10 * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [acknowledgedIssue]);
|
||||
|
||||
const acknowledgeIssue = () => {
|
||||
setAcknowledgedIssue(currentIssue);
|
||||
setAcknowledgedIssue(currentIssue);
|
||||
setHasConnectionIssues(false);
|
||||
};
|
||||
|
||||
const resetAcknowledgment = () => {
|
||||
setAcknowledgedIssue(null);
|
||||
setAcknowledgedIssue(null);
|
||||
checkStatus();
|
||||
};
|
||||
|
||||
return { hasConnectionIssues, currentIssue, acknowledgeIssue, resetAcknowledgment };
|
||||
};
|
||||
89
app/lib/hooks/useDebugStatus.ts
Normal file
89
app/lib/hooks/useDebugStatus.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getDebugStatus, acknowledgeWarning, acknowledgeError, type DebugIssue } from '~/lib/api/debug';
|
||||
|
||||
const ACKNOWLEDGED_DEBUG_ISSUES_KEY = 'bolt_acknowledged_debug_issues';
|
||||
|
||||
const getAcknowledgedIssues = (): string[] => {
|
||||
try {
|
||||
const stored = localStorage.getItem(ACKNOWLEDGED_DEBUG_ISSUES_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const setAcknowledgedIssues = (issueIds: string[]) => {
|
||||
try {
|
||||
localStorage.setItem(ACKNOWLEDGED_DEBUG_ISSUES_KEY, JSON.stringify(issueIds));
|
||||
} catch (error) {
|
||||
console.error('Failed to persist acknowledged debug issues:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const useDebugStatus = () => {
|
||||
const [hasActiveWarnings, setHasActiveWarnings] = useState(false);
|
||||
const [activeIssues, setActiveIssues] = useState<DebugIssue[]>([]);
|
||||
const [acknowledgedIssueIds, setAcknowledgedIssueIds] = useState<string[]>(() => getAcknowledgedIssues());
|
||||
|
||||
const checkDebugStatus = async () => {
|
||||
try {
|
||||
const status = await getDebugStatus();
|
||||
const issues: DebugIssue[] = [
|
||||
...status.warnings.map((w) => ({ ...w, type: 'warning' as const })),
|
||||
...status.errors.map((e) => ({ ...e, type: 'error' as const })),
|
||||
].filter((issue) => !acknowledgedIssueIds.includes(issue.id));
|
||||
|
||||
setActiveIssues(issues);
|
||||
setHasActiveWarnings(issues.length > 0);
|
||||
} catch (error) {
|
||||
console.error('Failed to check debug status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Check immediately and then every 5 seconds
|
||||
checkDebugStatus();
|
||||
|
||||
const interval = setInterval(checkDebugStatus, 5 * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [acknowledgedIssueIds]);
|
||||
|
||||
const acknowledgeIssue = async (issue: DebugIssue) => {
|
||||
try {
|
||||
if (issue.type === 'warning') {
|
||||
await acknowledgeWarning(issue.id);
|
||||
} else {
|
||||
await acknowledgeError(issue.id);
|
||||
}
|
||||
|
||||
const newAcknowledgedIds = [...acknowledgedIssueIds, issue.id];
|
||||
setAcknowledgedIssueIds(newAcknowledgedIds);
|
||||
setAcknowledgedIssues(newAcknowledgedIds);
|
||||
setActiveIssues((prev) => prev.filter((i) => i.id !== issue.id));
|
||||
setHasActiveWarnings(activeIssues.length > 1);
|
||||
} catch (error) {
|
||||
console.error('Failed to acknowledge issue:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const acknowledgeAllIssues = async () => {
|
||||
try {
|
||||
await Promise.all(
|
||||
activeIssues.map((issue) =>
|
||||
issue.type === 'warning' ? acknowledgeWarning(issue.id) : acknowledgeError(issue.id),
|
||||
),
|
||||
);
|
||||
|
||||
const newAcknowledgedIds = [...acknowledgedIssueIds, ...activeIssues.map((i) => i.id)];
|
||||
setAcknowledgedIssueIds(newAcknowledgedIds);
|
||||
setAcknowledgedIssues(newAcknowledgedIds);
|
||||
setActiveIssues([]);
|
||||
setHasActiveWarnings(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to acknowledge all issues:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return { hasActiveWarnings, activeIssues, acknowledgeIssue, acknowledgeAllIssues };
|
||||
};
|
||||
72
app/lib/hooks/useFeatures.ts
Normal file
72
app/lib/hooks/useFeatures.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getFeatureFlags, markFeatureViewed, type Feature } from '~/lib/api/features';
|
||||
|
||||
const VIEWED_FEATURES_KEY = 'bolt_viewed_features';
|
||||
|
||||
const getViewedFeatures = (): string[] => {
|
||||
try {
|
||||
const stored = localStorage.getItem(VIEWED_FEATURES_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const setViewedFeatures = (featureIds: string[]) => {
|
||||
try {
|
||||
localStorage.setItem(VIEWED_FEATURES_KEY, JSON.stringify(featureIds));
|
||||
} catch (error) {
|
||||
console.error('Failed to persist viewed features:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const useFeatures = () => {
|
||||
const [hasNewFeatures, setHasNewFeatures] = useState(false);
|
||||
const [unviewedFeatures, setUnviewedFeatures] = useState<Feature[]>([]);
|
||||
const [viewedFeatureIds, setViewedFeatureIds] = useState<string[]>(() => getViewedFeatures());
|
||||
|
||||
useEffect(() => {
|
||||
const checkNewFeatures = async () => {
|
||||
try {
|
||||
const features = await getFeatureFlags();
|
||||
const unviewed = features.filter((feature) => !viewedFeatureIds.includes(feature.id));
|
||||
setUnviewedFeatures(unviewed);
|
||||
setHasNewFeatures(unviewed.length > 0);
|
||||
} catch (error) {
|
||||
console.error('Failed to check for new features:', error);
|
||||
}
|
||||
};
|
||||
|
||||
checkNewFeatures();
|
||||
}, [viewedFeatureIds]);
|
||||
|
||||
const acknowledgeFeature = async (featureId: string) => {
|
||||
try {
|
||||
await markFeatureViewed(featureId);
|
||||
|
||||
const newViewedIds = [...viewedFeatureIds, featureId];
|
||||
setViewedFeatureIds(newViewedIds);
|
||||
setViewedFeatures(newViewedIds);
|
||||
setUnviewedFeatures((prev) => prev.filter((feature) => feature.id !== featureId));
|
||||
setHasNewFeatures(unviewedFeatures.length > 1);
|
||||
} catch (error) {
|
||||
console.error('Failed to acknowledge feature:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const acknowledgeAllFeatures = async () => {
|
||||
try {
|
||||
await Promise.all(unviewedFeatures.map((feature) => markFeatureViewed(feature.id)));
|
||||
|
||||
const newViewedIds = [...viewedFeatureIds, ...unviewedFeatures.map((f) => f.id)];
|
||||
setViewedFeatureIds(newViewedIds);
|
||||
setViewedFeatures(newViewedIds);
|
||||
setUnviewedFeatures([]);
|
||||
setHasNewFeatures(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to acknowledge all features:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return { hasNewFeatures, unviewedFeatures, acknowledgeFeature, acknowledgeAllFeatures };
|
||||
};
|
||||
77
app/lib/hooks/useNotifications.ts
Normal file
77
app/lib/hooks/useNotifications.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getNotifications, markNotificationRead, type Notification } from '~/lib/api/notifications';
|
||||
|
||||
const READ_NOTIFICATIONS_KEY = 'bolt_read_notifications';
|
||||
|
||||
const getReadNotifications = (): string[] => {
|
||||
try {
|
||||
const stored = localStorage.getItem(READ_NOTIFICATIONS_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const setReadNotifications = (notificationIds: string[]) => {
|
||||
try {
|
||||
localStorage.setItem(READ_NOTIFICATIONS_KEY, JSON.stringify(notificationIds));
|
||||
} catch (error) {
|
||||
console.error('Failed to persist read notifications:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const useNotifications = () => {
|
||||
const [hasUnreadNotifications, setHasUnreadNotifications] = useState(false);
|
||||
const [unreadNotifications, setUnreadNotifications] = useState<Notification[]>([]);
|
||||
const [readNotificationIds, setReadNotificationIds] = useState<string[]>(() => getReadNotifications());
|
||||
|
||||
const checkNotifications = async () => {
|
||||
try {
|
||||
const notifications = await getNotifications();
|
||||
const unread = notifications.filter((n) => !readNotificationIds.includes(n.id));
|
||||
setUnreadNotifications(unread);
|
||||
setHasUnreadNotifications(unread.length > 0);
|
||||
} catch (error) {
|
||||
console.error('Failed to check notifications:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Check immediately and then every minute
|
||||
checkNotifications();
|
||||
|
||||
const interval = setInterval(checkNotifications, 60 * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [readNotificationIds]);
|
||||
|
||||
const markAsRead = async (notificationId: string) => {
|
||||
try {
|
||||
await markNotificationRead(notificationId);
|
||||
|
||||
const newReadIds = [...readNotificationIds, notificationId];
|
||||
setReadNotificationIds(newReadIds);
|
||||
setReadNotifications(newReadIds);
|
||||
setUnreadNotifications((prev) => prev.filter((n) => n.id !== notificationId));
|
||||
setHasUnreadNotifications(unreadNotifications.length > 1);
|
||||
} catch (error) {
|
||||
console.error('Failed to mark notification as read:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const markAllAsRead = async () => {
|
||||
try {
|
||||
await Promise.all(unreadNotifications.map((n) => markNotificationRead(n.id)));
|
||||
|
||||
const newReadIds = [...readNotificationIds, ...unreadNotifications.map((n) => n.id)];
|
||||
setReadNotificationIds(newReadIds);
|
||||
setReadNotifications(newReadIds);
|
||||
setUnreadNotifications([]);
|
||||
setHasUnreadNotifications(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to mark all notifications as read:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return { hasUnreadNotifications, unreadNotifications, markAsRead, markAllAsRead };
|
||||
};
|
||||
222
app/lib/hooks/useSettings.ts
Normal file
222
app/lib/hooks/useSettings.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import {
|
||||
isDebugMode,
|
||||
isEventLogsEnabled,
|
||||
isLocalModelsEnabled,
|
||||
LOCAL_PROVIDERS,
|
||||
promptStore,
|
||||
providersStore,
|
||||
latestBranchStore,
|
||||
autoSelectStarterTemplate,
|
||||
enableContextOptimizationStore,
|
||||
tabConfigurationStore,
|
||||
updateTabConfiguration as updateTabConfig,
|
||||
resetTabConfiguration as resetTabConfig,
|
||||
} from '~/lib/stores/settings';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import Cookies from 'js-cookie';
|
||||
import type { IProviderSetting, ProviderInfo, IProviderConfig } from '~/types/model';
|
||||
import type { TabWindowConfig, TabVisibilityConfig } from '~/components/settings/settings.types';
|
||||
import { logStore } from '~/lib/stores/logs';
|
||||
import { getLocalStorage, setLocalStorage } from '~/utils/localStorage';
|
||||
|
||||
export interface Settings {
|
||||
theme: 'light' | 'dark' | 'system';
|
||||
language: string;
|
||||
notifications: boolean;
|
||||
eventLogs: boolean;
|
||||
timezone: string;
|
||||
tabConfiguration: TabWindowConfig;
|
||||
}
|
||||
|
||||
export interface UseSettingsReturn {
|
||||
// Theme and UI settings
|
||||
setTheme: (theme: Settings['theme']) => void;
|
||||
setLanguage: (language: string) => void;
|
||||
setNotifications: (enabled: boolean) => void;
|
||||
setEventLogs: (enabled: boolean) => void;
|
||||
setTimezone: (timezone: string) => void;
|
||||
settings: Settings;
|
||||
|
||||
// Provider settings
|
||||
providers: Record<string, IProviderConfig>;
|
||||
activeProviders: ProviderInfo[];
|
||||
updateProviderSettings: (provider: string, config: IProviderSetting) => void;
|
||||
isLocalModel: boolean;
|
||||
enableLocalModels: (enabled: boolean) => void;
|
||||
|
||||
// Debug and development settings
|
||||
debug: boolean;
|
||||
enableDebugMode: (enabled: boolean) => void;
|
||||
eventLogs: boolean;
|
||||
promptId: string;
|
||||
setPromptId: (promptId: string) => void;
|
||||
isLatestBranch: boolean;
|
||||
enableLatestBranch: (enabled: boolean) => void;
|
||||
autoSelectTemplate: boolean;
|
||||
setAutoSelectTemplate: (enabled: boolean) => void;
|
||||
contextOptimizationEnabled: boolean;
|
||||
enableContextOptimization: (enabled: boolean) => void;
|
||||
|
||||
// Tab configuration
|
||||
tabConfiguration: TabWindowConfig;
|
||||
updateTabConfiguration: (config: TabVisibilityConfig) => void;
|
||||
resetTabConfiguration: () => void;
|
||||
}
|
||||
|
||||
export function useSettings(): UseSettingsReturn {
|
||||
const providers = useStore(providersStore) as Record<string, IProviderConfig>;
|
||||
const debug = useStore(isDebugMode);
|
||||
const eventLogs = useStore(isEventLogsEnabled);
|
||||
const promptId = useStore(promptStore);
|
||||
const isLocalModel = useStore(isLocalModelsEnabled);
|
||||
const isLatestBranch = useStore(latestBranchStore);
|
||||
const autoSelectTemplate = useStore(autoSelectStarterTemplate);
|
||||
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
|
||||
const contextOptimizationEnabled = useStore(enableContextOptimizationStore);
|
||||
const tabConfiguration = useStore(tabConfigurationStore);
|
||||
const [settings, setSettings] = useState<Settings>(() => {
|
||||
const storedSettings = getLocalStorage('settings');
|
||||
return {
|
||||
theme: storedSettings?.theme || 'system',
|
||||
language: storedSettings?.language || 'en',
|
||||
notifications: storedSettings?.notifications ?? true,
|
||||
eventLogs: storedSettings?.eventLogs ?? true,
|
||||
timezone: storedSettings?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
tabConfiguration,
|
||||
};
|
||||
});
|
||||
|
||||
// writing values to cookies on change
|
||||
useEffect(() => {
|
||||
const providers = providersStore.get();
|
||||
const providerSetting: Record<string, IProviderSetting> = {};
|
||||
Object.keys(providers).forEach((provider) => {
|
||||
providerSetting[provider] = providers[provider].settings;
|
||||
});
|
||||
Cookies.set('providers', JSON.stringify(providerSetting));
|
||||
}, [providers]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = Object.entries(providers)
|
||||
.filter(([_key, provider]) => provider.settings.enabled)
|
||||
.map(([_k, p]) => p);
|
||||
|
||||
if (!isLocalModel) {
|
||||
active = active.filter((p) => !LOCAL_PROVIDERS.includes(p.name));
|
||||
}
|
||||
|
||||
setActiveProviders(active);
|
||||
}, [providers, isLocalModel]);
|
||||
|
||||
const saveSettings = useCallback((newSettings: Partial<Settings>) => {
|
||||
setSettings((prev) => {
|
||||
const updated = { ...prev, ...newSettings };
|
||||
setLocalStorage('settings', updated);
|
||||
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateProviderSettings = useCallback((provider: string, config: IProviderSetting) => {
|
||||
providersStore.setKey(provider, { settings: config } as IProviderConfig);
|
||||
}, []);
|
||||
|
||||
const enableDebugMode = useCallback((enabled: boolean) => {
|
||||
isDebugMode.set(enabled);
|
||||
logStore.logSystem(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('isDebugEnabled', String(enabled));
|
||||
}, []);
|
||||
|
||||
const setEventLogs = 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));
|
||||
}, []);
|
||||
|
||||
const setPromptId = useCallback((promptId: string) => {
|
||||
promptStore.set(promptId);
|
||||
Cookies.set('promptId', promptId);
|
||||
}, []);
|
||||
|
||||
const enableLatestBranch = useCallback((enabled: boolean) => {
|
||||
latestBranchStore.set(enabled);
|
||||
logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('isLatestBranch', String(enabled));
|
||||
}, []);
|
||||
|
||||
const setAutoSelectTemplate = useCallback((enabled: boolean) => {
|
||||
autoSelectStarterTemplate.set(enabled);
|
||||
logStore.logSystem(`Auto select template ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('autoSelectTemplate', String(enabled));
|
||||
}, []);
|
||||
|
||||
const enableContextOptimization = useCallback((enabled: boolean) => {
|
||||
enableContextOptimizationStore.set(enabled);
|
||||
logStore.logSystem(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('contextOptimizationEnabled', String(enabled));
|
||||
}, []);
|
||||
|
||||
const setTheme = useCallback(
|
||||
(theme: Settings['theme']) => {
|
||||
saveSettings({ theme });
|
||||
},
|
||||
[saveSettings],
|
||||
);
|
||||
|
||||
const setLanguage = useCallback(
|
||||
(language: string) => {
|
||||
saveSettings({ language });
|
||||
},
|
||||
[saveSettings],
|
||||
);
|
||||
|
||||
const setNotifications = useCallback(
|
||||
(enabled: boolean) => {
|
||||
saveSettings({ notifications: enabled });
|
||||
},
|
||||
[saveSettings],
|
||||
);
|
||||
|
||||
const setTimezone = useCallback(
|
||||
(timezone: string) => {
|
||||
saveSettings({ timezone });
|
||||
},
|
||||
[saveSettings],
|
||||
);
|
||||
|
||||
return {
|
||||
...settings,
|
||||
providers,
|
||||
activeProviders,
|
||||
updateProviderSettings,
|
||||
isLocalModel,
|
||||
enableLocalModels,
|
||||
debug,
|
||||
enableDebugMode,
|
||||
eventLogs,
|
||||
setEventLogs,
|
||||
promptId,
|
||||
setPromptId,
|
||||
isLatestBranch,
|
||||
enableLatestBranch,
|
||||
autoSelectTemplate,
|
||||
setAutoSelectTemplate,
|
||||
contextOptimizationEnabled,
|
||||
enableContextOptimization,
|
||||
setTheme,
|
||||
setLanguage,
|
||||
setNotifications,
|
||||
setTimezone,
|
||||
settings,
|
||||
tabConfiguration,
|
||||
updateTabConfiguration: updateTabConfig,
|
||||
resetTabConfiguration: resetTabConfig,
|
||||
};
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import {
|
||||
isDebugMode,
|
||||
isEventLogsEnabled,
|
||||
isLocalModelsEnabled,
|
||||
LOCAL_PROVIDERS,
|
||||
promptStore,
|
||||
providersStore,
|
||||
latestBranchStore,
|
||||
autoSelectStarterTemplate,
|
||||
enableContextOptimizationStore,
|
||||
} 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
|
||||
|
||||
interface CommitData {
|
||||
commit: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
const versionData: CommitData = {
|
||||
commit: __COMMIT_HASH,
|
||||
version: __APP_VERSION,
|
||||
};
|
||||
|
||||
export function useSettings() {
|
||||
const providers = useStore(providersStore);
|
||||
const debug = useStore(isDebugMode);
|
||||
const eventLogs = useStore(isEventLogsEnabled);
|
||||
const promptId = useStore(promptStore);
|
||||
const isLocalModel = useStore(isLocalModelsEnabled);
|
||||
const isLatestBranch = useStore(latestBranchStore);
|
||||
const autoSelectTemplate = useStore(autoSelectStarterTemplate);
|
||||
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
|
||||
const contextOptimizationEnabled = useStore(enableContextOptimizationStore);
|
||||
|
||||
// Function to check if we're on stable version
|
||||
const checkIsStableVersion = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.github.com/repos/stackblitz-labs/bolt.diy/git/refs/tags/v${versionData.version}`,
|
||||
);
|
||||
const data: { object: { sha: string } } = await response.json();
|
||||
|
||||
return versionData.commit.slice(0, 7) === data.object.sha.slice(0, 7);
|
||||
} catch (error) {
|
||||
console.warn('Error checking stable version:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// reading values from cookies on mount
|
||||
useEffect(() => {
|
||||
const savedProviders = Cookies.get('providers');
|
||||
|
||||
if (savedProviders) {
|
||||
try {
|
||||
const parsedProviders: Record<string, IProviderSetting> = JSON.parse(savedProviders);
|
||||
Object.keys(providers).forEach((provider) => {
|
||||
const currentProviderSettings = parsedProviders[provider];
|
||||
|
||||
if (currentProviderSettings) {
|
||||
providersStore.setKey(provider, {
|
||||
...providers[provider],
|
||||
settings: {
|
||||
...currentProviderSettings,
|
||||
enabled: currentProviderSettings.enabled ?? true,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to parse providers from cookies:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// load debug mode from cookies
|
||||
const savedDebugMode = Cookies.get('isDebugEnabled');
|
||||
|
||||
if (savedDebugMode) {
|
||||
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');
|
||||
|
||||
if (savedLocalModels) {
|
||||
isLocalModelsEnabled.set(savedLocalModels === 'true');
|
||||
}
|
||||
|
||||
const promptId = Cookies.get('promptId');
|
||||
|
||||
if (promptId) {
|
||||
promptStore.set(promptId);
|
||||
}
|
||||
|
||||
// load latest branch setting from cookies or determine based on version
|
||||
const savedLatestBranch = Cookies.get('isLatestBranch');
|
||||
let checkCommit = Cookies.get('commitHash');
|
||||
|
||||
if (checkCommit === undefined) {
|
||||
checkCommit = versionData.commit;
|
||||
}
|
||||
|
||||
if (savedLatestBranch === undefined || checkCommit !== versionData.commit) {
|
||||
// If setting hasn't been set by user, check version
|
||||
checkIsStableVersion().then((isStable) => {
|
||||
const shouldUseLatest = !isStable;
|
||||
latestBranchStore.set(shouldUseLatest);
|
||||
Cookies.set('isLatestBranch', String(shouldUseLatest));
|
||||
Cookies.set('commitHash', String(versionData.commit));
|
||||
});
|
||||
} else {
|
||||
latestBranchStore.set(savedLatestBranch === 'true');
|
||||
}
|
||||
|
||||
const autoSelectTemplate = Cookies.get('autoSelectTemplate');
|
||||
|
||||
if (autoSelectTemplate) {
|
||||
autoSelectStarterTemplate.set(autoSelectTemplate === 'true');
|
||||
}
|
||||
|
||||
const savedContextOptimizationEnabled = Cookies.get('contextOptimizationEnabled');
|
||||
|
||||
if (savedContextOptimizationEnabled) {
|
||||
enableContextOptimizationStore.set(savedContextOptimizationEnabled === 'true');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// writing values to cookies on change
|
||||
useEffect(() => {
|
||||
const providers = providersStore.get();
|
||||
const providerSetting: Record<string, IProviderSetting> = {};
|
||||
Object.keys(providers).forEach((provider) => {
|
||||
providerSetting[provider] = providers[provider].settings;
|
||||
});
|
||||
Cookies.set('providers', JSON.stringify(providerSetting));
|
||||
}, [providers]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = Object.entries(providers)
|
||||
.filter(([_key, provider]) => provider.settings.enabled)
|
||||
.map(([_k, p]) => p);
|
||||
|
||||
if (!isLocalModel) {
|
||||
active = active.filter((p) => !LOCAL_PROVIDERS.includes(p.name));
|
||||
}
|
||||
|
||||
setActiveProviders(active);
|
||||
}, [providers, isLocalModel]);
|
||||
|
||||
// helper function to update settings
|
||||
const updateProviderSettings = useCallback(
|
||||
(provider: string, config: IProviderSetting) => {
|
||||
const settings = providers[provider].settings;
|
||||
providersStore.setKey(provider, { ...providers[provider], settings: { ...settings, ...config } });
|
||||
},
|
||||
[providers],
|
||||
);
|
||||
|
||||
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));
|
||||
}, []);
|
||||
|
||||
const setPromptId = useCallback((promptId: string) => {
|
||||
promptStore.set(promptId);
|
||||
Cookies.set('promptId', promptId);
|
||||
}, []);
|
||||
const enableLatestBranch = useCallback((enabled: boolean) => {
|
||||
latestBranchStore.set(enabled);
|
||||
logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('isLatestBranch', String(enabled));
|
||||
}, []);
|
||||
|
||||
const setAutoSelectTemplate = useCallback((enabled: boolean) => {
|
||||
autoSelectStarterTemplate.set(enabled);
|
||||
logStore.logSystem(`Auto select template ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('autoSelectTemplate', String(enabled));
|
||||
}, []);
|
||||
|
||||
const enableContextOptimization = useCallback((enabled: boolean) => {
|
||||
enableContextOptimizationStore.set(enabled);
|
||||
logStore.logSystem(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('contextOptimizationEnabled', String(enabled));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
providers,
|
||||
activeProviders,
|
||||
updateProviderSettings,
|
||||
debug,
|
||||
enableDebugMode,
|
||||
eventLogs,
|
||||
enableEventLogs,
|
||||
isLocalModel,
|
||||
enableLocalModels,
|
||||
promptId,
|
||||
setPromptId,
|
||||
isLatestBranch,
|
||||
enableLatestBranch,
|
||||
autoSelectTemplate,
|
||||
setAutoSelectTemplate,
|
||||
contextOptimizationEnabled,
|
||||
enableContextOptimization,
|
||||
};
|
||||
}
|
||||
58
app/lib/hooks/useUpdateCheck.ts
Normal file
58
app/lib/hooks/useUpdateCheck.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { checkForUpdates, acknowledgeUpdate } from '~/lib/api/updates';
|
||||
|
||||
const LAST_ACKNOWLEDGED_VERSION_KEY = 'bolt_last_acknowledged_version';
|
||||
|
||||
export const useUpdateCheck = () => {
|
||||
const [hasUpdate, setHasUpdate] = useState(false);
|
||||
const [currentVersion, setCurrentVersion] = useState<string>('');
|
||||
const [lastAcknowledgedVersion, setLastAcknowledgedVersion] = useState<string | null>(() => {
|
||||
try {
|
||||
return localStorage.getItem(LAST_ACKNOWLEDGED_VERSION_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const checkUpdate = async () => {
|
||||
try {
|
||||
const { available, version } = await checkForUpdates();
|
||||
setCurrentVersion(version);
|
||||
|
||||
// Only show update if it's a new version and hasn't been acknowledged
|
||||
setHasUpdate(available && version !== lastAcknowledgedVersion);
|
||||
} catch (error) {
|
||||
console.error('Failed to check for updates:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Check immediately and then every 30 minutes
|
||||
checkUpdate();
|
||||
|
||||
const interval = setInterval(checkUpdate, 30 * 60 * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [lastAcknowledgedVersion]);
|
||||
|
||||
const handleAcknowledgeUpdate = async () => {
|
||||
try {
|
||||
const { version } = await checkForUpdates();
|
||||
await acknowledgeUpdate(version);
|
||||
|
||||
// Store in localStorage
|
||||
try {
|
||||
localStorage.setItem(LAST_ACKNOWLEDGED_VERSION_KEY, version);
|
||||
} catch (error) {
|
||||
console.error('Failed to persist acknowledged version:', error);
|
||||
}
|
||||
|
||||
setLastAcknowledgedVersion(version);
|
||||
setHasUpdate(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to acknowledge update:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return { hasUpdate, currentVersion, acknowledgeUpdate: handleAcknowledgeUpdate };
|
||||
};
|
||||
@@ -11,7 +11,8 @@ export default class GithubProvider extends BaseProvider {
|
||||
config = {
|
||||
apiTokenKey: 'GITHUB_API_KEY',
|
||||
};
|
||||
// find more in https://github.com/marketplace?type=models
|
||||
|
||||
// find more in https://github.com/marketplace?type=models
|
||||
staticModels: ModelInfo[] = [
|
||||
{ name: 'gpt-4o', label: 'GPT-4o', provider: 'Github', maxTokenAllowed: 8000 },
|
||||
{ name: 'o1', label: 'o1-preview', provider: 'Github', maxTokenAllowed: 100000 },
|
||||
|
||||
@@ -10,7 +10,10 @@ export interface LogEntry {
|
||||
level: 'info' | 'warning' | 'error' | 'debug';
|
||||
message: string;
|
||||
details?: Record<string, any>;
|
||||
category: 'system' | 'provider' | 'user' | 'error';
|
||||
category: 'system' | 'provider' | 'user' | 'error' | 'api' | 'auth' | 'database' | 'network';
|
||||
subCategory?: string;
|
||||
duration?: number;
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
const MAX_LOGS = 1000; // Maximum number of logs to keep in memory
|
||||
@@ -101,18 +104,76 @@ class LogStore {
|
||||
return this.addLog(message, 'info', 'user', details);
|
||||
}
|
||||
|
||||
// API Connection Logging
|
||||
logAPIRequest(endpoint: string, method: string, duration: number, statusCode: number, details?: Record<string, any>) {
|
||||
const message = `${method} ${endpoint} - ${statusCode} (${duration}ms)`;
|
||||
const level = statusCode >= 400 ? 'error' : statusCode >= 300 ? 'warning' : 'info';
|
||||
|
||||
return this.addLog(message, level, 'api', {
|
||||
...details,
|
||||
endpoint,
|
||||
method,
|
||||
duration,
|
||||
statusCode,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Authentication Logging
|
||||
logAuth(
|
||||
action: 'login' | 'logout' | 'token_refresh' | 'key_validation',
|
||||
success: boolean,
|
||||
details?: Record<string, any>,
|
||||
) {
|
||||
const message = `Auth ${action} - ${success ? 'Success' : 'Failed'}`;
|
||||
const level = success ? 'info' : 'error';
|
||||
|
||||
return this.addLog(message, level, 'auth', {
|
||||
...details,
|
||||
action,
|
||||
success,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Network Status Logging
|
||||
logNetworkStatus(status: 'online' | 'offline' | 'reconnecting' | 'connected', details?: Record<string, any>) {
|
||||
const message = `Network ${status}`;
|
||||
const level = status === 'offline' ? 'error' : status === 'reconnecting' ? 'warning' : 'info';
|
||||
|
||||
return this.addLog(message, level, 'network', {
|
||||
...details,
|
||||
status,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Database Operations Logging
|
||||
logDatabase(operation: string, success: boolean, duration: number, details?: Record<string, any>) {
|
||||
const message = `DB ${operation} - ${success ? 'Success' : 'Failed'} (${duration}ms)`;
|
||||
const level = success ? 'info' : 'error';
|
||||
|
||||
return this.addLog(message, level, 'database', {
|
||||
...details,
|
||||
operation,
|
||||
success,
|
||||
duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
// 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,
|
||||
};
|
||||
const errorDetails =
|
||||
error instanceof Error
|
||||
? {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
...details,
|
||||
}
|
||||
: { error, ...details };
|
||||
|
||||
return this.addLog(message, 'error', 'error', errorDetails);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ import { atom, map } from 'nanostores';
|
||||
import { workbenchStore } from './workbench';
|
||||
import { PROVIDER_LIST } from '~/utils/constants';
|
||||
import type { IProviderConfig } from '~/types/model';
|
||||
import type { TabVisibilityConfig, TabWindowConfig } from '~/components/settings/settings.types';
|
||||
import { DEFAULT_TAB_CONFIG } from '~/components/settings/settings.types';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
export interface Shortcut {
|
||||
key: string;
|
||||
@@ -46,7 +49,9 @@ export const providersStore = map<ProviderSetting>(initialProviderSettings);
|
||||
|
||||
export const isDebugMode = atom(false);
|
||||
|
||||
export const isEventLogsEnabled = atom(false);
|
||||
// Initialize event logs from cookie or default to false
|
||||
const savedEventLogs = Cookies.get('isEventLogsEnabled');
|
||||
export const isEventLogsEnabled = atom(savedEventLogs === 'true');
|
||||
|
||||
export const isLocalModelsEnabled = atom(true);
|
||||
|
||||
@@ -56,3 +61,48 @@ export const latestBranchStore = atom(false);
|
||||
|
||||
export const autoSelectStarterTemplate = atom(false);
|
||||
export const enableContextOptimizationStore = atom(false);
|
||||
|
||||
// Initialize tab configuration from cookie or default
|
||||
const savedTabConfig = Cookies.get('tabConfiguration');
|
||||
const initialTabConfig: TabWindowConfig = savedTabConfig
|
||||
? JSON.parse(savedTabConfig)
|
||||
: {
|
||||
userTabs: DEFAULT_TAB_CONFIG.filter((tab) => tab.window === 'user'),
|
||||
developerTabs: DEFAULT_TAB_CONFIG.filter((tab) => tab.window === 'developer'),
|
||||
};
|
||||
|
||||
export const tabConfigurationStore = map<TabWindowConfig>(initialTabConfig);
|
||||
|
||||
// Helper function to update tab configuration
|
||||
export const updateTabConfiguration = (config: TabVisibilityConfig) => {
|
||||
const currentConfig = tabConfigurationStore.get();
|
||||
const isUserTab = config.window === 'user';
|
||||
const targetArray = isUserTab ? 'userTabs' : 'developerTabs';
|
||||
|
||||
// Only update the tab in its respective window
|
||||
const updatedTabs = currentConfig[targetArray].map((tab) => (tab.id === config.id ? { ...config } : tab));
|
||||
|
||||
// If tab doesn't exist in this window yet, add it
|
||||
if (!updatedTabs.find((tab) => tab.id === config.id)) {
|
||||
updatedTabs.push(config);
|
||||
}
|
||||
|
||||
// Create new config, only updating the target window's tabs
|
||||
const newConfig: TabWindowConfig = {
|
||||
...currentConfig,
|
||||
[targetArray]: updatedTabs,
|
||||
};
|
||||
|
||||
tabConfigurationStore.set(newConfig);
|
||||
Cookies.set('tabConfiguration', JSON.stringify(newConfig));
|
||||
};
|
||||
|
||||
// Helper function to reset tab configuration
|
||||
export const resetTabConfiguration = () => {
|
||||
const defaultConfig: TabWindowConfig = {
|
||||
userTabs: DEFAULT_TAB_CONFIG.filter((tab) => tab.window === 'user'),
|
||||
developerTabs: DEFAULT_TAB_CONFIG.filter((tab) => tab.window === 'developer'),
|
||||
};
|
||||
tabConfigurationStore.set(defaultConfig);
|
||||
Cookies.set('tabConfiguration', JSON.stringify(defaultConfig));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user