From a85e96aad810af537a6e9ccf0f5855a1457d0cb9 Mon Sep 17 00:00:00 2001 From: Drew Peifer Date: Thu, 12 Feb 2026 03:11:51 -0500 Subject: [PATCH] added custom instructions in env, better error handling, fixed null pointer --- claudash/.env.example | 6 +- claudash/src/components/Dashboard.jsx | 49 +++++++++---- .../src/components/widgets/NewsWidget.jsx | 72 +++++++++++++++---- claudash/src/services/claudeService.js | 11 ++- claudash/src/services/dataFetcher.js | 13 +++- claudash/vite.config.js | 1 + 6 files changed, 121 insertions(+), 31 deletions(-) diff --git a/claudash/.env.example b/claudash/.env.example index b8ecd60..3826623 100644 --- a/claudash/.env.example +++ b/claudash/.env.example @@ -20,5 +20,9 @@ REACT_APP_HISTORY_DAYS=7 # Optional: Claude model (default: claude-sonnet-4-5-20250929) REACT_APP_CLAUDE_MODEL=claude-3-5-sonnet-20241022 +# Optional: Custom instructions for Claude (prepended to every briefing prompt) +# Example: "Focus on world news more than tech news. Use a concise and informative tone." +REACT_APP_CLAUDE_INSTRUCTIONS= + # Optional: Debug mode (shows raw data, enables dummy data) -REACT_APP_DEBUG_MODE=false \ No newline at end of file +REACT_APP_DEBUG_MODE=false diff --git a/claudash/src/components/Dashboard.jsx b/claudash/src/components/Dashboard.jsx index b5b3a40..737e0b7 100644 --- a/claudash/src/components/Dashboard.jsx +++ b/claudash/src/components/Dashboard.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Box, Container, CircularProgress, IconButton, Alert } from '@mui/material'; +import { Box, Container, CircularProgress, IconButton, Alert, Typography } from '@mui/material'; import { Brightness4, Brightness7, Refresh } from '@mui/icons-material'; import { fetchAllSources } from '../services/dataFetcher'; import { generateBriefing } from '../services/claudeService'; @@ -11,6 +11,7 @@ import ChatInterface from './ChatInterface'; function Dashboard({ themeToggle, isDark }) { const [loading, setLoading] = useState(true); + const [loadingStatus, setLoadingStatus] = useState('Initializing...'); const [briefing, setBriefing] = useState(''); const [sourceData, setSourceData] = useState({}); const [error, setError] = useState(null); @@ -37,12 +38,21 @@ function Dashboard({ themeToggle, isDark }) { } console.log('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); setSourceData(data.sources || {}); + // If all sources failed, show error but continue + if (data.failed > 0 && data.successful === 0) { + setError('All data sources failed to load. Please check your internet connection and try again.'); + } else if (data.failed > 0) { + setError(`${data.failed} data source(s) failed to load. Showing available data.`); + } + // Check if API key is configured const apiKey = import.meta.env.REACT_APP_ANTHROPIC_API_KEY; console.log('API key configured:', !!apiKey); @@ -58,16 +68,28 @@ function Dashboard({ themeToggle, isDark }) { const previousBriefing = getPreviousBriefing(1); console.log('Previous briefing exists:', !!previousBriefing); - console.log('Calling generateBriefing...'); - // Generate today's briefing with Claude - const newBriefing = await generateBriefing(data, previousBriefing); - console.log('Briefing generated, length:', newBriefing?.length); - setBriefing(newBriefing); - - // Save to localStorage - const today = new Date().toISOString().split('T')[0]; - console.log('Saving briefing for:', today); - saveBriefing(today, newBriefing, data.sources); + // Only generate briefing if we have some data + if (Object.keys(data.sources || {}).length > 0) { + console.log('Calling generateBriefing...'); + setLoadingStatus('Generating your personalized briefing with AI...'); + + try { + // Generate today's briefing with Claude + const newBriefing = await generateBriefing(data, previousBriefing); + console.log('Briefing generated, length:', newBriefing?.length); + setBriefing(newBriefing); + + // Save to localStorage + const today = new Date().toISOString().split('T')[0]; + console.log('Saving briefing for:', today); + saveBriefing(today, newBriefing, data.sources); + } catch (briefingError) { + console.error('Failed to generate briefing:', briefingError); + setBriefing(`Unable to generate AI briefing: ${briefingError.message}\n\nYou can still view the data widgets below.`); + } + } else { + setBriefing('No data sources loaded successfully. Please check your configuration and try again.'); + } } catch (error) { console.error('Dashboard load error:', error); @@ -86,8 +108,11 @@ function Dashboard({ themeToggle, isDark }) { if (loading) { return ( - + + + {loadingStatus} + ); } diff --git a/claudash/src/components/widgets/NewsWidget.jsx b/claudash/src/components/widgets/NewsWidget.jsx index 969abb5..b03891a 100644 --- a/claudash/src/components/widgets/NewsWidget.jsx +++ b/claudash/src/components/widgets/NewsWidget.jsx @@ -25,6 +25,26 @@ function NewsWidget({ data, sx }) { + {post.thumbnail && post.thumbnail.startsWith('http') && ( + + + + )} - - {item.title} - - - + + {(item.thumbnail || item.enclosure?.url || item['media:thumbnail']?.url) && ( + + + + )} + + + {item.title} + + + + } secondaryTypographyProps={{ component: 'div' }} secondary={ diff --git a/claudash/src/services/claudeService.js b/claudash/src/services/claudeService.js index 4c3d7c6..d6a6044 100644 --- a/claudash/src/services/claudeService.js +++ b/claudash/src/services/claudeService.js @@ -76,7 +76,16 @@ async function callClaude(prompt, messages = []) { * @returns {string} The prompt */ function buildBriefingPrompt(currentData, previousBriefing) { - let prompt = `You are creating a personalized daily briefing. Be conversational and highlight what's interesting. + const customInstructions = import.meta.env.REACT_APP_CLAUDE_INSTRUCTIONS; + + let prompt = ''; + + // Prepend custom instructions if provided + if (customInstructions && customInstructions.trim()) { + prompt += `CUSTOM INSTRUCTIONS: ${customInstructions}\n\n`; + } + + prompt += `You are creating a personalized daily briefing. Be conversational and highlight what's interesting. CURRENT DATA: `; diff --git a/claudash/src/services/dataFetcher.js b/claudash/src/services/dataFetcher.js index 7a2aecd..9d886be 100644 --- a/claudash/src/services/dataFetcher.js +++ b/claudash/src/services/dataFetcher.js @@ -157,8 +157,17 @@ export async function fetchAllSources() { results.forEach((result, index) => { if (result.status === 'fulfilled') { const data = result.value; - if (data.error) { - errors.push(data); + // Handle null or error responses + if (!data || data.error) { + if (data) { + errors.push(data); + } else { + errors.push({ + error: true, + index: index, + reason: 'Source returned null' + }); + } } else { // Assign to appropriate key if (data.type === 'weather') { diff --git a/claudash/vite.config.js b/claudash/vite.config.js index c914a58..60f7c4b 100644 --- a/claudash/vite.config.js +++ b/claudash/vite.config.js @@ -30,6 +30,7 @@ export default defineConfig(({ mode }) => { 'import.meta.env.REACT_APP_REFRESH_INTERVAL': JSON.stringify(env.REACT_APP_REFRESH_INTERVAL), 'import.meta.env.REACT_APP_HISTORY_DAYS': JSON.stringify(env.REACT_APP_HISTORY_DAYS), 'import.meta.env.REACT_APP_CLAUDE_MODEL': JSON.stringify(env.REACT_APP_CLAUDE_MODEL), + '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), } }