Skip to content

Commit 78fa244

Browse files
committed
feat: add real-time conversation cost tracking
Display token costs for conversations in real-time as messages stream. Costs update automatically and show color-coded indicators based on amount spent. - Calculate costs dynamically from message history - Support for 100+ models across OpenAI, Anthropic, Google, and AWS Bedrock - Historical pricing for accurate cost calculation - Comprehensive test coverage (56 unit tests) - Color-coded display: green (<$0.01), yellow (<$0.10), orange (<$1), red (>$1)
1 parent 9dbf153 commit 78fa244

File tree

8 files changed

+2531
-0
lines changed

8 files changed

+2531
-0
lines changed

api/server/routes/convos.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const requireJwtAuth = require('~/server/middleware/requireJwtAuth');
1212
const { importConversations } = require('~/server/utils/import');
1313
const { deleteToolCalls } = require('~/models/ToolCall');
1414
const getLogStores = require('~/cache/getLogStores');
15+
const {
16+
getConversationCostDisplayFromMessages,
17+
getMultipleConversationCosts,
18+
} = require('~/server/services/ConversationCostDynamic');
1519

1620
const assistantClients = {
1721
[EModelEndpoint.azureAssistants]: require('~/server/services/Endpoints/azureAssistants'),
@@ -229,4 +233,102 @@ router.post('/duplicate', async (req, res) => {
229233
}
230234
});
231235

236+
/**
237+
* GET /:conversationId/cost
238+
* Get cost summary for a specific conversation
239+
*/
240+
router.get('/:conversationId/cost', async (req, res) => {
241+
try {
242+
const { conversationId } = req.params;
243+
const { detailed: _detailed = false } = req.query;
244+
const userId = req.user.id;
245+
246+
console.log('Cost API called:', { conversationId, userId });
247+
248+
// Get the conversation
249+
const conversation = await getConvo(userId, conversationId);
250+
251+
if (!conversation) {
252+
return res.status(404).json({
253+
error: 'Conversation not found',
254+
});
255+
}
256+
257+
// Get actual messages from the messages collection
258+
const { getMessages } = require('~/models/Message');
259+
const messages = await getMessages({
260+
user: userId,
261+
conversationId: conversationId,
262+
});
263+
264+
if (messages.length === 0) {
265+
return res.status(404).json({
266+
error: 'No messages found in this conversation',
267+
});
268+
}
269+
270+
console.log('Messages found:', messages.length);
271+
console.log('Sample message:', JSON.stringify(messages[0], null, 2));
272+
273+
// Calculate cost from actual messages
274+
const costDisplay = getConversationCostDisplayFromMessages(messages);
275+
276+
console.log('Cost calculated:', costDisplay);
277+
278+
if (!costDisplay) {
279+
return res.json({
280+
conversationId,
281+
totalCost: '$0.00',
282+
totalCostRaw: 0,
283+
primaryModel: 'Unknown',
284+
totalTokens: 0,
285+
lastUpdated: new Date(),
286+
error: 'No cost data available',
287+
});
288+
}
289+
290+
// Add conversationId to response
291+
costDisplay.conversationId = conversationId;
292+
293+
res.json(costDisplay);
294+
} catch (error) {
295+
logger.error('Error getting conversation cost:', error);
296+
res.status(500).json({
297+
error: 'Failed to calculate conversation cost',
298+
});
299+
}
300+
});
301+
302+
/**
303+
* POST /costs
304+
* Get cost summaries for multiple conversations
305+
* Body: { conversationIds: string[] }
306+
*/
307+
router.post('/costs', async (req, res) => {
308+
try {
309+
const { conversationIds } = req.body;
310+
const userId = req.user.id;
311+
312+
if (!Array.isArray(conversationIds)) {
313+
return res.status(400).json({
314+
error: 'conversationIds must be an array',
315+
});
316+
}
317+
318+
if (conversationIds.length > 50) {
319+
return res.status(400).json({
320+
error: 'Maximum 50 conversations allowed per request',
321+
});
322+
}
323+
324+
const costs = await getMultipleConversationCosts(conversationIds, userId);
325+
res.json(costs);
326+
} catch (error) {
327+
logger.error('Error getting multiple conversation costs:', error);
328+
res.status(500).json({
329+
error: 'Failed to calculate conversation costs',
330+
});
331+
}
332+
});
333+
232334
module.exports = router;

0 commit comments

Comments
 (0)