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)
|
# Optional: Claude model (default: claude-sonnet-4-5-20250929)
|
||||||
REACT_APP_CLAUDE_MODEL=claude-3-5-sonnet-20241022
|
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)
|
# 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 { 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 { Brightness4, Brightness7, Refresh } from '@mui/icons-material';
|
||||||
import { fetchAllSources } from '../services/dataFetcher';
|
import { fetchAllSources } from '../services/dataFetcher';
|
||||||
import { generateBriefing } from '../services/claudeService';
|
import { generateBriefing } from '../services/claudeService';
|
||||||
|
|
@ -11,6 +11,7 @@ import ChatInterface from './ChatInterface';
|
||||||
|
|
||||||
function Dashboard({ themeToggle, isDark }) {
|
function Dashboard({ themeToggle, isDark }) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [loadingStatus, setLoadingStatus] = useState('Initializing...');
|
||||||
const [briefing, setBriefing] = useState('');
|
const [briefing, setBriefing] = useState('');
|
||||||
const [sourceData, setSourceData] = useState({});
|
const [sourceData, setSourceData] = useState({});
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
@ -37,12 +38,21 @@ function Dashboard({ themeToggle, isDark }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Fetching fresh data...');
|
console.log('Fetching fresh data...');
|
||||||
|
setLoadingStatus('Fetching weather and news sources...');
|
||||||
|
|
||||||
// Fetch all configured sources
|
// Fetch all configured sources
|
||||||
const data = await fetchAllSources();
|
const data = await fetchAllSources();
|
||||||
console.log('Fetched sources:', Object.keys(data.sources || {}));
|
console.log('Fetched sources:', Object.keys(data.sources || {}));
|
||||||
|
console.log('Failed sources:', data.failed);
|
||||||
setSourceData(data.sources || {});
|
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
|
// Check if API key is configured
|
||||||
const apiKey = import.meta.env.REACT_APP_ANTHROPIC_API_KEY;
|
const apiKey = import.meta.env.REACT_APP_ANTHROPIC_API_KEY;
|
||||||
console.log('API key configured:', !!apiKey);
|
console.log('API key configured:', !!apiKey);
|
||||||
|
|
@ -58,16 +68,28 @@ function Dashboard({ themeToggle, isDark }) {
|
||||||
const previousBriefing = getPreviousBriefing(1);
|
const previousBriefing = getPreviousBriefing(1);
|
||||||
console.log('Previous briefing exists:', !!previousBriefing);
|
console.log('Previous briefing exists:', !!previousBriefing);
|
||||||
|
|
||||||
console.log('Calling generateBriefing...');
|
// Only generate briefing if we have some data
|
||||||
// Generate today's briefing with Claude
|
if (Object.keys(data.sources || {}).length > 0) {
|
||||||
const newBriefing = await generateBriefing(data, previousBriefing);
|
console.log('Calling generateBriefing...');
|
||||||
console.log('Briefing generated, length:', newBriefing?.length);
|
setLoadingStatus('Generating your personalized briefing with AI...');
|
||||||
setBriefing(newBriefing);
|
|
||||||
|
try {
|
||||||
// Save to localStorage
|
// Generate today's briefing with Claude
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const newBriefing = await generateBriefing(data, previousBriefing);
|
||||||
console.log('Saving briefing for:', today);
|
console.log('Briefing generated, length:', newBriefing?.length);
|
||||||
saveBriefing(today, newBriefing, data.sources);
|
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) {
|
} catch (error) {
|
||||||
console.error('Dashboard load error:', error);
|
console.error('Dashboard load error:', error);
|
||||||
|
|
@ -86,8 +108,11 @@ function Dashboard({ themeToggle, isDark }) {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
|
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center" minHeight="100vh" gap={2}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
{loadingStatus}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,26 @@ function NewsWidget({ data, sx }) {
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'start', gap: 1 }}>
|
<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
|
<Link
|
||||||
href={post.url}
|
href={post.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -79,21 +99,43 @@ function NewsWidget({ data, sx }) {
|
||||||
<ListItem key={index} disablePadding sx={{ mb: 1 }}>
|
<ListItem key={index} disablePadding sx={{ mb: 1 }}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<Link
|
<Box sx={{ display: 'flex', alignItems: 'start', gap: 1 }}>
|
||||||
href={item.link}
|
{(item.thumbnail || item.enclosure?.url || item['media:thumbnail']?.url) && (
|
||||||
target="_blank"
|
<Link
|
||||||
rel="noopener noreferrer"
|
href={item.link}
|
||||||
sx={{
|
target="_blank"
|
||||||
textDecoration: 'none',
|
rel="noopener noreferrer"
|
||||||
color: 'text.primary',
|
>
|
||||||
'&:hover': { textDecoration: 'underline' }
|
<Box
|
||||||
}}
|
component="img"
|
||||||
>
|
src={item.thumbnail || item.enclosure?.url || item['media:thumbnail']?.url}
|
||||||
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
alt={item.title}
|
||||||
{item.title}
|
sx={{
|
||||||
<OpenInNew sx={{ fontSize: 14 }} />
|
width: 70,
|
||||||
</Typography>
|
height: 70,
|
||||||
</Link>
|
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' }}
|
secondaryTypographyProps={{ component: 'div' }}
|
||||||
secondary={
|
secondary={
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,16 @@ async function callClaude(prompt, messages = []) {
|
||||||
* @returns {string} The prompt
|
* @returns {string} The prompt
|
||||||
*/
|
*/
|
||||||
function buildBriefingPrompt(currentData, previousBriefing) {
|
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:
|
CURRENT DATA:
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,17 @@ export async function fetchAllSources() {
|
||||||
results.forEach((result, index) => {
|
results.forEach((result, index) => {
|
||||||
if (result.status === 'fulfilled') {
|
if (result.status === 'fulfilled') {
|
||||||
const data = result.value;
|
const data = result.value;
|
||||||
if (data.error) {
|
// Handle null or error responses
|
||||||
errors.push(data);
|
if (!data || data.error) {
|
||||||
|
if (data) {
|
||||||
|
errors.push(data);
|
||||||
|
} else {
|
||||||
|
errors.push({
|
||||||
|
error: true,
|
||||||
|
index: index,
|
||||||
|
reason: 'Source returned null'
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Assign to appropriate key
|
// Assign to appropriate key
|
||||||
if (data.type === 'weather') {
|
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_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_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_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),
|
'import.meta.env.REACT_APP_DEBUG_MODE': JSON.stringify(env.REACT_APP_DEBUG_MODE),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue