Final UI V3

# UI V3 Changelog

Major updates and improvements in this release:

## Core Changes
- Complete NEW REWRITTEN UI system overhaul (V3) with semantic design tokens
- New settings management system with drag-and-drop capabilities
- Enhanced provider system supporting multiple AI services
- Improved theme system with better dark mode support
- New component library with consistent design patterns

## Technical Updates
- Reorganized project architecture for better maintainability
- Performance optimizations and bundle size improvements
- Enhanced security features and access controls
- Improved developer experience with better tooling
- Comprehensive testing infrastructure

## New Features
- Background rays effect for improved visual feedback
- Advanced tab management system
- Automatic and manual update support
- Enhanced error handling and visualization
- Improved accessibility across all components

For detailed information about all changes and improvements, please see the full changelog.
This commit is contained in:
Stijnus
2025-02-02 01:42:30 +01:00
parent 999d87b1e8
commit fc3dd8c84c
76 changed files with 4540 additions and 4936 deletions

View File

@@ -5,14 +5,59 @@ export interface ConnectionStatus {
}
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(),
};
try {
// Check if we have network connectivity
const online = navigator.onLine;
if (!online) {
return {
connected: false,
latency: 0,
lastChecked: new Date().toISOString(),
};
}
// Try multiple endpoints in case one fails
const endpoints = [
'/api/health',
'/', // Fallback to root route
'/favicon.ico', // Another common fallback
];
let latency = 0;
let connected = false;
for (const endpoint of endpoints) {
try {
const start = performance.now();
const response = await fetch(endpoint, {
method: 'HEAD',
cache: 'no-cache',
});
const end = performance.now();
if (response.ok) {
latency = Math.round(end - start);
connected = true;
break;
}
} catch (endpointError) {
console.debug(`Failed to connect to ${endpoint}:`, endpointError);
continue;
}
}
return {
connected,
latency,
lastChecked: new Date().toISOString(),
};
} catch (error) {
console.error('Connection check failed:', error);
return {
connected: false,
latency: 0,
lastChecked: new Date().toISOString(),
};
}
};

View File

@@ -13,53 +13,109 @@ export interface DebugError {
}
export interface DebugStatus {
warnings: DebugWarning[];
errors: DebugError[];
lastChecked: string;
warnings: DebugIssue[];
errors: DebugIssue[];
}
export interface DebugIssue {
id: string;
type: 'warning' | 'error';
message: string;
type: 'warning' | 'error';
timestamp: string;
details?: Record<string, unknown>;
}
// Keep track of acknowledged issues
const acknowledgedIssues = new Set<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(),
const issues: DebugStatus = {
warnings: [],
errors: [],
};
try {
// Check memory usage
if (performance && 'memory' in performance) {
const memory = (performance as any).memory;
if (memory.usedJSHeapSize > memory.jsHeapSizeLimit * 0.8) {
issues.warnings.push({
id: 'high-memory-usage',
message: 'High memory usage detected',
type: 'warning',
timestamp: new Date().toISOString(),
details: {
used: memory.usedJSHeapSize,
total: memory.jsHeapSizeLimit,
},
});
}
}
// Check storage quota
if (navigator.storage && navigator.storage.estimate) {
const estimate = await navigator.storage.estimate();
const usageRatio = (estimate.usage || 0) / (estimate.quota || 1);
if (usageRatio > 0.9) {
issues.warnings.push({
id: 'storage-quota-warning',
message: 'Storage quota nearly reached',
type: 'warning',
timestamp: new Date().toISOString(),
details: {
used: estimate.usage,
quota: estimate.quota,
},
});
}
}
// Check for console errors (if any)
const errorLogs = localStorage.getItem('error_logs');
if (errorLogs) {
const errors = JSON.parse(errorLogs);
errors.forEach((error: any) => {
issues.errors.push({
id: `error-${error.timestamp}`,
message: error.message,
type: 'error',
timestamp: error.timestamp,
details: error.details,
});
});
}
// Filter out acknowledged issues
issues.warnings = issues.warnings.filter((warning) => !acknowledgedIssues.has(warning.id));
issues.errors = issues.errors.filter((error) => !acknowledgedIssues.has(error.id));
return issues;
} catch (error) {
console.error('Error getting debug status:', error);
return issues;
}
};
export const acknowledgeWarning = async (warningId: string): Promise<void> => {
/*
* TODO: Implement actual warning acknowledgment logic
*/
console.log(`Acknowledging warning ${warningId}`);
export const acknowledgeWarning = async (id: string): Promise<void> => {
acknowledgedIssues.add(id);
};
export const acknowledgeError = async (errorId: string): Promise<void> => {
/*
* TODO: Implement actual error acknowledgment logic
*/
console.log(`Acknowledging error ${errorId}`);
export const acknowledgeError = async (id: string): Promise<void> => {
acknowledgedIssues.add(id);
// Also remove from error logs if present
try {
const errorLogs = localStorage.getItem('error_logs');
if (errorLogs) {
const errors = JSON.parse(errorLogs);
const updatedErrors = errors.filter((error: any) => `error-${error.timestamp}` !== id);
localStorage.setItem('error_logs', JSON.stringify(updatedErrors));
}
} catch (error) {
console.error('Error acknowledging error:', error);
}
};

View File

@@ -1,71 +1,35 @@
import { logStore, type LogEntry } from '~/lib/stores/logs';
export type NotificationType = 'info' | 'warning' | 'error' | 'success' | 'update';
export interface NotificationDetails {
type?: string;
message?: string;
currentVersion?: string;
latestVersion?: string;
branch?: string;
updateUrl?: string;
}
import { logStore } from '~/lib/stores/logs';
import type { LogEntry } from '~/lib/stores/logs';
export interface Notification {
id: string;
title: string;
message: string;
type: NotificationType;
read: boolean;
type: 'info' | 'warning' | 'error' | 'success';
timestamp: string;
details?: NotificationDetails;
read: boolean;
details?: Record<string, unknown>;
}
interface LogEntryWithRead extends LogEntry {
read?: boolean;
export interface LogEntryWithRead extends LogEntry {
read: boolean;
}
const mapLogToNotification = (log: LogEntryWithRead): Notification => {
const type: NotificationType =
log.details?.type === 'update'
? 'update'
: log.level === 'error'
? 'error'
: log.level === 'warning'
? 'warning'
: 'info';
const baseNotification: Notification = {
id: log.id,
title: log.category.charAt(0).toUpperCase() + log.category.slice(1),
message: log.message,
type,
read: log.read || false,
timestamp: log.timestamp,
};
if (log.details) {
return {
...baseNotification,
details: log.details as NotificationDetails,
};
}
return baseNotification;
};
export const getNotifications = async (): Promise<Notification[]> => {
const logs = Object.values(logStore.logs.get()) as LogEntryWithRead[];
// Get notifications from the log store
const logs = Object.values(logStore.logs.get());
return logs
.filter((log) => {
if (log.details?.type === 'update') {
return true;
}
return log.level === 'error' || log.level === 'warning';
})
.map(mapLogToNotification)
.filter((log) => log.category !== 'system') // Filter out system logs
.map((log) => ({
id: log.id,
title: (log.details?.title as string) || log.message.split('\n')[0],
message: log.message,
type: log.level as 'info' | 'warning' | 'error' | 'success',
timestamp: log.timestamp,
read: logStore.isRead(log.id),
details: log.details,
}))
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
};
@@ -81,7 +45,7 @@ export const getUnreadCount = (): number => {
const logs = Object.values(logStore.logs.get()) as LogEntryWithRead[];
return logs.filter((log) => {
if (!log.read) {
if (!logStore.isRead(log.id)) {
if (log.details?.type === 'update') {
return true;
}

View File

@@ -2,27 +2,107 @@ export interface UpdateCheckResult {
available: boolean;
version: string;
releaseNotes?: string;
error?: {
type: 'rate_limit' | 'network' | 'auth' | 'unknown';
message: string;
};
}
interface PackageJson {
version: string;
name: string;
[key: string]: unknown;
}
function compareVersions(v1: string, v2: string): number {
// Remove 'v' prefix if present
const version1 = v1.replace(/^v/, '');
const version2 = v2.replace(/^v/, '');
const parts1 = version1.split('.').map(Number);
const parts2 = version2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const part1 = parts1[i] || 0;
const part2 = parts2[i] || 0;
if (part1 !== part2) {
return part1 - part2;
}
}
return 0;
}
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',
};
try {
// Get the current version from local package.json
const packageResponse = await fetch('/package.json');
if (!packageResponse.ok) {
throw new Error('Failed to fetch local package.json');
}
const packageData = (await packageResponse.json()) as PackageJson;
if (!packageData.version || typeof packageData.version !== 'string') {
throw new Error('Invalid package.json format: missing or invalid version');
}
const currentVersion = packageData.version;
/*
* Get the latest version from GitHub's main branch package.json
* Using raw.githubusercontent.com which doesn't require authentication
*/
const latestPackageResponse = await fetch(
'https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/main/package.json',
);
if (!latestPackageResponse.ok) {
throw new Error(`Failed to fetch latest package.json: ${latestPackageResponse.status}`);
}
const latestPackageData = (await latestPackageResponse.json()) as PackageJson;
if (!latestPackageData.version || typeof latestPackageData.version !== 'string') {
throw new Error('Invalid remote package.json format: missing or invalid version');
}
const latestVersion = latestPackageData.version;
// Compare versions semantically
const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
return {
available: hasUpdate,
version: latestVersion,
releaseNotes: hasUpdate ? 'Update available. Check GitHub for release notes.' : undefined,
};
} catch (error) {
console.error('Error checking for updates:', error);
// Determine error type
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
const isNetworkError =
errorMessage.toLowerCase().includes('network') || errorMessage.toLowerCase().includes('fetch');
return {
available: false,
version: 'unknown',
error: {
type: isNetworkError ? 'network' : 'unknown',
message: `Failed to check for updates: ${errorMessage}`,
},
};
}
};
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}`);
// Store the acknowledged version in localStorage
try {
localStorage.setItem('last_acknowledged_update', version);
} catch (error) {
console.error('Failed to store acknowledged version:', error);
}
};