mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-20 04:12:23 -06:00
feat: adiciona painel interativo com chat e IA para análise de mensagens
- Dashboard completo com métricas em tempo real - Chat interativo com IA para consultas em linguagem natural - Análise de sentimento das mensagens - Gráficos interativos (mensagens por dia, sentimentos) - Filtros avançados por instância e data - Top contatos e timeline de mensagens - API routes para stats, mensagens, sentimento e chat - Integração com PostgreSQL via Prisma - Interface moderna com Next.js 14, TypeScript e Tailwind CSS - Documentação completa com README e QUICKSTART
This commit is contained in:
98
dashboard/app/api/sentiment/route.ts
Normal file
98
dashboard/app/api/sentiment/route.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import prisma from '@/lib/prisma';
|
||||
// @ts-ignore
|
||||
import Sentiment from 'sentiment';
|
||||
|
||||
const sentiment = new Sentiment();
|
||||
|
||||
function extractMessageText(messageObj: any): string {
|
||||
if (typeof messageObj === 'string') return messageObj;
|
||||
if (!messageObj || typeof messageObj !== 'object') return '';
|
||||
|
||||
const msg = messageObj.message || messageObj;
|
||||
|
||||
if (msg.conversation) return msg.conversation;
|
||||
if (msg.extendedTextMessage?.text) return msg.extendedTextMessage.text;
|
||||
if (msg.imageMessage?.caption) return msg.imageMessage.caption;
|
||||
if (msg.videoMessage?.caption) return msg.videoMessage.caption;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function categorizeSentiment(score: number): string {
|
||||
if (score > 2) return 'very_positive';
|
||||
if (score > 0) return 'positive';
|
||||
if (score < -2) return 'very_negative';
|
||||
if (score < 0) return 'negative';
|
||||
return 'neutral';
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const instanceId = searchParams.get('instanceId');
|
||||
const limit = parseInt(searchParams.get('limit') || '1000');
|
||||
|
||||
// Construir where clause
|
||||
const where: any = {
|
||||
fromMe: false, // Apenas mensagens recebidas
|
||||
};
|
||||
if (instanceId) {
|
||||
where.instanceId = instanceId;
|
||||
}
|
||||
|
||||
// Buscar mensagens
|
||||
const messages = await prisma.message.findMany({
|
||||
where,
|
||||
take: limit,
|
||||
orderBy: { messageTimestamp: 'desc' },
|
||||
});
|
||||
|
||||
// Analisar sentimento
|
||||
const analyzed = messages.map((msg: any) => {
|
||||
const text = extractMessageText(msg.message);
|
||||
if (!text) return null;
|
||||
|
||||
const analysis = sentiment.analyze(text);
|
||||
const category = categorizeSentiment(analysis.score);
|
||||
|
||||
return {
|
||||
id: msg.id,
|
||||
text,
|
||||
sentiment: {
|
||||
score: analysis.score,
|
||||
comparative: analysis.comparative,
|
||||
category,
|
||||
positive: analysis.positive,
|
||||
negative: analysis.negative,
|
||||
},
|
||||
timestamp: msg.messageTimestamp,
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
// Calcular distribuição
|
||||
const distribution = analyzed.reduce((acc: any, item: any) => {
|
||||
const cat = item.sentiment.category;
|
||||
acc[cat] = (acc[cat] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Calcular score médio
|
||||
const avgScore = analyzed.length > 0
|
||||
? analyzed.reduce((sum: number, item: any) => sum + item.sentiment.score, 0) / analyzed.length
|
||||
: 0;
|
||||
|
||||
return NextResponse.json({
|
||||
total: analyzed.length,
|
||||
avgScore: avgScore.toFixed(2),
|
||||
distribution,
|
||||
messages: analyzed.slice(0, 100), // Retornar apenas as primeiras 100
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao analisar sentimento:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao analisar sentimento' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user