File size: 7,005 Bytes
b5e5eac 4a31a72 b5e5eac 4068be5 b5e5eac 4068be5 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac 4a31a72 b5e5eac |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
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<MulterFile>
: 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<boolean> {
// 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;
}
}
|