|
|
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 { |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
const agent = await agentRepository.findOne({ |
|
|
where: { |
|
|
id: agentId, |
|
|
...(isAdmin ? {} : { status: AgentStatus.APPROVED }) |
|
|
}, |
|
|
}); |
|
|
|
|
|
if (!agent) { |
|
|
return res.status(404).json({ error: 'Agent not found' }); |
|
|
} |
|
|
|
|
|
|
|
|
const isAdminTestingUnapproved = isAdmin && agent.status !== AgentStatus.APPROVED; |
|
|
const shouldCharge = userId && !isAdminTestingUnapproved; |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
const chatFiles = files?.map(file => ({ |
|
|
fieldname: file.fieldname, |
|
|
originalname: file.originalname, |
|
|
mimetype: file.mimetype, |
|
|
size: file.size, |
|
|
buffer: file.buffer, |
|
|
})); |
|
|
|
|
|
|
|
|
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', |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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' }); |
|
|
} |
|
|
|
|
|
|
|
|
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' }); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async checkUserAccess( |
|
|
userId: string, |
|
|
agentId: string |
|
|
): Promise<boolean> { |
|
|
|
|
|
const subscription = await subscriptionRepository.findOne({ |
|
|
where: { |
|
|
userId, |
|
|
agentId, |
|
|
status: SubscriptionStatus.ACTIVE, |
|
|
isPaymentVerified: true, |
|
|
}, |
|
|
}); |
|
|
|
|
|
if (!subscription) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (subscription.expiresAt && subscription.expiresAt < new Date()) { |
|
|
subscription.status = SubscriptionStatus.EXPIRED; |
|
|
await subscriptionRepository.save(subscription); |
|
|
return false; |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
} |
|
|
|