diff --git a/app/components/settings/debug/DebugTab.tsx b/app/components/settings/debug/DebugTab.tsx index 7a84ec16..f37d1d4f 100644 --- a/app/components/settings/debug/DebugTab.tsx +++ b/app/components/settings/debug/DebugTab.tsx @@ -2,68 +2,493 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useSettings } from '~/lib/hooks/useSettings'; import commit from '~/commit.json'; -const versionHash = commit.commit; // Get the version hash from commit.json +interface ProviderStatus { + name: string; + enabled: boolean; + isLocal: boolean; + isRunning: boolean | null; + error?: string; + lastChecked: Date; + responseTime?: number; + url: string | null; +} + +interface SystemInfo { + os: string; + browser: string; + screen: string; + language: string; + timezone: string; + memory: string; + cores: number; +} + +interface IProviderConfig { + name: string; + settings: { + enabled: boolean; + }; +} + +const LOCAL_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike']; +const versionHash = commit.commit; +const GITHUB_URLS = { + original: 'https://api.github.com/repos/Stijnus/bolt.new-any-llm/commits/main', + fork: 'https://api.github.com/repos/Stijnus/bolt.new-any-llm/commits/main', +}; + +function getSystemInfo(): SystemInfo { + const formatBytes = (bytes: number): string => { + if (bytes === 0) { + return '0 Bytes'; + } + + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }; + + return { + os: navigator.platform, + browser: navigator.userAgent.split(' ').slice(-1)[0], + screen: `${window.screen.width}x${window.screen.height}`, + language: navigator.language, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + memory: formatBytes(performance?.memory?.jsHeapSizeLimit || 0), + cores: navigator.hardwareConcurrency || 0, + }; +} + +const checkProviderStatus = async (url: string | null, providerName: string): Promise => { + if (!url) { + console.log(`[Debug] No URL provided for ${providerName}`); + return { + name: providerName, + enabled: false, + isLocal: true, + isRunning: false, + error: 'No URL configured', + lastChecked: new Date(), + url: null, + }; + } + + console.log(`[Debug] Checking status for ${providerName} at ${url}`); + + const startTime = performance.now(); + + try { + if (providerName.toLowerCase() === 'ollama') { + // Special check for Ollama root endpoint + try { + console.log(`[Debug] Checking Ollama root endpoint: ${url}`); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout + + const response = await fetch(url, { + signal: controller.signal, + headers: { + Accept: 'text/plain,application/json', + }, + }); + clearTimeout(timeoutId); + + const text = await response.text(); + console.log(`[Debug] Ollama root response:`, text); + + if (text.includes('Ollama is running')) { + console.log(`[Debug] Ollama running confirmed via root endpoint`); + return { + name: providerName, + enabled: false, + isLocal: true, + isRunning: true, + lastChecked: new Date(), + responseTime: performance.now() - startTime, + url, + }; + } + } catch (error) { + console.log(`[Debug] Ollama root check failed:`, error); + + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + + if (errorMessage.includes('aborted')) { + return { + name: providerName, + enabled: false, + isLocal: true, + isRunning: false, + error: 'Connection timeout', + lastChecked: new Date(), + responseTime: performance.now() - startTime, + url, + }; + } + } + } + + // Try different endpoints based on provider + const checkUrls = [`${url}/api/health`, `${url}/v1/models`]; + console.log(`[Debug] Checking additional endpoints:`, checkUrls); + + const results = await Promise.all( + checkUrls.map(async (checkUrl) => { + try { + console.log(`[Debug] Trying endpoint: ${checkUrl}`); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + const response = await fetch(checkUrl, { + signal: controller.signal, + headers: { + Accept: 'application/json', + }, + }); + clearTimeout(timeoutId); + + const ok = response.ok; + console.log(`[Debug] Endpoint ${checkUrl} response:`, ok); + + if (ok) { + try { + const data = await response.json(); + console.log(`[Debug] Endpoint ${checkUrl} data:`, data); + } catch { + console.log(`[Debug] Could not parse JSON from ${checkUrl}`); + } + } + + return ok; + } catch (error) { + console.log(`[Debug] Endpoint ${checkUrl} failed:`, error); + return false; + } + }), + ); + + const isRunning = results.some((result) => result); + console.log(`[Debug] Final status for ${providerName}:`, isRunning); + + return { + name: providerName, + enabled: false, + isLocal: true, + isRunning, + lastChecked: new Date(), + responseTime: performance.now() - startTime, + url, + }; + } catch (error) { + console.log(`[Debug] Provider check failed for ${providerName}:`, error); + return { + name: providerName, + enabled: false, + isLocal: true, + isRunning: false, + error: error instanceof Error ? error.message : 'Unknown error', + lastChecked: new Date(), + responseTime: performance.now() - startTime, + url, + }; + } +}; export default function DebugTab() { const { providers } = useSettings(); - const [activeProviders, setActiveProviders] = useState([]); + const [activeProviders, setActiveProviders] = useState([]); + const [updateMessage, setUpdateMessage] = useState(''); + const [systemInfo] = useState(getSystemInfo()); + const [isCheckingUpdate, setIsCheckingUpdate] = useState(false); + + const updateProviderStatuses = async () => { + if (!providers) { + return; + } + + try { + const entries = Object.entries(providers) as [string, IProviderConfig][]; + const statuses = entries + .filter(([, provider]) => LOCAL_PROVIDERS.includes(provider.name)) + .map(async ([, provider]) => { + const envVarName = + provider.name.toLowerCase() === 'ollama' + ? 'OLLAMA_API_BASE_URL' + : provider.name.toLowerCase() === 'lmstudio' + ? 'LMSTUDIO_API_BASE_URL' + : `REACT_APP_${provider.name.toUpperCase()}_URL`; + + // Access environment variables through import.meta.env + const url = import.meta.env[envVarName] || null; + console.log(`[Debug] Using URL for ${provider.name}:`, url, `(from ${envVarName})`); + + const status = await checkProviderStatus(url, provider.name); + + return { + ...status, + enabled: provider.settings.enabled ?? false, + }; + }); + + Promise.all(statuses).then(setActiveProviders); + } catch (error) { + console.error('[Debug] Failed to update provider statuses:', error); + } + }; + useEffect(() => { - setActiveProviders( - Object.entries(providers) - .filter(([_key, provider]) => provider.settings.enabled) - .map(([_key, provider]) => provider.name), - ); + updateProviderStatuses(); + + const interval = setInterval(updateProviderStatuses, 30000); + + return () => clearInterval(interval); }, [providers]); + const handleCheckForUpdate = useCallback(async () => { + if (isCheckingUpdate) { + return; + } + + try { + setIsCheckingUpdate(true); + setUpdateMessage('Checking for updates...'); + + const [originalResponse, forkResponse] = await Promise.all([ + fetch(GITHUB_URLS.original), + fetch(GITHUB_URLS.fork), + ]); + + if (!originalResponse.ok || !forkResponse.ok) { + throw new Error('Failed to fetch repository information'); + } + + const [originalData, forkData] = await Promise.all([ + originalResponse.json() as Promise<{ sha: string }>, + forkResponse.json() as Promise<{ sha: string }>, + ]); + + const originalCommitHash = originalData.sha; + const forkCommitHash = forkData.sha; + const isForked = versionHash === forkCommitHash && forkCommitHash !== originalCommitHash; + + if (originalCommitHash !== versionHash) { + setUpdateMessage( + `Update available from original repository!\n` + + `Current: ${versionHash.slice(0, 7)}${isForked ? ' (forked)' : ''}\n` + + `Latest: ${originalCommitHash.slice(0, 7)}`, + ); + } else { + setUpdateMessage('You are on the latest version from the original repository'); + } + } catch (error) { + setUpdateMessage('Failed to check for updates'); + console.error('[Debug] Failed to check for updates:', error); + } finally { + setIsCheckingUpdate(false); + } + }, [isCheckingUpdate]); + const handleCopyToClipboard = useCallback(() => { const debugInfo = { - OS: navigator.platform, - Browser: navigator.userAgent, - ActiveFeatures: activeProviders, - BaseURLs: { - Ollama: process.env.REACT_APP_OLLAMA_URL, - OpenAI: process.env.REACT_APP_OPENAI_URL, - LMStudio: process.env.REACT_APP_LM_STUDIO_URL, - }, + System: systemInfo, + Providers: activeProviders.map((provider) => ({ + name: provider.name, + enabled: provider.enabled, + isLocal: provider.isLocal, + running: provider.isRunning, + error: provider.error, + lastChecked: provider.lastChecked, + responseTime: provider.responseTime, + url: provider.url, + })), Version: versionHash, + Timestamp: new Date().toISOString(), }; navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => { alert('Debug information copied to clipboard!'); }); - }, [providers]); + }, [activeProviders, systemInfo]); return ( -
-

Debug Tab

- +
+
+

Debug Information

+
+ + +
+
-

System Information

-

OS: {navigator.platform}

-

Browser: {navigator.userAgent}

+ {updateMessage && ( +
+

{updateMessage}

+ {updateMessage.includes('Update available') && ( +
+

To update:

+
    +
  1. + Pull the latest changes:{' '} + git pull upstream main +
  2. +
  3. + Install any new dependencies:{' '} + npm install +
  4. +
  5. Restart the application
  6. +
+
+ )} +
+ )} -

Active Features

-
    - {activeProviders.map((name) => ( -
  • - {name} -
  • - ))} -
+
+
+

System Information

+
+
+
+

Operating System

+

{systemInfo.os}

+
+
+

Browser

+

{systemInfo.browser}

+
+
+

Screen Resolution

+

{systemInfo.screen}

+
+
+

Language

+

{systemInfo.language}

+
+
+

Timezone

+

{systemInfo.timezone}

+
+
+

CPU Cores

+

{systemInfo.cores}

+
+
+
+

Version

+

+ {versionHash.slice(0, 7)} + + ({new Date().toLocaleDateString()}) + +

+
+
+
-

Base URLs

-
    -
  • Ollama: {process.env.REACT_APP_OLLAMA_URL}
  • -
  • OpenAI: {process.env.REACT_APP_OPENAI_URL}
  • -
  • LM Studio: {process.env.REACT_APP_LM_STUDIO_URL}
  • -
+
+

Local LLM Status

+
+
+ {activeProviders.map((provider) => ( +
+
+
+
+
+
+
+

{provider.name}

+ {provider.url && ( +

+ {provider.url} +

+ )} +
+
+
+ + {provider.enabled ? 'Enabled' : 'Disabled'} + + {provider.enabled && ( + + {provider.isRunning ? 'Running' : 'Not Running'} + + )} +
+
-

Version Information

-

Version Hash: {versionHash}

+
+ {/* Status Details */} +
+ + Last checked: {new Date(provider.lastChecked).toLocaleTimeString()} + + {provider.responseTime && ( + + Response time: {Math.round(provider.responseTime)}ms + + )} +
+ + {/* Error Message */} + {provider.error && ( +
+ Error: {provider.error} +
+ )} + + {/* Connection Info */} + {provider.url && ( +
+ Endpoints checked: +
    +
  • {provider.url} (root)
  • +
  • {provider.url}/api/health
  • +
  • {provider.url}/v1/models
  • +
+
+ )} +
+
+ ))} + {activeProviders.length === 0 && ( +
No local LLMs configured
+ )} +
+
+
+
); } diff --git a/app/components/settings/event-logs/EventLogsTab.tsx b/app/components/settings/event-logs/EventLogsTab.tsx index be2a2d2f..55c3d660 100644 --- a/app/components/settings/event-logs/EventLogsTab.tsx +++ b/app/components/settings/event-logs/EventLogsTab.tsx @@ -32,10 +32,30 @@ export default function EventLogsTab() { }, []); useEffect(() => { - // Add some initial logs for testing - logStore.logSystem('System started', { version: '1.0.0' }); - logStore.logWarning('High memory usage detected', { memoryUsage: '85%' }); - logStore.logError('Failed to connect to provider', new Error('Connection timeout'), { provider: 'OpenAI' }); + // System info logs + logStore.logSystem('Application initialized', { + version: process.env.NEXT_PUBLIC_APP_VERSION, + environment: process.env.NODE_ENV, + }); + + // Debug logs for system state + logStore.logDebug('System configuration loaded', { + runtime: 'Next.js', + features: ['AI Chat', 'Event Logging'], + }); + + // Warning logs for potential issues + logStore.logWarning('Resource usage threshold approaching', { + memoryUsage: '75%', + cpuLoad: '60%', + }); + + // Error logs with detailed context + logStore.logError('API connection failed', new Error('Connection timeout'), { + endpoint: '/api/chat', + retryCount: 3, + lastAttempt: new Date().toISOString(), + }); }, []); useEffect(() => { diff --git a/app/types/global.d.ts b/app/types/global.d.ts index 6a030362..e0ad6642 100644 --- a/app/types/global.d.ts +++ b/app/types/global.d.ts @@ -3,3 +3,11 @@ interface Window { webkitSpeechRecognition: typeof SpeechRecognition; SpeechRecognition: typeof SpeechRecognition; } + +interface Performance { + memory?: { + jsHeapSizeLimit: number; + totalJSHeapSize: number; + usedJSHeapSize: number; + }; +}