| 190 | } |
| 191 | |
| 192 | async function getConversations(teamId, userId, limit = 20, offset = 0) { |
| 193 | const conversations = await db.AiConversation.findAll({ |
| 194 | where: { |
| 195 | team_id: teamId, |
| 196 | user_id: userId, |
| 197 | }, |
| 198 | order: [["updatedAt", "DESC"]], |
| 199 | limit, |
| 200 | offset, |
| 201 | attributes: ["id", "title", "status", "message_count", "createdAt", "updatedAt", "source"], |
| 202 | include: [ |
| 203 | { |
| 204 | model: db.AiUsage, |
| 205 | attributes: [], |
| 206 | } |
| 207 | ], |
| 208 | }); |
| 209 | |
| 210 | // Compute token totals from AiUsage for each conversation |
| 211 | const conversationsWithUsage = await Promise.all(conversations.map(async (conv) => { |
| 212 | const usageStats = await db.AiUsage.findAll({ |
| 213 | where: { conversation_id: conv.id }, |
| 214 | attributes: [ |
| 215 | [fn("SUM", col("total_tokens")), "total_tokens"], |
| 216 | [fn("SUM", col("prompt_tokens")), "prompt_tokens"], |
| 217 | [fn("SUM", col("completion_tokens")), "completion_tokens"], |
| 218 | ], |
| 219 | raw: true, |
| 220 | }); |
| 221 | |
| 222 | const stats = usageStats[0] || {}; |
| 223 | |
| 224 | return { |
| 225 | id: conv.id, |
| 226 | title: conv.title, |
| 227 | source: conv.source, |
| 228 | status: conv.status, |
| 229 | message_count: conv.message_count, |
| 230 | total_tokens: parseInt(stats.total_tokens, 10) || 0, |
| 231 | prompt_tokens: parseInt(stats.prompt_tokens, 10) || 0, |
| 232 | completion_tokens: parseInt(stats.completion_tokens, 10) || 0, |
| 233 | createdAt: conv.createdAt, |
| 234 | updatedAt: conv.updatedAt, |
| 235 | }; |
| 236 | })); |
| 237 | |
| 238 | return conversationsWithUsage; |
| 239 | } |
| 240 | |
| 241 | async function getConversation(conversationId, teamId, userId) { |
| 242 | const conversation = await db.AiConversation.findOne({ |