import { Request, Response } from 'express'; import { AppDataSource } from '../config/database'; import { Agent, AgentStatus } from '../entities/Agent'; import { Subscription, SubscriptionStatus } from '../entities/Subscription'; import { Transaction, TransactionType, TransactionStatus } from '../entities/Transaction'; import { ChatProtocolService } from '../services/chatProtocol'; import { WalletService } from '../services/walletService'; const agentRepository = AppDataSource.getRepository(Agent); const subscriptionRepository = AppDataSource.getRepository(Subscription); const chatService = new ChatProtocolService(); const walletService = new WalletService(); export class ChatController { /** * Send chat message to agent (with optional file uploads) */ async sendMessage(req: Request, res: Response) { try { const { id: agentId } = req.params; const { message, conversationId, metadata } = req.body; const userId = (req as any).user?.id; // Extract files from multer - support both 'file' (singular) and 'files' (plural) interface MulterFile { fieldname: string; originalname: string; encoding: string; mimetype: string; size: number; buffer: Buffer; } const multerFiles = (req as any).files; const files = Array.isArray(multerFiles) ? multerFiles as Array : multerFiles ? [multerFiles as MulterFile] : undefined; // Message is optional if files are provided if (!message && (!files || files.length === 0)) { return res.status(400).json({ error: 'Message or files are required' }); } const user = (req as any).user; const isAdmin = user?.isAdmin || false; // Get agent - admins can test pending/rejected agents, regular users only approved const agent = await agentRepository.findOne({ where: { id: agentId, ...(isAdmin ? {} : { status: AgentStatus.APPROVED }) }, }); if (!agent) { return res.status(404).json({ error: 'Agent not found' }); } // Admins don't pay for unapproved agents (pending/rejected) - they're testing quality const isAdminTestingUnapproved = isAdmin && agent.status !== AgentStatus.APPROVED; const shouldCharge = userId && !isAdminTestingUnapproved; // Check wallet balance and charge points (first 2 tasks free) if (shouldCharge) { const chargeResult = await walletService.chargeForTask(userId, agentId, agent); if (!chargeResult.success) { const balance = await walletService.getBalance(userId); const pointsRequired = Number(agent.pointsPerTask) || 0; const freeTasksRemaining = await walletService.getFreeTasksRemaining(userId); return res.status(402).json({ error: 'Insufficient wallet balance', details: { balance, required: pointsRequired, freeTasksRemaining, message: freeTasksRemaining > 0 ? `You have ${freeTasksRemaining} free task(s) remaining. After that, you need ${pointsRequired} points ($${(pointsRequired * 0.05).toFixed(2)}) per task.` : `You need ${pointsRequired} points ($${(pointsRequired * 0.05).toFixed(2)}) to use this agent. Please fund your wallet.`, }, }); } } else if (isAdminTestingUnapproved) { // Create a free transaction for admin testing (for tracking purposes) const wallet = await walletService.getOrCreateWallet(userId); const transactionRepository = AppDataSource.getRepository(Transaction); const transaction = transactionRepository.create({ walletId: wallet.id, userId, agentId, type: TransactionType.ADMIN_TEST, status: TransactionStatus.COMPLETED, amount: 0, balanceAfter: Number(wallet.balance), description: `Admin test of ${agent.name} (${agent.status}) - no charge`, metadata: { agentId, agentName: agent.name, agentStatus: agent.status, isAdminTest: true }, }); await transactionRepository.save(transaction); } // Convert multer files to chat protocol format const chatFiles = files?.map(file => ({ fieldname: file.fieldname, originalname: file.originalname, mimetype: file.mimetype, size: file.size, buffer: file.buffer, })); // Send message via chat protocol const response = await chatService.sendMessage(agent, { message: message || '', conversationId, userId, metadata: metadata ? (typeof metadata === 'string' ? JSON.parse(metadata) : metadata) : undefined, files: chatFiles, }); res.json(response); } catch (error: any) { console.error('Chat error:', error); res.status(500).json({ error: error.message || 'Failed to send message to agent', }); } } /** * Get conversation history */ async getHistory(req: Request, res: Response) { try { const { id: agentId } = req.params; const { conversationId, limit } = req.query; const user = (req as any).user; const userId = user?.id; const isAdmin = user?.isAdmin || false; if (!userId) { return res.status(401).json({ error: 'Authentication required' }); } // Admins can view history for any agent, regular users only approved agents const agent = await agentRepository.findOne({ where: { id: agentId, ...(isAdmin ? {} : { status: AgentStatus.APPROVED }) }, }); if (!agent) { return res.status(404).json({ error: 'Agent not found' }); } const messages = await chatService.getConversationHistory( agentId, userId, conversationId as string, limit ? Number(limit) : 50 ); res.json({ messages }); } catch (error) { console.error('Get history error:', error); res.status(500).json({ error: 'Failed to get conversation history' }); } } /** * Check if user has access to agent */ private async checkUserAccess( userId: string, agentId: string ): Promise { // Check for active subscription const subscription = await subscriptionRepository.findOne({ where: { userId, agentId, status: SubscriptionStatus.ACTIVE, isPaymentVerified: true, }, }); if (!subscription) { return false; } // Check if subscription is expired if (subscription.expiresAt && subscription.expiresAt < new Date()) { subscription.status = SubscriptionStatus.EXPIRED; await subscriptionRepository.save(subscription); return false; } return true; } }