Spaces:
Sleeping
Sleeping
| const db = require('../db/database'); | |
| const { v4: uuidv4 } = require('uuid'); | |
| const Conversation = { | |
| create(data) { | |
| if (!data.user_id) throw new Error('Conversation.create: user_id is required'); | |
| const id = data.id || uuidv4(); | |
| const now = new Date().toISOString(); | |
| const stmt = db.prepare(` | |
| INSERT INTO conversations (id, user_id, task_type, status, context_summary, started_at, ended_at) | |
| VALUES (?, ?, ?, ?, ?, ?, ?) | |
| `); | |
| stmt.run( | |
| id, | |
| data.user_id, | |
| data.task_type || null, | |
| data.status || 'active', | |
| data.context_summary || null, | |
| data.started_at || now, | |
| data.ended_at || null | |
| ); | |
| return this.findById(id); | |
| }, | |
| findById(id) { | |
| return db.prepare('SELECT * FROM conversations WHERE id = ?').get(id) || null; | |
| }, | |
| findByUserId(userId) { | |
| return db.prepare('SELECT * FROM conversations WHERE user_id = ? ORDER BY started_at DESC').all(userId); | |
| }, | |
| findActive(userId) { | |
| return db.prepare("SELECT * FROM conversations WHERE user_id = ? AND status = 'active' ORDER BY started_at DESC").all(userId); | |
| }, | |
| update(id, fields) { | |
| const allowed = ['task_type', 'status', 'context_summary', 'ended_at']; | |
| const updates = []; | |
| const values = []; | |
| for (const key of allowed) { | |
| if (fields[key] !== undefined) { | |
| updates.push(`${key} = ?`); | |
| values.push(fields[key]); | |
| } | |
| } | |
| if (updates.length === 0) return this.findById(id); | |
| values.push(id); | |
| db.prepare(`UPDATE conversations SET ${updates.join(', ')} WHERE id = ?`).run(...values); | |
| return this.findById(id); | |
| }, | |
| close(id) { | |
| const now = new Date().toISOString(); | |
| db.prepare("UPDATE conversations SET status = 'closed', ended_at = ? WHERE id = ?").run(now, id); | |
| return this.findById(id); | |
| }, | |
| /** | |
| * Mark active conversations with no recent activity as abandoned. | |
| * @param {number} cutoffMinutes conversations idle longer than this are abandoned | |
| * @returns {number} count of conversations abandoned | |
| */ | |
| abandonStale(cutoffMinutes) { | |
| const cutoff = new Date(Date.now() - cutoffMinutes * 60 * 1000).toISOString(); | |
| const now = new Date().toISOString(); | |
| const staleConversations = db.prepare(` | |
| SELECT c.id | |
| FROM conversations c | |
| LEFT JOIN ( | |
| SELECT conversation_id, MAX(created_at) AS last_message_at | |
| FROM messages | |
| GROUP BY conversation_id | |
| ) m ON c.id = m.conversation_id | |
| WHERE c.status = 'active' | |
| AND ( | |
| COALESCE(m.last_message_at, c.started_at) < ? | |
| ) | |
| `).all(cutoff); | |
| for (const row of staleConversations) { | |
| this.update(row.id, { status: 'abandoned', ended_at: now }); | |
| } | |
| return staleConversations.length; | |
| }, | |
| }; | |
| module.exports = Conversation; | |