🎉 first commit
This commit is contained in:
63
app/lib/api/connection.ts
Normal file
63
app/lib/api/connection.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
export interface ConnectionStatus {
|
||||
connected: boolean;
|
||||
latency: number;
|
||||
lastChecked: string;
|
||||
}
|
||||
|
||||
export const checkConnection = async (): Promise<ConnectionStatus> => {
|
||||
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(),
|
||||
};
|
||||
}
|
||||
};
|
||||
33
app/lib/api/cookies.ts
Normal file
33
app/lib/api/cookies.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export function parseCookies(cookieHeader: string | null) {
|
||||
const cookies: Record<string, string> = {};
|
||||
|
||||
if (!cookieHeader) {
|
||||
return cookies;
|
||||
}
|
||||
|
||||
// Split the cookie string by semicolons and spaces
|
||||
const items = cookieHeader.split(';').map((cookie) => cookie.trim());
|
||||
|
||||
items.forEach((item) => {
|
||||
const [name, ...rest] = item.split('=');
|
||||
|
||||
if (name && rest.length > 0) {
|
||||
// Decode the name and value, and join value parts in case it contains '='
|
||||
const decodedName = decodeURIComponent(name.trim());
|
||||
const decodedValue = decodeURIComponent(rest.join('=').trim());
|
||||
cookies[decodedName] = decodedValue;
|
||||
}
|
||||
});
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
export function getApiKeysFromCookie(cookieHeader: string | null): Record<string, string> {
|
||||
const cookies = parseCookies(cookieHeader);
|
||||
return cookies.apiKeys ? JSON.parse(cookies.apiKeys) : {};
|
||||
}
|
||||
|
||||
export function getProviderSettingsFromCookie(cookieHeader: string | null): Record<string, any> {
|
||||
const cookies = parseCookies(cookieHeader);
|
||||
return cookies.providers ? JSON.parse(cookies.providers) : {};
|
||||
}
|
||||
121
app/lib/api/debug.ts
Normal file
121
app/lib/api/debug.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
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: DebugIssue[];
|
||||
errors: DebugIssue[];
|
||||
}
|
||||
|
||||
export interface DebugIssue {
|
||||
id: string;
|
||||
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> => {
|
||||
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 (id: string): Promise<void> => {
|
||||
acknowledgedIssues.add(id);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
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`);
|
||||
};
|
||||
58
app/lib/api/notifications.ts
Normal file
58
app/lib/api/notifications.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { LogEntry } from '~/lib/stores/logs';
|
||||
import { logStore } from '~/lib/stores/logs';
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
type: 'info' | 'warning' | 'error' | 'success';
|
||||
timestamp: string;
|
||||
read: boolean;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface LogEntryWithRead extends LogEntry {
|
||||
read: boolean;
|
||||
}
|
||||
|
||||
export const getNotifications = async (): Promise<Notification[]> => {
|
||||
// Get notifications from the log store
|
||||
const logs = Object.values(logStore.logs.get());
|
||||
|
||||
return logs
|
||||
.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());
|
||||
};
|
||||
|
||||
export const markNotificationRead = async (notificationId: string): Promise<void> => {
|
||||
logStore.markAsRead(notificationId);
|
||||
};
|
||||
|
||||
export const clearNotifications = async (): Promise<void> => {
|
||||
logStore.clearLogs();
|
||||
};
|
||||
|
||||
export const getUnreadCount = (): number => {
|
||||
const logs = Object.values(logStore.logs.get()) as LogEntryWithRead[];
|
||||
|
||||
return logs.filter((log) => {
|
||||
if (!logStore.isRead(log.id)) {
|
||||
if (log.details?.type === 'update') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return log.level === 'error' || log.level === 'warning';
|
||||
}
|
||||
|
||||
return false;
|
||||
}).length;
|
||||
};
|
||||
Reference in New Issue
Block a user