added custom instructions in env, better error handling, fixed null pointer
This commit is contained in:
parent
871351ddec
commit
a85e96aad8
6 changed files with 121 additions and 31 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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={
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue