env cleanup, gated logs in debug mode, readme cleanup

This commit is contained in:
Drew Peifer 2026-02-21 02:35:29 -05:00
parent f0a23e0333
commit 6e3c42e243
9 changed files with 96 additions and 79 deletions

View file

@ -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=...');
});

View file

@ -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;
export default Dashboard;

View file

@ -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.`;
}
}
}

View file

@ -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
};
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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)}`;

View file

@ -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),
}
}
})