diff --git a/claudash/src/App.css b/claudash/src/App.css index 3504db9..840beb3 100644 --- a/claudash/src/App.css +++ b/claudash/src/App.css @@ -27,7 +27,6 @@ body { h1, h2, h3, h4, h5, h6 { font-family: "Russo One", sans-serif !important; - font-size:1.2rem; } /* App theme containers */ .app-dark { @@ -59,7 +58,6 @@ h1, h2, h3, h4, h5, h6 { flex-grow: 2; } .widget-row.condensed { - flex-wrap: wrap; justify-content: space-evenly; } .news-widget img { diff --git a/claudash/src/components/Dashboard.jsx b/claudash/src/components/Dashboard.jsx index c947b7a..f7820c2 100644 --- a/claudash/src/components/Dashboard.jsx +++ b/claudash/src/components/Dashboard.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Box, Container, CircularProgress, IconButton, Alert } from '@mui/material'; import { Brightness4, Brightness7, Refresh } from '@mui/icons-material'; import { fetchAllSources } from '../services/dataFetcher'; @@ -18,15 +18,27 @@ function Dashboard({ themeToggle, isDark }) { const [refreshing, setRefreshing] = useState(false); const [tokenUsage, setTokenUsage] = useState(null); const [cacheInfo, setCacheInfo] = useState(null); + const isLoadingRef = useRef(false); useEffect(() => { + console.log('=== 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()); + return; + } + + isLoadingRef.current = true; + try { console.log('=== loadDashboard called ==='); console.log('Force refresh:', forceRefresh); + console.log('Request timestamp:', new Date().toISOString()); setError(null); // Check if we already have today's briefing (unless forcing refresh) @@ -126,11 +138,15 @@ function Dashboard({ themeToggle, isDark }) { } finally { setLoading(false); setRefreshing(false); + isLoadingRef.current = false; + console.log('=== loadDashboard completed at:', new Date().toISOString(), '==='); } } const handleRefresh = () => { setRefreshing(true); + setLoading(true); + setLoadingStatus('Refreshing your dashboard...'); loadDashboard(true); }; @@ -221,17 +237,28 @@ function Dashboard({ themeToggle, isDark }) { /> - {/* Render rows based on the new data structure */} + {/* Render rows based on the data structure */} {Object.entries(sourceData).map(([key, data]) => { - // Skip special entries like weather and legacy flat entries - if (key === 'weather' || key.startsWith('reddit_') || key.startsWith('rss_') || key.startsWith('source_')) { + + // Skip non-array entries (like weather) + if (!Array.isArray(data) || data.length === 0) { return null; } - // Only process row entries (row1, row2, etc.) - if (key.startsWith('row') && Array.isArray(data)) { - return ( - + return ( + + {/* Display row header if not using legacy row naming */} + {!key.startsWith('row') && ( +

+ {key} +

+ )} + {data.map((widgetData, index) => ( ))} - ); - } - - return null; +
+ ); })} - - {/* Fallback for legacy format - if no rows detected, render flat structure */} - {!Object.keys(sourceData).some(k => k.startsWith('row')) && ( - - {Object.entries(sourceData).map(([key, data]) => { - if (key === 'weather' || !data) return null; - return ( - - ); - })} - - )}
diff --git a/claudash/src/main.jsx b/claudash/src/main.jsx index 6161d18..1f58390 100644 --- a/claudash/src/main.jsx +++ b/claudash/src/main.jsx @@ -3,7 +3,5 @@ import ReactDOM from 'react-dom/client' import App from './App.jsx' ReactDOM.createRoot(document.getElementById('root')).render( - - - , -) \ No newline at end of file + +) diff --git a/claudash/src/services/claudeService.js b/claudash/src/services/claudeService.js index a0e9e47..ecab7b8 100644 --- a/claudash/src/services/claudeService.js +++ b/claudash/src/services/claudeService.js @@ -123,7 +123,15 @@ CURRENT DATA: - Includes relevant weather information if available - Ends with something engaging or thought-provoking -Focus on the most newsworthy and interesting items. Don't try to mention everything.`; +Focus on the most newsworthy and interesting items. Don't try to mention everything. + +IMPORTANT: Provide your response as valid JSON with exactly two fields: +{ + "fullBriefing": "Your complete 300-400 word friendly briefing text here", + "condensedSummary": "A 50-75 word ultra-condensed summary of the key topics and main stories for historical context" +} + +The JSON must be properly formatted with escaped quotes where necessary.`; return prompt; } @@ -171,13 +179,46 @@ export async function generateBriefing(currentData, previousBriefing) { const prompt = buildBriefingPrompt(currentData, previousBriefing); const result = await callClaude(prompt); - // Generate condensed summary for historical storage - const condensedSummary = await generateCondensedSummary(result.text); - - return { - ...result, - condensedSummary: condensedSummary - }; + // Parse JSON response that now includes both fullBriefing and condensedSummary + try { + // Strip markdown code blocks if present + let jsonText = result.text; + if (jsonText.includes('```json')) { + jsonText = jsonText.replace(/```json\s*/g, '').replace(/```\s*/g, ''); + } else if (jsonText.includes('```')) { + jsonText = jsonText.replace(/```\s*/g, ''); + } + jsonText = jsonText.trim(); + + const parsed = JSON.parse(jsonText); + + // Validate the JSON structure + if (!parsed.fullBriefing || !parsed.condensedSummary) { + 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) + '...'); + + return { + text: parsed.fullBriefing, + condensedSummary: parsed.condensedSummary, + usage: result.usage + }; + } catch (parseError) { + console.error('Failed to parse JSON response, attempting fallback:', parseError); + + // 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'); + const condensedSummary = await generateCondensedSummary(result.text); + + return { + text: result.text, + condensedSummary: condensedSummary, + usage: result.usage + }; + } } catch (error) { console.error('Error generating briefing:', error);