added token usage and cost to UI, cleanup

This commit is contained in:
Drew Peifer 2026-02-12 03:26:10 -05:00
parent a85e96aad8
commit bffb11e5b0
15 changed files with 285 additions and 254 deletions

View file

@ -46,6 +46,7 @@ app.post('/api/claude', async (req, res) => {
const data = await response.json(); const data = await response.json();
console.log('Claude API success, response content length:', data.content?.[0]?.text?.length); console.log('Claude API success, response content length:', data.content?.[0]?.text?.length);
console.log('Token usage:', data.usage);
res.json(data); res.json(data);
} catch (error) { } catch (error) {
console.error('Proxy error:', error); console.error('Proxy error:', error);

View file

@ -16,6 +16,7 @@ function Dashboard({ themeToggle, isDark }) {
const [sourceData, setSourceData] = useState({}); const [sourceData, setSourceData] = useState({});
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [tokenUsage, setTokenUsage] = useState(null);
useEffect(() => { useEffect(() => {
loadDashboard(); loadDashboard();
@ -75,14 +76,24 @@ function Dashboard({ themeToggle, isDark }) {
try { try {
// Generate today's briefing with Claude // Generate today's briefing with Claude
const newBriefing = await generateBriefing(data, previousBriefing); const result = await generateBriefing(data, previousBriefing);
console.log('Briefing generated, length:', newBriefing?.length); console.log('Briefing generated');
setBriefing(newBriefing);
// Handle result object with text and usage
if (result && typeof result === 'object' && result.text) {
setBriefing(result.text);
setTokenUsage(result.usage);
console.log('Token usage:', result.usage);
} else if (typeof result === 'string') {
// Fallback for string responses
setBriefing(result);
}
// Save to localStorage // Save to localStorage
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
console.log('Saving briefing for:', today); console.log('Saving briefing for:', today);
saveBriefing(today, newBriefing, data.sources); const briefingText = result?.text || result;
saveBriefing(today, briefingText, data.sources);
} catch (briefingError) { } catch (briefingError) {
console.error('Failed to generate briefing:', 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.`); setBriefing(`Unable to generate AI briefing: ${briefingError.message}\n\nYou can still view the data widgets below.`);
@ -161,6 +172,20 @@ function Dashboard({ themeToggle, isDark }) {
</Alert> </Alert>
)} )}
{tokenUsage && (
<Box sx={{ mb: 2, p: 2, bgcolor: 'background.paper', borderRadius: 1, border: '1px solid', borderColor: 'divider' }}>
<Typography variant="body2" color="text.secondary">
<strong>API Usage:</strong> {tokenUsage.input_tokens?.toLocaleString()} input + {tokenUsage.output_tokens?.toLocaleString()} output = {(tokenUsage.input_tokens + tokenUsage.output_tokens).toLocaleString()} total tokens
{' • '}
<strong>Cost:</strong> ${((tokenUsage.input_tokens * 0.003 / 1000) + (tokenUsage.output_tokens * 0.015 / 1000)).toFixed(4)}
{' '}
<Typography component="span" variant="caption" color="text.disabled">
(Claude Sonnet 4 pricing: $0.003/1K input, $0.015/1K output)
</Typography>
</Typography>
</Box>
)}
{/* Widget Grid - easily rearrangeable */} {/* Widget Grid - easily rearrangeable */}
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',

View file

@ -62,7 +62,12 @@ async function callClaude(prompt, messages = []) {
const data = await response.json(); const data = await response.json();
console.log('Response data structure:', Object.keys(data)); console.log('Response data structure:', Object.keys(data));
console.log('Response content:', data.content?.[0]?.text?.substring(0, 200) + '...'); console.log('Response content:', data.content?.[0]?.text?.substring(0, 200) + '...');
return data.content[0].text; console.log('Usage data:', data.usage);
return {
text: data.content[0].text,
usage: data.usage // { input_tokens, output_tokens }
};
} catch (error) { } catch (error) {
console.error('Error calling Claude:', error); console.error('Error calling Claude:', error);
throw error; throw error;
@ -129,8 +134,8 @@ export async function generateBriefing(currentData, previousBriefing) {
try { try {
const prompt = buildBriefingPrompt(currentData, previousBriefing); const prompt = buildBriefingPrompt(currentData, previousBriefing);
const briefing = await callClaude(prompt); const result = await callClaude(prompt);
return briefing; return result; // Returns { text, usage }
} catch (error) { } catch (error) {
console.error('Error generating briefing:', error); console.error('Error generating briefing:', error);
@ -168,8 +173,8 @@ export async function sendChatMessage(message, conversationHistory = []) {
User's message: ${message}`; User's message: ${message}`;
try { try {
const response = await callClaude(contextPrompt, messages); const result = await callClaude(contextPrompt, messages);
return response; return result.text; // For chat, just return text for now
} catch (error) { } catch (error) {
console.error('Error in chat:', error); console.error('Error in chat:', error);
return `I apologize, but I encountered an error: ${error.message}. Please try again.`; return `I apologize, but I encountered an error: ${error.message}. Please try again.`;