From 6e3c42e24301f825321fd03a0023d3d48d6fa3b7 Mon Sep 17 00:00:00 2001 From: Drew Peifer Date: Sat, 21 Feb 2026 02:35:29 -0500 Subject: [PATCH] env cleanup, gated logs in debug mode, readme cleanup --- claudash/proxy-server.js | 30 ++++++++-------- claudash/src/components/Dashboard.jsx | 39 ++++++++++----------- claudash/src/services/claudeService.js | 42 +++++++++++------------ claudash/src/services/dataFetcher.js | 27 ++++++++------- claudash/src/services/openMeteoService.js | 11 +++--- claudash/src/services/sourceParser.js | 4 +-- claudash/src/utils/constants.js | 15 ++++++++ claudash/src/utils/corsProxy.js | 4 +-- claudash/vite.config.js | 3 ++ 9 files changed, 96 insertions(+), 79 deletions(-) diff --git a/claudash/proxy-server.js b/claudash/proxy-server.js index 2770c61..524f34c 100644 --- a/claudash/proxy-server.js +++ b/claudash/proxy-server.js @@ -3,6 +3,11 @@ import cors from 'cors'; const app = express(); const PORT = 3001; +const DEBUG = process.env.REACT_APP_DEBUG_MODE === 'true'; + +function debugLog(...args) { + if (DEBUG) console.log(...args); +} app.use(cors()); // Increase payload limit to 10MB (default is 100kb) @@ -13,11 +18,10 @@ app.post('/api/claude', async (req, res) => { try { const { messages, apiKey, model, maxTokens } = req.body; - console.log('=== Proxy received request ==='); - console.log('Model:', model); - console.log('Max tokens:', maxTokens); - console.log('Messages count:', messages?.length); - console.log('API Key present:', !!apiKey); + debugLog('=== Proxy received request ==='); + debugLog('Model:', model); + debugLog('Max tokens:', maxTokens); + debugLog('Messages count:', messages?.length); const requestBody = { model: model, @@ -25,7 +29,7 @@ app.post('/api/claude', async (req, res) => { messages: messages }; - console.log('Request body:', JSON.stringify(requestBody, null, 2)); + debugLog('Request body:', JSON.stringify(requestBody, null, 2)); const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', @@ -37,7 +41,7 @@ app.post('/api/claude', async (req, res) => { body: JSON.stringify(requestBody) }); - console.log('Claude API response status:', response.status); + debugLog('Claude API response status:', response.status); if (!response.ok) { const error = await response.text(); @@ -46,8 +50,8 @@ app.post('/api/claude', async (req, res) => { } const data = await response.json(); - console.log('Claude API success, response content length:', data.content?.[0]?.text?.length); - console.log('Token usage:', data.usage); + debugLog('Claude API success, response content length:', data.content?.[0]?.text?.length); + debugLog('Token usage:', data.usage); res.json(data); } catch (error) { console.error('Proxy error:', error); @@ -64,8 +68,8 @@ app.get('/api/scrape', async (req, res) => { return res.status(400).json({ error: 'Missing url query parameter' }); } - console.log('=== Scrape proxy request ==='); - console.log('URL:', url); + debugLog('=== Scrape proxy request ==='); + debugLog('URL:', url); const response = await fetch(url, { headers: { @@ -81,7 +85,7 @@ app.get('/api/scrape', async (req, res) => { } const html = await response.text(); - console.log('Scraped HTML length:', html.length); + debugLog('Scraped HTML length:', html.length); res.type('text/html').send(html); } catch (error) { console.error('Scrape proxy error:', error); @@ -91,6 +95,4 @@ app.get('/api/scrape', async (req, res) => { app.listen(PORT, () => { console.log(`Proxy server running on http://localhost:${PORT}`); - console.log('This proxy allows the frontend to communicate with the Anthropic API'); - console.log('Scrape endpoint available at GET /api/scrape?url=...'); }); diff --git a/claudash/src/components/Dashboard.jsx b/claudash/src/components/Dashboard.jsx index 34bad76..6b5427b 100644 --- a/claudash/src/components/Dashboard.jsx +++ b/claudash/src/components/Dashboard.jsx @@ -4,6 +4,7 @@ import { Brightness4, Brightness7, Refresh } from '@mui/icons-material'; import { fetchAllSources } from '../services/dataFetcher'; import { generateBriefing } from '../services/claudeService'; import { saveBriefing, getPreviousBriefing, hasTodaysBriefing, getTodaysBriefing } from '../services/storageService'; +import { debugLog, debugWarn } from '../utils/constants'; import BriefingWidget from './widgets/BriefingWidget'; import WeatherWidget from './widgets/WeatherWidget'; import NewsWidget from './widgets/NewsWidget'; @@ -23,29 +24,27 @@ function Dashboard({ themeToggle, isDark }) { const isLoadingRef = useRef(false); useEffect(() => { - console.log('=== Dashboard mounting, calling loadDashboard ==='); + debugLog('=== Dashboard mounting, calling loadDashboard ==='); loadDashboard(); }, []); async function loadDashboard(forceRefresh = false) { // Prevent concurrent requests using ref for immediate check if (isLoadingRef.current) { - console.log('=== loadDashboard SKIPPED - already in progress ==='); - console.log('Attempted at:', new Date().toISOString()); + debugLog('=== loadDashboard SKIPPED - already in progress ==='); return; } isLoadingRef.current = true; try { - console.log('=== loadDashboard called ==='); - console.log('Force refresh:', forceRefresh); - console.log('Request timestamp:', new Date().toISOString()); + debugLog('=== loadDashboard called ==='); + debugLog('Force refresh:', forceRefresh); setError(null); // Check if we already have today's briefing (unless forcing refresh) if (!forceRefresh && hasTodaysBriefing()) { - console.log('Using cached briefing from today'); + debugLog('Using cached briefing from today'); const todaysBriefing = getTodaysBriefing(); setBriefing(todaysBriefing.briefing); setSourceData(todaysBriefing.sources || {}); @@ -66,13 +65,13 @@ function Dashboard({ themeToggle, isDark }) { return; } - console.log('Fetching fresh data...'); + debugLog('Fetching fresh data...'); setLoadingStatus('Fetching weather and news sources...'); // Fetch all configured sources const data = await fetchAllSources(); - console.log('Fetched sources:', Object.keys(data.sources || {})); - console.log('Failed sources:', data.failed); + debugLog('Fetched sources:', Object.keys(data.sources || {})); + debugLog('Failed sources:', data.failed); setSourceData(data.sources || {}); // Track webpage summarization token usage @@ -89,10 +88,9 @@ function Dashboard({ themeToggle, isDark }) { // Check if API key is configured const apiKey = import.meta.env.REACT_APP_ANTHROPIC_API_KEY; - console.log('API key configured:', !!apiKey); if (!apiKey || apiKey === 'sk-ant-your-key-here') { - console.warn('API key not configured properly'); + debugWarn('API key not configured properly'); setBriefing('Please add your Anthropic API key to .env.local to generate briefings.'); setLoading(false); return; @@ -100,25 +98,25 @@ function Dashboard({ themeToggle, isDark }) { // Get yesterday's briefing for comparison const previousBriefing = getPreviousBriefing(1); - console.log('Previous briefing exists:', !!previousBriefing); + debugLog('Previous briefing exists:', !!previousBriefing); // Only generate briefing if we have some data if (Object.keys(data.sources || {}).length > 0) { - console.log('Calling generateBriefing...'); + debugLog('Calling generateBriefing...'); setLoadingStatus('Generating your personalized briefing with AI...'); try { // Generate today's briefing with Claude const result = await generateBriefing(data, previousBriefing); - console.log('Briefing generated'); - console.log('Condensed summary:', result.condensedSummary?.substring(0, 100) + '...'); + debugLog('Briefing generated'); + debugLog('Condensed summary:', result.condensedSummary?.substring(0, 100) + '...'); // Handle result object with text, condensedSummary and usage if (result && typeof result === 'object' && result.text) { setBriefing(result.text); setBriefingTokenUsage(result.usage); setCacheInfo(null); // Clear cache info when generating fresh briefing - console.log('Briefing token usage:', result.usage); + debugLog('Briefing token usage:', result.usage); } else if (typeof result === 'string') { // Fallback for string responses setBriefing(result); @@ -127,7 +125,7 @@ function Dashboard({ themeToggle, isDark }) { // Save to localStorage with condensed summary const today = new Date().toISOString().split('T')[0]; - console.log('Saving briefing for:', today); + debugLog('Saving briefing for:', today); const briefingText = result?.text || result; const condensedSummary = result?.condensedSummary || null; saveBriefing(today, briefingText, data.sources, condensedSummary); @@ -141,13 +139,12 @@ function Dashboard({ themeToggle, isDark }) { } catch (error) { console.error('Dashboard load error:', error); - console.error('Error stack:', error.stack); setError(`Failed to load dashboard: ${error.message}`); } finally { setLoading(false); setRefreshing(false); isLoadingRef.current = false; - console.log('=== loadDashboard completed at:', new Date().toISOString(), '==='); + debugLog('=== loadDashboard completed ==='); } } @@ -309,4 +306,4 @@ function Dashboard({ themeToggle, isDark }) { ); } -export default Dashboard; \ No newline at end of file +export default Dashboard; diff --git a/claudash/src/services/claudeService.js b/claudash/src/services/claudeService.js index c7be403..7d0f7ca 100644 --- a/claudash/src/services/claudeService.js +++ b/claudash/src/services/claudeService.js @@ -1,7 +1,9 @@ import { ANTHROPIC_API_URL, DEFAULT_CLAUDE_MODEL, - MAX_TOKENS + MAX_TOKENS, + debugLog, + debugWarn } from '../utils/constants'; import { condenseSourcesForClaude } from './sourceParser'; @@ -14,20 +16,16 @@ import { condenseSourcesForClaude } from './sourceParser'; async function callClaude(prompt, messages = []) { const apiKey = import.meta.env.REACT_APP_ANTHROPIC_API_KEY; - console.log('=== callClaude function ==='); - console.log('API Key configured:', !!apiKey); - console.log('API Key starts with:', apiKey?.substring(0, 15) + '...'); - if (!apiKey || apiKey === 'sk-ant-your-key-here') { console.error('Anthropic API key not configured'); return 'Please configure your Anthropic API key in .env.local'; } const model = import.meta.env.REACT_APP_CLAUDE_MODEL || DEFAULT_CLAUDE_MODEL; - console.log('Using model:', model); - console.log('Max tokens:', MAX_TOKENS); - console.log('Prompt length:', prompt.length); - console.log('Previous messages:', messages.length); + debugLog('Using model:', model); + debugLog('Max tokens:', MAX_TOKENS); + debugLog('Prompt length:', prompt.length); + debugLog('Previous messages:', messages.length); try { const requestBody = { @@ -40,8 +38,8 @@ async function callClaude(prompt, messages = []) { ] }; - console.log('Sending request to proxy...'); - console.log('Request messages:', requestBody.messages.map(m => ({ role: m.role, contentLength: m.content.length }))); + debugLog('Sending request to proxy...'); + debugLog('Request messages:', requestBody.messages.map(m => ({ role: m.role, contentLength: m.content.length }))); // Use local proxy server instead of direct API call const response = await fetch('http://localhost:3001/api/claude', { @@ -52,7 +50,7 @@ async function callClaude(prompt, messages = []) { body: JSON.stringify(requestBody) }); - console.log('Proxy response status:', response.status); + debugLog('Proxy response status:', response.status); if (!response.ok) { const error = await response.text(); @@ -61,9 +59,9 @@ async function callClaude(prompt, messages = []) { } const data = await response.json(); - console.log('Response data structure:', Object.keys(data)); - console.log('Response content:', data.content?.[0]?.text?.substring(0, 200) + '...'); - console.log('Usage data:', data.usage); + debugLog('Response data structure:', Object.keys(data)); + debugLog('Response content:', data.content?.[0]?.text?.substring(0, 200) + '...'); + debugLog('Usage data:', data.usage); return { text: data.content[0].text, @@ -98,9 +96,9 @@ CURRENT DATA: // CONDENSE DATA BEFORE SENDING TO CLAUDE const condensedData = condenseSourcesForClaude(currentData.sources || currentData); - console.log('Original data size:', JSON.stringify(currentData).length); - console.log('Condensed data size:', JSON.stringify(condensedData).length); - console.log('Size reduction:', Math.round((1 - JSON.stringify(condensedData).length / JSON.stringify(currentData).length) * 100) + '%'); + debugLog('Original data size:', JSON.stringify(currentData).length); + debugLog('Condensed data size:', JSON.stringify(condensedData).length); + debugLog('Size reduction:', Math.round((1 - JSON.stringify(condensedData).length / JSON.stringify(currentData).length) * 100) + '%'); // Add all condensed source data Object.entries(condensedData).forEach(([source, data]) => { @@ -197,8 +195,8 @@ export async function generateBriefing(currentData, previousBriefing) { throw new Error('Invalid response structure from Claude'); } - console.log('Successfully parsed briefing with condensed summary'); - console.log('Condensed summary:', parsed.condensedSummary.substring(0, 100) + '...'); + debugLog('Successfully parsed briefing with condensed summary'); + debugLog('Condensed summary:', parsed.condensedSummary.substring(0, 100) + '...'); return { text: parsed.fullBriefing, @@ -210,7 +208,7 @@ export async function generateBriefing(currentData, previousBriefing) { // Fallback: if JSON parsing fails, use the raw text and generate summary separately // This maintains backward compatibility but should rarely happen - console.warn('Using fallback: generating condensed summary separately'); + debugWarn('Using fallback: generating condensed summary separately'); const condensedSummary = await generateCondensedSummary(result.text); return { @@ -394,4 +392,4 @@ Be concise and focus on what would be most relevant to someone checking their da console.error(`Error summarizing ${dataType}:`, error); return `Unable to summarize ${dataType} data.`; } -} \ No newline at end of file +} diff --git a/claudash/src/services/dataFetcher.js b/claudash/src/services/dataFetcher.js index 30a25ea..a2a31e1 100644 --- a/claudash/src/services/dataFetcher.js +++ b/claudash/src/services/dataFetcher.js @@ -7,7 +7,9 @@ import { DEFAULT_DATA_SOURCES, WEATHER_API_BASE, DEFAULT_REFRESH_INTERVAL, - DEFAULT_TEMPERATURE_UNIT + DEFAULT_TEMPERATURE_UNIT, + debugLog, + debugWarn } from '../utils/constants'; /** @@ -53,19 +55,19 @@ export async function fetchSingleSource(url) { // Check cache first const cached = getCachedData(cacheKey); if (cached) { - console.log(`Using cached data for ${url}`); + debugLog(`Using cached data for ${url}`); return cached; } try { - console.log(`Fetching fresh data from ${url}`); + debugLog(`Fetching fresh data from ${url}`); const data = await parseSource(url); let standardized = standardizeSourceData(data, url); let tokenUsage = null; // If this is a webpage that needs Claude summarization, do it now if (standardized.type === 'webpage' && standardized.needsSummarization && standardized.cleanedText) { - console.log(`Summarizing webpage content from ${url} with Claude...`); + debugLog(`Summarizing webpage content from ${url} with Claude...`); try { const summary = await summarizeWebpage(standardized.cleanedText, url); standardized = { @@ -80,9 +82,9 @@ export async function fetchSingleSource(url) { }; // Track token usage from webpage summarization tokenUsage = summary.usage; - console.log(`Webpage summarized: "${summary.title}" (${summary.items?.length || 0} items)`); + debugLog(`Webpage summarized: "${summary.title}" (${summary.items?.length || 0} items)`); if (tokenUsage) { - console.log(`Webpage summarization tokens: ${tokenUsage.input_tokens} input + ${tokenUsage.output_tokens} output`); + debugLog(`Webpage summarization tokens: ${tokenUsage.input_tokens} input + ${tokenUsage.output_tokens} output`); } } catch (summaryError) { console.error(`Failed to summarize webpage ${url}:`, summaryError); @@ -104,7 +106,7 @@ export async function fetchSingleSource(url) { // Try to return cached data even if expired const expiredCache = getCachedData(cacheKey); if (expiredCache) { - console.log(`Returning expired cache for ${url} due to fetch error`); + debugLog(`Returning expired cache for ${url} due to fetch error`); return expiredCache; } throw error; @@ -118,7 +120,7 @@ export async function fetchSingleSource(url) { */ export async function fetchWeather(location) { if (!location) { - console.warn('No location configured for weather'); + debugWarn('No location configured for weather'); return null; } @@ -158,8 +160,8 @@ export async function fetchAllSources() { // Get location for weather const location = import.meta.env.REACT_APP_LOCATION; - console.log('Fetching from sources:', sources); - console.log('Weather location:', location); + debugLog('Fetching from sources:', sources); + debugLog('Weather location:', location); let sourcesByRow = {}; let allUrls = []; @@ -237,7 +239,7 @@ export async function fetchAllSources() { }); if (allTokenUsage.length > 0) { - console.log(`Total webpage summarization usage: ${totalWebpageTokens.input_tokens} input + ${totalWebpageTokens.output_tokens} output tokens from ${allTokenUsage.length} source(s)`); + debugLog(`Total webpage summarization usage: ${totalWebpageTokens.input_tokens} input + ${totalWebpageTokens.output_tokens} output tokens from ${allTokenUsage.length} source(s)`); } // Organize data by rows @@ -282,7 +284,7 @@ export async function fetchAllSources() { // Log any errors if (errors.length > 0) { - console.warn('Some sources failed to fetch:', errors); + debugWarn('Some sources failed to fetch:', errors); } return { @@ -294,4 +296,3 @@ export async function fetchAllSources() { webpageTokenUsage: allTokenUsage.length > 0 ? totalWebpageTokens : null }; } - diff --git a/claudash/src/services/openMeteoService.js b/claudash/src/services/openMeteoService.js index cc82e12..6616d4a 100644 --- a/claudash/src/services/openMeteoService.js +++ b/claudash/src/services/openMeteoService.js @@ -1,5 +1,6 @@ import { getCachedData, cacheSourceData } from './storageService'; import { fetchWithCors } from '../utils/corsProxy'; +import { debugLog } from '../utils/constants'; // Weather code descriptions mapping (WMO Weather interpretation codes) const WEATHER_CODE_MAP = { @@ -76,13 +77,13 @@ async function geocodeLocation(location) { // Check cache first (geocoding results are stable) const cached = getCachedData(cacheKey); if (cached) { - console.log(`Using cached geocoding for ${locationStr}`); + debugLog(`Using cached geocoding for ${locationStr}`); return cached; } try { const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(locationStr)}&count=1&language=en&format=json`; - console.log(`Geocoding location: ${locationStr}`); + debugLog(`Geocoding location: ${locationStr}`); const data = await fetchWithCors(url); @@ -129,7 +130,7 @@ async function fetchCurrentAndForecast(latitude, longitude, tempUnit = 'F') { `&timezone=auto` + `&forecast_days=5`; - console.log(`Fetching current weather and forecast for ${latitude}, ${longitude}`); + debugLog(`Fetching current weather and forecast for ${latitude}, ${longitude}`); const data = await fetchWithCors(url); return data; @@ -161,7 +162,7 @@ async function fetchHistorical(latitude, longitude, tempUnit = 'F') { `&wind_speed_unit=${windUnitParam}` + `&timezone=auto`; - console.log(`Fetching historical weather for ${latitude}, ${longitude}`); + debugLog(`Fetching historical weather for ${latitude}, ${longitude}`); const data = await fetchWithCors(url); return data; @@ -190,7 +191,7 @@ export async function fetchOpenMeteoWeather(location, tempUnit = 'F') { // Check cache first const cached = getCachedData(cacheKey); if (cached) { - console.log(`Using cached Open-Meteo data for ${location}`); + debugLog(`Using cached Open-Meteo data for ${location}`); return cached; } diff --git a/claudash/src/services/sourceParser.js b/claudash/src/services/sourceParser.js index 313507e..9b55c6e 100644 --- a/claudash/src/services/sourceParser.js +++ b/claudash/src/services/sourceParser.js @@ -1,5 +1,5 @@ import { fetchWithCors } from '../utils/corsProxy'; -import { RSS_TO_JSON_API } from '../utils/constants'; +import { RSS_TO_JSON_API, debugLog, debugWarn } from '../utils/constants'; /** * Determine source type from URL @@ -126,7 +126,7 @@ export async function parseHTML(url, corsProxy) { rawHTML = await response.text(); } catch (proxyError) { - console.warn(`Local scrape proxy failed for ${url}, falling back to CORS proxy:`, proxyError.message); + debugWarn(`Local scrape proxy failed for ${url}, falling back to CORS proxy:`, proxyError.message); try { const html = await fetchWithCors(url); rawHTML = typeof html === 'string' ? html : JSON.stringify(html); diff --git a/claudash/src/utils/constants.js b/claudash/src/utils/constants.js index 07831ba..86481d5 100644 --- a/claudash/src/utils/constants.js +++ b/claudash/src/utils/constants.js @@ -1,5 +1,20 @@ // App constants export const STORAGE_KEY = 'claudash'; +export const DEBUG = import.meta.env.REACT_APP_DEBUG_MODE === 'true'; + +/** + * Debug-gated console.log — only outputs when DEBUG mode is enabled + */ +export function debugLog(...args) { + if (DEBUG) console.log(...args); +} + +/** + * Debug-gated console.warn — only outputs when DEBUG mode is enabled + */ +export function debugWarn(...args) { + if (DEBUG) console.warn(...args); +} export const DEFAULT_REFRESH_INTERVAL = 15; // minutes export const DEFAULT_HISTORY_DAYS = 7; diff --git a/claudash/src/utils/corsProxy.js b/claudash/src/utils/corsProxy.js index 46f8ef5..d3e7e91 100644 --- a/claudash/src/utils/corsProxy.js +++ b/claudash/src/utils/corsProxy.js @@ -1,4 +1,4 @@ -import { DEFAULT_CORS_PROXY } from './constants'; +import { DEFAULT_CORS_PROXY, debugWarn } from './constants'; /** * Fetch data with CORS proxy fallback @@ -32,7 +32,7 @@ export async function fetchWithCors(url, options = {}) { } } catch (error) { // If CORS fails, try with proxy - console.warn(`CORS failed for ${url}, trying proxy...`, error.message); + debugWarn(`CORS failed for ${url}, trying proxy...`, error.message); try { const proxiedUrl = `${proxy}${encodeURIComponent(url)}`; diff --git a/claudash/vite.config.js b/claudash/vite.config.js index e40bcfa..091b291 100644 --- a/claudash/vite.config.js +++ b/claudash/vite.config.js @@ -33,6 +33,9 @@ export default defineConfig(({ mode }) => { 'import.meta.env.REACT_APP_CLAUDE_INSTRUCTIONS': JSON.stringify(env.REACT_APP_CLAUDE_INSTRUCTIONS), 'import.meta.env.REACT_APP_DEBUG_MODE': JSON.stringify(env.REACT_APP_DEBUG_MODE), 'import.meta.env.REACT_APP_QUICK_LINKS': JSON.stringify(env.REACT_APP_QUICK_LINKS), + 'import.meta.env.REACT_APP_TEMPERATURE_UNIT': JSON.stringify(env.REACT_APP_TEMPERATURE_UNIT), + 'import.meta.env.REACT_APP_CHART_DATE_FORMAT': JSON.stringify(env.REACT_APP_CHART_DATE_FORMAT), + 'import.meta.env.REACT_APP_TEXT_VOICE': JSON.stringify(env.REACT_APP_TEXT_VOICE), } } }) \ No newline at end of file