added custom instructions in env, better error handling, fixed null pointer

This commit is contained in:
Drew Peifer 2026-02-12 03:11:51 -05:00
parent 871351ddec
commit a85e96aad8
6 changed files with 121 additions and 31 deletions

View file

@ -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
REACT_APP_DEBUG_MODE=false

View file

@ -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 (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center" minHeight="100vh" gap={2}>
<CircularProgress />
<Typography variant="body1" color="text.secondary">
{loadingStatus}
</Typography>
</Box>
);
}

View file

@ -25,6 +25,26 @@ function NewsWidget({ data, sx }) {
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'start', gap: 1 }}>
{post.thumbnail && post.thumbnail.startsWith('http') && (
<Link
href={post.url}
target="_blank"
rel="noopener noreferrer"
>
<Box
component="img"
src={post.thumbnail}
alt={post.title}
sx={{
width: 70,
height: 70,
objectFit: 'cover',
borderRadius: 1,
flexShrink: 0
}}
/>
</Link>
)}
<Link
href={post.url}
target="_blank"
@ -79,21 +99,43 @@ function NewsWidget({ data, sx }) {
<ListItem key={index} disablePadding sx={{ mb: 1 }}>
<ListItemText
primary={
<Link
href={item.link}
target="_blank"
rel="noopener noreferrer"
sx={{
textDecoration: 'none',
color: 'text.primary',
'&:hover': { textDecoration: 'underline' }
}}
>
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{item.title}
<OpenInNew sx={{ fontSize: 14 }} />
</Typography>
</Link>
<Box sx={{ display: 'flex', alignItems: 'start', gap: 1 }}>
{(item.thumbnail || item.enclosure?.url || item['media:thumbnail']?.url) && (
<Link
href={item.link}
target="_blank"
rel="noopener noreferrer"
>
<Box
component="img"
src={item.thumbnail || item.enclosure?.url || item['media:thumbnail']?.url}
alt={item.title}
sx={{
width: 70,
height: 70,
objectFit: 'cover',
borderRadius: 1,
flexShrink: 0
}}
/>
</Link>
)}
<Link
href={item.link}
target="_blank"
rel="noopener noreferrer"
sx={{
textDecoration: 'none',
color: 'text.primary',
'&:hover': { textDecoration: 'underline' }
}}
>
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{item.title}
<OpenInNew sx={{ fontSize: 14 }} />
</Typography>
</Link>
</Box>
}
secondaryTypographyProps={{ component: 'div' }}
secondary={

View file

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

View file

@ -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') {

View file

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