Spaces:
Sleeping
Sleeping
Update modules/contexto.py
Browse files- modules/contexto.py +418 -686
modules/contexto.py
CHANGED
|
@@ -1,722 +1,454 @@
|
|
| 1 |
-
# modules/
|
| 2 |
"""
|
| 3 |
-
✅ TOTALMENTE ADAPTADO ao
|
| 4 |
-
✅
|
| 5 |
-
✅
|
| 6 |
-
✅
|
| 7 |
"""
|
| 8 |
|
| 9 |
-
import
|
|
|
|
|
|
|
| 10 |
import time
|
| 11 |
-
import os
|
| 12 |
import json
|
| 13 |
-
import
|
| 14 |
-
from
|
| 15 |
-
from typing import Optional, List, Dict, Any, Tuple
|
| 16 |
-
from loguru import logger
|
| 17 |
|
| 18 |
-
|
| 19 |
-
def __init__(self, db_path: str = "akira.db"):
|
| 20 |
-
self.db_path = db_path
|
| 21 |
-
self.max_retries = 5
|
| 22 |
-
self.retry_delay = 0.1
|
| 23 |
-
|
| 24 |
-
db_dir = os.path.dirname(self.db_path)
|
| 25 |
-
if db_dir:
|
| 26 |
-
os.makedirs(db_dir, exist_ok=True)
|
| 27 |
-
|
| 28 |
-
self._init_db()
|
| 29 |
-
self._ensure_columns()
|
| 30 |
-
logger.info(f"✅ Database inicializado: {self.db_path}")
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
if commit:
|
| 51 |
-
conn.commit()
|
| 52 |
-
|
| 53 |
-
if fetch and query.strip().upper().startswith('SELECT'):
|
| 54 |
-
return c.fetchall()
|
| 55 |
-
elif fetch:
|
| 56 |
-
return c.fetchall() if c.description else []
|
| 57 |
-
else:
|
| 58 |
-
return c.lastrowid
|
| 59 |
-
except sqlite3.OperationalError as e:
|
| 60 |
-
if "database is locked" in str(e) and attempt < self.max_retries - 1:
|
| 61 |
-
time.sleep(self.retry_delay * (2 ** attempt))
|
| 62 |
-
continue
|
| 63 |
-
logger.error(f"Erro SQL: {e}")
|
| 64 |
-
raise
|
| 65 |
-
except Exception as e:
|
| 66 |
-
logger.error(f"Erro na query: {e}")
|
| 67 |
-
raise
|
| 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 |
-
reply_info_json TEXT,
|
| 95 |
-
|
| 96 |
-
-- Estado
|
| 97 |
-
humor TEXT DEFAULT 'normal_ironico',
|
| 98 |
-
modo_resposta TEXT DEFAULT 'normal_ironico',
|
| 99 |
-
emocao_detectada TEXT,
|
| 100 |
-
confianca_emocao REAL DEFAULT 0.5,
|
| 101 |
-
|
| 102 |
-
-- Grupo
|
| 103 |
-
grupo_id TEXT DEFAULT '',
|
| 104 |
-
grupo_nome TEXT DEFAULT '',
|
| 105 |
-
|
| 106 |
-
-- Audio
|
| 107 |
-
audio_transcricao TEXT,
|
| 108 |
-
fonte_stt TEXT DEFAULT 'deepgram',
|
| 109 |
-
confianca_stt REAL DEFAULT 0.0,
|
| 110 |
-
|
| 111 |
-
-- Meta
|
| 112 |
-
comando_executado TEXT,
|
| 113 |
-
has_media BOOLEAN DEFAULT 0,
|
| 114 |
-
media_type TEXT DEFAULT '',
|
| 115 |
-
message_id TEXT UNIQUE,
|
| 116 |
-
bot_response_time_ms INTEGER DEFAULT 0,
|
| 117 |
-
is_mention BOOLEAN DEFAULT 0,
|
| 118 |
-
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 119 |
-
deletado BOOLEAN DEFAULT 0
|
| 120 |
-
)
|
| 121 |
-
''')
|
| 122 |
-
|
| 123 |
-
# Usuários privilegiados
|
| 124 |
-
c.execute('''
|
| 125 |
-
CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
|
| 126 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 127 |
-
numero TEXT UNIQUE NOT NULL,
|
| 128 |
-
nome TEXT NOT NULL,
|
| 129 |
-
nome_curto TEXT,
|
| 130 |
-
tom_inicial TEXT DEFAULT 'formal',
|
| 131 |
-
pode_dar_ordens BOOLEAN DEFAULT 0,
|
| 132 |
-
pode_usar_reset BOOLEAN DEFAULT 0,
|
| 133 |
-
pode_forcar_modo BOOLEAN DEFAULT 0,
|
| 134 |
-
pode_apagar_mensagens BOOLEAN DEFAULT 0,
|
| 135 |
-
pode_moderar_grupos BOOLEAN DEFAULT 0,
|
| 136 |
-
nivel_acesso TEXT DEFAULT 'vip',
|
| 137 |
-
ultimo_comando TEXT,
|
| 138 |
-
timestamp_comando DATETIME,
|
| 139 |
-
comandos_executados INTEGER DEFAULT 0,
|
| 140 |
-
comandos_falhos INTEGER DEFAULT 0,
|
| 141 |
-
config_personalizada TEXT DEFAULT '{}',
|
| 142 |
-
data_criacao DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 143 |
-
data_atualizacao DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 144 |
-
)
|
| 145 |
-
''')
|
| 146 |
-
|
| 147 |
-
# Contexto
|
| 148 |
-
c.execute('''
|
| 149 |
-
CREATE TABLE IF NOT EXISTS contexto (
|
| 150 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 151 |
-
numero TEXT UNIQUE NOT NULL,
|
| 152 |
-
contexto_id TEXT NOT NULL,
|
| 153 |
-
tipo_contexto TEXT DEFAULT 'pv',
|
| 154 |
-
historico TEXT,
|
| 155 |
-
humor_atual TEXT DEFAULT 'normal_ironico',
|
| 156 |
-
modo_resposta TEXT DEFAULT 'normal_ironico',
|
| 157 |
-
nivel_transicao INTEGER DEFAULT 0,
|
| 158 |
-
humor_alvo TEXT DEFAULT 'normal_ironico',
|
| 159 |
-
termos TEXT,
|
| 160 |
-
girias TEXT,
|
| 161 |
-
tom TEXT DEFAULT 'normal',
|
| 162 |
-
emocao_tendencia TEXT DEFAULT 'neutral',
|
| 163 |
-
volatilidade REAL DEFAULT 0.5,
|
| 164 |
-
nome_usuario TEXT DEFAULT '',
|
| 165 |
-
ultima_mensagem_audio BOOLEAN DEFAULT 0,
|
| 166 |
-
frequencia_audio INTEGER DEFAULT 0,
|
| 167 |
-
prefere_audio BOOLEAN DEFAULT 0,
|
| 168 |
-
nivel_confianca_stt REAL DEFAULT 0.0,
|
| 169 |
-
configuracao_reply TEXT DEFAULT '{}',
|
| 170 |
-
estatisticas_interacao TEXT DEFAULT '{}',
|
| 171 |
-
ultimo_contato DATETIME,
|
| 172 |
-
data_criacao DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 173 |
-
data_atualizacao DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 174 |
-
)
|
| 175 |
-
''')
|
| 176 |
-
|
| 177 |
-
# Training examples
|
| 178 |
-
c.execute('''
|
| 179 |
-
CREATE TABLE IF NOT EXISTS training_examples (
|
| 180 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 181 |
-
input_text TEXT NOT NULL,
|
| 182 |
-
output_text TEXT NOT NULL,
|
| 183 |
-
humor TEXT DEFAULT 'normal_ironico',
|
| 184 |
-
modo_resposta TEXT DEFAULT 'normal_ironico',
|
| 185 |
-
emocao_contexto TEXT,
|
| 186 |
-
contexto_super_claro TEXT,
|
| 187 |
-
tipo_interacao TEXT DEFAULT 'normal',
|
| 188 |
-
score_relevancia REAL DEFAULT 1.0,
|
| 189 |
-
tags TEXT DEFAULT '',
|
| 190 |
-
qualidade_score REAL DEFAULT 1.0,
|
| 191 |
-
usado BOOLEAN DEFAULT 0,
|
| 192 |
-
usado_para_finetuning BOOLEAN DEFAULT 0,
|
| 193 |
-
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 194 |
-
)
|
| 195 |
-
''')
|
| 196 |
-
|
| 197 |
-
# Transições de humor
|
| 198 |
-
c.execute('''
|
| 199 |
-
CREATE TABLE IF NOT EXISTS transicoes_humor (
|
| 200 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 201 |
-
numero TEXT NOT NULL,
|
| 202 |
-
contexto_id TEXT NOT NULL,
|
| 203 |
-
humor_anterior TEXT NOT NULL,
|
| 204 |
-
humor_novo TEXT NOT NULL,
|
| 205 |
-
emocao_trigger TEXT,
|
| 206 |
-
confianca_emocao REAL,
|
| 207 |
-
nivel_transicao INTEGER,
|
| 208 |
-
razao TEXT,
|
| 209 |
-
intensidade REAL DEFAULT 0.5,
|
| 210 |
-
contexto_mensagem TEXT,
|
| 211 |
-
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 212 |
-
)
|
| 213 |
-
''')
|
| 214 |
-
|
| 215 |
-
# Gírias
|
| 216 |
-
c.execute('''
|
| 217 |
-
CREATE TABLE IF NOT EXISTS girias_aprendidas (
|
| 218 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 219 |
-
numero TEXT NOT NULL,
|
| 220 |
-
contexto_id TEXT NOT NULL,
|
| 221 |
-
giria TEXT NOT NULL,
|
| 222 |
-
significado TEXT NOT NULL,
|
| 223 |
-
contexto TEXT,
|
| 224 |
-
frequencia INTEGER DEFAULT 1,
|
| 225 |
-
ultimo_uso DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 226 |
-
data_criacao DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 227 |
-
UNIQUE(numero, giria)
|
| 228 |
-
)
|
| 229 |
-
''')
|
| 230 |
-
|
| 231 |
-
# Comandos executados
|
| 232 |
-
c.execute('''
|
| 233 |
-
CREATE TABLE IF NOT EXISTS comandos_executados (
|
| 234 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 235 |
-
numero TEXT NOT NULL,
|
| 236 |
-
comando TEXT NOT NULL,
|
| 237 |
-
parametros TEXT,
|
| 238 |
-
sucesso BOOLEAN DEFAULT 1,
|
| 239 |
-
resposta TEXT,
|
| 240 |
-
tipo_conversa TEXT DEFAULT 'pv',
|
| 241 |
-
grupo_id TEXT DEFAULT '',
|
| 242 |
-
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 243 |
-
)
|
| 244 |
-
''')
|
| 245 |
-
|
| 246 |
-
# Reset log
|
| 247 |
-
c.execute('''
|
| 248 |
-
CREATE TABLE IF NOT EXISTS reset_log (
|
| 249 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 250 |
-
numero TEXT NOT NULL,
|
| 251 |
-
tipo_reset TEXT NOT NULL,
|
| 252 |
-
itens_apagados INTEGER DEFAULT 0,
|
| 253 |
-
motivo TEXT,
|
| 254 |
-
sucesso BOOLEAN DEFAULT 1,
|
| 255 |
-
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 256 |
-
)
|
| 257 |
-
''')
|
| 258 |
-
|
| 259 |
-
conn.commit()
|
| 260 |
-
logger.info("✅ Tabelas criadas/verificadas")
|
| 261 |
-
|
| 262 |
-
except Exception as e:
|
| 263 |
-
logger.error(f"❌ Erro ao criar tabelas: {e}")
|
| 264 |
-
raise
|
| 265 |
-
|
| 266 |
-
def _ensure_columns(self):
|
| 267 |
-
"""Garante que todas as colunas existam"""
|
| 268 |
-
try:
|
| 269 |
-
with self._get_connection() as conn:
|
| 270 |
-
c = conn.cursor()
|
| 271 |
-
|
| 272 |
-
# Colunas para mensagens
|
| 273 |
-
novas_colunas = [
|
| 274 |
-
("tipo_mensagem", "TEXT DEFAULT 'texto'"),
|
| 275 |
-
("reply_info_json", "TEXT"),
|
| 276 |
-
("usuario_nome", "TEXT DEFAULT ''"),
|
| 277 |
-
("grupo_id", "TEXT DEFAULT ''"),
|
| 278 |
-
("grupo_nome", "TEXT DEFAULT ''"),
|
| 279 |
-
("audio_transcricao", "TEXT"),
|
| 280 |
-
("fonte_stt", "TEXT DEFAULT 'deepgram'"),
|
| 281 |
-
("confianca_stt", "REAL DEFAULT 0.0"),
|
| 282 |
-
("comando_executado", "TEXT"),
|
| 283 |
-
("tipo_conversa", "TEXT DEFAULT 'pv'"),
|
| 284 |
-
("is_mention", "BOOLEAN DEFAULT 0"),
|
| 285 |
-
("has_media", "BOOLEAN DEFAULT 0"),
|
| 286 |
-
("media_type", "TEXT DEFAULT ''"),
|
| 287 |
-
("message_id", "TEXT UNIQUE"),
|
| 288 |
-
("bot_response_time_ms", "INTEGER DEFAULT 0")
|
| 289 |
-
]
|
| 290 |
-
|
| 291 |
-
for col_name, col_def in novas_colunas:
|
| 292 |
-
try:
|
| 293 |
-
c.execute(f"ALTER TABLE mensagens ADD COLUMN {col_name} {col_def}")
|
| 294 |
-
except sqlite3.OperationalError:
|
| 295 |
-
pass
|
| 296 |
-
|
| 297 |
-
conn.commit()
|
| 298 |
-
|
| 299 |
-
except Exception as e:
|
| 300 |
-
logger.warning(f"Erro ao verificar colunas: {e}")
|
| 301 |
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
|
| 306 |
-
def
|
| 307 |
-
|
| 308 |
-
mensagem_original: str = None,
|
| 309 |
-
mensagem_citada_limpa: str = None,
|
| 310 |
-
reply_to_bot: bool = False,
|
| 311 |
-
humor: str = 'normal_ironico',
|
| 312 |
-
modo_resposta: str = 'normal_ironico',
|
| 313 |
-
emocao_detectada: str = None,
|
| 314 |
-
confianca_emocao: float = 0.5,
|
| 315 |
-
tipo_mensagem: str = 'texto',
|
| 316 |
-
reply_info_json: str = None,
|
| 317 |
-
usuario_nome: str = '',
|
| 318 |
-
grupo_id: str = '',
|
| 319 |
-
grupo_nome: str = '',
|
| 320 |
-
tipo_conversa: str = 'pv',
|
| 321 |
-
audio_transcricao: str = None,
|
| 322 |
-
fonte_stt: str = 'deepgram',
|
| 323 |
-
confianca_stt: float = 0.0,
|
| 324 |
-
comando_executado: str = None,
|
| 325 |
-
has_media: bool = False,
|
| 326 |
-
media_type: str = '',
|
| 327 |
-
message_id: str = None,
|
| 328 |
-
bot_response_time_ms: int = 0,
|
| 329 |
-
is_mention: bool = False) -> bool:
|
| 330 |
-
"""Salva mensagem no banco - COMPATÍVEL COM INDEX.JS"""
|
| 331 |
try:
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
# Converte reply_info para JSON se for dict
|
| 336 |
-
if isinstance(reply_info_json, dict):
|
| 337 |
-
reply_info_json = json.dumps(reply_info_json, ensure_ascii=False)
|
| 338 |
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
message_id = f"{numero_final}_{int(time.time() * 1000)}"
|
| 342 |
|
| 343 |
-
self.
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
fonte_stt[:50], confianca_stt, comando_executado[:100] if comando_executado else None,
|
| 363 |
-
int(has_media), media_type[:50], message_id[:100],
|
| 364 |
-
bot_response_time_ms, int(is_mention)
|
| 365 |
-
),
|
| 366 |
-
commit=True,
|
| 367 |
-
fetch=False
|
| 368 |
-
)
|
| 369 |
|
| 370 |
-
|
| 371 |
-
return True
|
| 372 |
|
| 373 |
except Exception as e:
|
| 374 |
-
logger.
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
try:
|
| 388 |
-
|
| 389 |
-
tags_str = ",".join(tags) if tags else ""
|
| 390 |
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
INSERT INTO training_examples
|
| 394 |
-
(input_text, output_text, humor, modo_resposta, emocao_contexto,
|
| 395 |
-
qualidade_score, contexto_super_claro, tipo_interacao, score_relevancia, tags)
|
| 396 |
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 397 |
-
""",
|
| 398 |
-
(
|
| 399 |
-
input_text[:2000], output_text[:2000], humor, modo_resposta,
|
| 400 |
-
emocao_contexto, qualidade_score, contexto_json,
|
| 401 |
-
tipo_interacao, score_relevancia, tags_str[:200]
|
| 402 |
-
),
|
| 403 |
-
commit=True,
|
| 404 |
-
fetch=False
|
| 405 |
-
)
|
| 406 |
-
logger.debug(f"✅ Training example salvo | Score: {qualidade_score:.2f}")
|
| 407 |
-
return True
|
| 408 |
-
except Exception as e:
|
| 409 |
-
logger.error(f"❌ Erro ao salvar training: {e}")
|
| 410 |
-
return False
|
| 411 |
-
|
| 412 |
-
def salvar_transicao_humor(self, numero: str, humor_anterior: str,
|
| 413 |
-
humor_novo: str, emocao_trigger: str = None,
|
| 414 |
-
confianca_emocao: float = 0.5,
|
| 415 |
-
nivel_transicao: int = 0,
|
| 416 |
-
razao: str = "", intensidade: float = 0.5,
|
| 417 |
-
contexto_mensagem: str = None):
|
| 418 |
-
"""Salva transição de humor"""
|
| 419 |
-
try:
|
| 420 |
-
contexto_id = self._gerar_contexto_id(numero, 'auto')
|
| 421 |
|
| 422 |
-
|
| 423 |
-
"""
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
confianca_emocao, nivel_transicao, razao, intensidade, contexto_mensagem)
|
| 427 |
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 428 |
-
""",
|
| 429 |
-
(
|
| 430 |
-
str(numero).strip(), contexto_id, humor_anterior, humor_novo,
|
| 431 |
-
emocao_trigger, confianca_emocao, nivel_transicao,
|
| 432 |
-
razao[:200], intensidade, contexto_mensagem[:500] if contexto_mensagem else None
|
| 433 |
-
),
|
| 434 |
-
commit=True,
|
| 435 |
-
fetch=False
|
| 436 |
-
)
|
| 437 |
-
logger.debug(f"🎭 Transição salva: {humor_anterior} → {humor_novo}")
|
| 438 |
-
except Exception as e:
|
| 439 |
-
logger.error(f"❌ Erro ao salvar transição: {e}")
|
| 440 |
-
|
| 441 |
-
def salvar_giria(self, numero: str, giria: str, significado: str, contexto: str = ""):
|
| 442 |
-
"""Salva gíria aprendida"""
|
| 443 |
-
try:
|
| 444 |
-
numero_final = str(numero).strip()
|
| 445 |
-
contexto_id = self._gerar_contexto_id(numero_final, 'auto')
|
| 446 |
|
| 447 |
-
|
| 448 |
-
"SELECT id, frequencia FROM girias_aprendidas WHERE numero = ? AND giria = ?",
|
| 449 |
-
(numero_final, giria),
|
| 450 |
-
fetch=True
|
| 451 |
-
)
|
| 452 |
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
"""
|
| 456 |
-
UPDATE girias_aprendidas
|
| 457 |
-
SET frequencia = frequencia + 1,
|
| 458 |
-
ultimo_uso = CURRENT_TIMESTAMP
|
| 459 |
-
WHERE numero = ? AND giria = ?
|
| 460 |
-
""",
|
| 461 |
-
(numero_final, giria),
|
| 462 |
-
commit=True,
|
| 463 |
-
fetch=False
|
| 464 |
-
)
|
| 465 |
-
else:
|
| 466 |
-
self._execute_with_retry(
|
| 467 |
-
"""
|
| 468 |
-
INSERT INTO girias_aprendidas
|
| 469 |
-
(numero, contexto_id, giria, significado, contexto)
|
| 470 |
-
VALUES (?, ?, ?, ?, ?)
|
| 471 |
-
""",
|
| 472 |
-
(numero_final, contexto_id, giria, significado, contexto[:100]),
|
| 473 |
-
commit=True,
|
| 474 |
-
fetch=False
|
| 475 |
-
)
|
| 476 |
-
return True
|
| 477 |
-
except Exception as e:
|
| 478 |
-
logger.error(f"❌ Erro ao salvar gíria: {e}")
|
| 479 |
-
return False
|
| 480 |
-
|
| 481 |
-
# ========================================================================
|
| 482 |
-
# MÉTODOS DE RECUPERAÇÃO
|
| 483 |
-
# ========================================================================
|
| 484 |
-
|
| 485 |
-
def recuperar_mensagens(self, numero: str, limite: int = 10) -> List[Tuple]:
|
| 486 |
-
"""Recupera mensagens do usuário"""
|
| 487 |
-
try:
|
| 488 |
-
results = self._execute_with_retry(
|
| 489 |
-
"""
|
| 490 |
-
SELECT mensagem, resposta, is_reply, mensagem_original,
|
| 491 |
-
reply_to_bot, humor, modo_resposta, timestamp
|
| 492 |
-
FROM mensagens
|
| 493 |
-
WHERE numero = ? AND deletado = 0
|
| 494 |
-
ORDER BY timestamp DESC
|
| 495 |
-
LIMIT ?
|
| 496 |
-
""",
|
| 497 |
-
(str(numero).strip(), limite),
|
| 498 |
-
fetch=True
|
| 499 |
-
)
|
| 500 |
|
| 501 |
-
|
| 502 |
-
return results[::-1] # Reverter para ordem cronológica
|
| 503 |
-
return []
|
| 504 |
-
except Exception as e:
|
| 505 |
-
logger.error(f"❌ Erro ao recuperar mensagens: {e}")
|
| 506 |
-
return []
|
| 507 |
-
|
| 508 |
-
def recuperar_humor_atual(self, numero: str) -> str:
|
| 509 |
-
"""Recupera humor atual"""
|
| 510 |
-
try:
|
| 511 |
-
result = self._execute_with_retry(
|
| 512 |
-
"SELECT humor_atual FROM contexto WHERE numero = ?",
|
| 513 |
-
(str(numero).strip(),),
|
| 514 |
-
fetch=True
|
| 515 |
-
)
|
| 516 |
-
return result[0][0] if result else "normal_ironico"
|
| 517 |
-
except Exception:
|
| 518 |
-
return "normal_ironico"
|
| 519 |
-
|
| 520 |
-
def recuperar_modo_resposta(self, numero: str) -> str:
|
| 521 |
-
"""Recupera modo de resposta"""
|
| 522 |
-
try:
|
| 523 |
-
result = self._execute_with_retry(
|
| 524 |
-
"SELECT modo_resposta FROM contexto WHERE numero = ?",
|
| 525 |
-
(str(numero).strip(),),
|
| 526 |
-
fetch=True
|
| 527 |
-
)
|
| 528 |
-
return result[0][0] if result else "normal_ironico"
|
| 529 |
-
except Exception:
|
| 530 |
-
return "normal_ironico"
|
| 531 |
-
|
| 532 |
-
def recuperar_training_examples(self, limite: int = 100, usado: bool = False) -> List[Dict]:
|
| 533 |
-
"""Recupera exemplos de treinamento"""
|
| 534 |
-
try:
|
| 535 |
-
where_clause = "WHERE usado = 0" if not usado else ""
|
| 536 |
-
results = self._execute_with_retry(
|
| 537 |
-
f"""
|
| 538 |
-
SELECT input_text, output_text, humor, modo_resposta,
|
| 539 |
-
qualidade_score, tipo_interacao
|
| 540 |
-
FROM training_examples
|
| 541 |
-
{where_clause}
|
| 542 |
-
ORDER BY qualidade_score DESC
|
| 543 |
-
LIMIT ?
|
| 544 |
-
""",
|
| 545 |
-
(limite,),
|
| 546 |
-
fetch=True
|
| 547 |
-
)
|
| 548 |
|
| 549 |
-
return [
|
| 550 |
-
{
|
| 551 |
-
"input": r[0],
|
| 552 |
-
"output": r[1],
|
| 553 |
-
"humor": r[2],
|
| 554 |
-
"modo": r[3],
|
| 555 |
-
"score": r[4],
|
| 556 |
-
"tipo": r[5]
|
| 557 |
-
}
|
| 558 |
-
for r in results
|
| 559 |
-
]
|
| 560 |
except Exception as e:
|
| 561 |
-
logger.
|
| 562 |
-
return
|
| 563 |
-
|
| 564 |
-
def
|
| 565 |
-
"""
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
)
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
# ========================================================================
|
| 583 |
|
| 584 |
-
def
|
| 585 |
-
"""
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
try:
|
| 629 |
-
|
| 630 |
-
return {"sucesso": False, "erro": "Sem permissão", "itens_apagados": 0}
|
| 631 |
|
| 632 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
|
| 634 |
-
#
|
| 635 |
-
self.
|
| 636 |
-
|
| 637 |
-
(str(numero).strip(),),
|
| 638 |
-
commit=True,
|
| 639 |
-
fetch=False
|
| 640 |
-
)
|
| 641 |
-
itens += 1
|
| 642 |
|
| 643 |
-
|
| 644 |
-
self._execute_with_retry(
|
| 645 |
-
"DELETE FROM contexto WHERE numero = ?",
|
| 646 |
-
(str(numero).strip(),),
|
| 647 |
-
commit=True,
|
| 648 |
-
fetch=False
|
| 649 |
-
)
|
| 650 |
-
itens += 1
|
| 651 |
|
| 652 |
-
|
| 653 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 654 |
|
| 655 |
except Exception as e:
|
| 656 |
-
logger.error(f"
|
| 657 |
-
return {"sucesso": False, "erro": str(e), "itens_apagados": 0}
|
| 658 |
-
|
| 659 |
-
# ========================================================================
|
| 660 |
-
# AUXILIARES
|
| 661 |
-
# ========================================================================
|
| 662 |
|
| 663 |
-
def
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
salt = f"AKIRA_V21_{data_semana}_ISOLATION"
|
| 674 |
-
raw = f"{str(numero).strip()}|{tipo}|{salt}"
|
| 675 |
-
return hashlib.sha256(raw.encode()).hexdigest()[:32]
|
| 676 |
-
|
| 677 |
-
def registrar_tom_usuario(self, numero: str, tom: str, confianca: float = 0.6,
|
| 678 |
-
mensagem_contexto: str = None) -> bool:
|
| 679 |
-
"""Registra tom detectado"""
|
| 680 |
-
try:
|
| 681 |
-
logger.info(f"✅ Tom registrado: {tom} ({confianca:.2f}) para {numero}")
|
| 682 |
-
return True
|
| 683 |
-
except Exception as e:
|
| 684 |
-
logger.error(f"❌ Erro ao registrar tom: {e}")
|
| 685 |
-
return False
|
| 686 |
-
|
| 687 |
-
def salvar_aprendizado_detalhado(self, input_text: str, output_text: str,
|
| 688 |
-
contexto: Dict, qualidade_score: float = 1.0,
|
| 689 |
-
tipo_aprendizado: str = "reply_padrao",
|
| 690 |
-
metadata: Dict = None) -> bool:
|
| 691 |
-
"""Salva aprendizado detalhado"""
|
| 692 |
-
try:
|
| 693 |
-
contexto_super_claro = {
|
| 694 |
-
'tipo_aprendizado': tipo_aprendizado,
|
| 695 |
-
'metadata': metadata or {},
|
| 696 |
-
'timestamp': time.time()
|
| 697 |
-
}
|
| 698 |
-
|
| 699 |
-
return self.salvar_training_example(
|
| 700 |
-
input_text=input_text,
|
| 701 |
-
output_text=output_text,
|
| 702 |
-
humor=contexto.get("humor_atualizado", "normal_ironico"),
|
| 703 |
-
qualidade_score=qualidade_score,
|
| 704 |
-
contexto_super_claro=contexto_super_claro,
|
| 705 |
-
tipo_interacao=tipo_aprendizado
|
| 706 |
-
)
|
| 707 |
-
except Exception as e:
|
| 708 |
-
logger.error(f"❌ Erro ao salvar aprendizado: {e}")
|
| 709 |
-
return False
|
| 710 |
-
|
| 711 |
-
def close(self):
|
| 712 |
-
"""Fecha conexão"""
|
| 713 |
-
logger.info("✅ Database fechado")
|
| 714 |
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/contexto.py — AKIRA V21 FINAL CORRIGIDO (Dezembro 2025)
|
| 2 |
"""
|
| 3 |
+
✅ TOTALMENTE ADAPTADO ao database.py correto
|
| 4 |
+
✅ Usa métodos corretos do database
|
| 5 |
+
✅ Processa reply_metadata do index.js
|
| 6 |
+
✅ Sistema emocional DistilBERT
|
| 7 |
"""
|
| 8 |
|
| 9 |
+
import logging
|
| 10 |
+
import re
|
| 11 |
+
import random
|
| 12 |
import time
|
|
|
|
| 13 |
import json
|
| 14 |
+
from typing import Optional, List, Dict, Tuple, Any
|
| 15 |
+
from collections import deque
|
|
|
|
|
|
|
| 16 |
|
| 17 |
+
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
# Modelo de emoções
|
| 20 |
+
try:
|
| 21 |
+
from transformers import pipeline
|
| 22 |
+
EMOTION_CLASSIFIER = pipeline(
|
| 23 |
+
"text-classification",
|
| 24 |
+
model="j-hartmann/emotion-english-distilroberta-base",
|
| 25 |
+
top_k=3,
|
| 26 |
+
device=-1,
|
| 27 |
+
truncation=True
|
| 28 |
+
)
|
| 29 |
+
logger.info("✅ Modelo DistilBERT carregado")
|
| 30 |
+
EMOTION_CACHE = {}
|
| 31 |
+
except Exception as e:
|
| 32 |
+
logger.warning(f"⚠️ DistilBERT não disponível: {e}")
|
| 33 |
+
EMOTION_CLASSIFIER = None
|
| 34 |
+
EMOTION_CACHE = {}
|
| 35 |
|
| 36 |
+
# Mapeamento emoção → humor
|
| 37 |
+
EMOTION_TO_HUMOR = {
|
| 38 |
+
"joy": "feliz_ironica",
|
| 39 |
+
"sadness": "triste_ironica",
|
| 40 |
+
"anger": "irritada_ironica",
|
| 41 |
+
"fear": "preocupada_ironica",
|
| 42 |
+
"surprise": "curiosa_ironica",
|
| 43 |
+
"disgust": "irritada_ironica",
|
| 44 |
+
"neutral": "normal_ironico",
|
| 45 |
+
"love": "romantico_carinhoso"
|
| 46 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
class MemoriaEmocional:
|
| 49 |
+
def __init__(self, max_size=50):
|
| 50 |
+
self.historico = deque(maxlen=max_size)
|
| 51 |
+
self.tendencia_emocional = "neutral"
|
| 52 |
+
self.volatilidade = 0.5
|
| 53 |
+
|
| 54 |
+
def adicionar_interacao(self, mensagem: str, emocao: str, confianca: float):
|
| 55 |
+
self.historico.append({
|
| 56 |
+
"mensagem": mensagem[:100],
|
| 57 |
+
"emocao": emocao,
|
| 58 |
+
"confianca": confianca,
|
| 59 |
+
"timestamp": time.time()
|
| 60 |
+
})
|
| 61 |
+
self._atualizar_tendencia()
|
| 62 |
+
|
| 63 |
+
def _atualizar_tendencia(self):
|
| 64 |
+
if not self.historico:
|
| 65 |
+
return
|
| 66 |
+
recentes = list(self.historico)[-10:]
|
| 67 |
+
contagem = {}
|
| 68 |
+
for entry in recentes:
|
| 69 |
+
emocao = entry["emocao"]
|
| 70 |
+
contagem[emocao] = contagem.get(emocao, 0) + entry["confianca"]
|
| 71 |
+
if contagem:
|
| 72 |
+
self.tendencia_emocional = max(contagem, key=contagem.get)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
+
class Contexto:
|
| 75 |
+
def __init__(self, db: Any, usuario: str = "anonimo"):
|
| 76 |
+
self.db = db
|
| 77 |
+
self.usuario = usuario
|
| 78 |
+
|
| 79 |
+
# Estado
|
| 80 |
+
self.humor_atual = "normal_ironico"
|
| 81 |
+
self.modo_resposta_atual = "normal_ironico"
|
| 82 |
+
self.memoria_emocional = MemoriaEmocional(max_size=50)
|
| 83 |
+
|
| 84 |
+
# Transição
|
| 85 |
+
self.nivel_transicao = 0
|
| 86 |
+
self.humor_alvo = "normal_ironico"
|
| 87 |
+
self.ultima_transicao = time.time()
|
| 88 |
+
|
| 89 |
+
# Conversa
|
| 90 |
+
self.ultima_mensagem_akira = None
|
| 91 |
+
self.tipo_conversa = "pv"
|
| 92 |
+
self.is_grupo = False
|
| 93 |
+
|
| 94 |
+
# Usuário
|
| 95 |
+
self.numero_usuario = ""
|
| 96 |
+
self.nome_usuario = "Anônimo"
|
| 97 |
+
self.grupo_id = ""
|
| 98 |
+
self.grupo_nome = ""
|
| 99 |
+
|
| 100 |
+
# Histórico
|
| 101 |
+
self.historico_mensagens = []
|
| 102 |
+
|
| 103 |
+
self._carregar_estado_inicial()
|
| 104 |
+
logger.info(f"✅ Contexto inicializado: {self.usuario}")
|
| 105 |
|
| 106 |
+
def _carregar_estado_inicial(self):
|
| 107 |
+
"""Carrega estado do banco"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
try:
|
| 109 |
+
if hasattr(self.db, 'recuperar_humor_atual'):
|
| 110 |
+
self.humor_atual = self.db.recuperar_humor_atual(self.usuario)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
+
if hasattr(self.db, 'recuperar_modo_resposta'):
|
| 113 |
+
self.modo_resposta_atual = self.db.recuperar_modo_resposta(self.usuario)
|
|
|
|
| 114 |
|
| 115 |
+
if hasattr(self.db, 'recuperar_mensagens'):
|
| 116 |
+
try:
|
| 117 |
+
mensagens_db = self.db.recuperar_mensagens(self.usuario, limite=10)
|
| 118 |
+
for msg in mensagens_db:
|
| 119 |
+
if isinstance(msg, tuple) and len(msg) >= 2:
|
| 120 |
+
if msg[0]: # mensagem
|
| 121 |
+
self.historico_mensagens.append({
|
| 122 |
+
"role": "user",
|
| 123 |
+
"content": msg[0],
|
| 124 |
+
"timestamp": msg[7] if len(msg) > 7 else time.time()
|
| 125 |
+
})
|
| 126 |
+
if len(msg) > 1 and msg[1]: # resposta
|
| 127 |
+
self.historico_mensagens.append({
|
| 128 |
+
"role": "assistant",
|
| 129 |
+
"content": msg[1],
|
| 130 |
+
"timestamp": msg[7] if len(msg) > 7 else time.time()
|
| 131 |
+
})
|
| 132 |
+
except Exception as e:
|
| 133 |
+
logger.warning(f"Falha ao carregar histórico: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
self.historico_mensagens.sort(key=lambda x: x.get('timestamp', 0))
|
|
|
|
| 136 |
|
| 137 |
except Exception as e:
|
| 138 |
+
logger.warning(f"Erro ao carregar estado: {e}")
|
| 139 |
+
|
| 140 |
+
def detectar_emocao_avancada(self, mensagem: str) -> Tuple[str, float, Dict]:
|
| 141 |
+
"""Detecta emoção usando DistilBERT"""
|
| 142 |
+
mensagem_limpa = mensagem.strip()
|
| 143 |
+
cache_key = mensagem_limpa[:100].lower()
|
| 144 |
+
|
| 145 |
+
if cache_key in EMOTION_CACHE:
|
| 146 |
+
return EMOTION_CACHE[cache_key]
|
| 147 |
+
|
| 148 |
+
if not EMOTION_CLASSIFIER:
|
| 149 |
+
return self._detectar_emocao_fallback(mensagem_limpa)
|
| 150 |
+
|
| 151 |
try:
|
| 152 |
+
resultados = EMOTION_CLASSIFIER(mensagem_limpa[:256], truncation=True)
|
|
|
|
| 153 |
|
| 154 |
+
emocao_primaria = resultados[0][0]['label']
|
| 155 |
+
confianca_primaria = resultados[0][0]['score']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
+
detalhes = {
|
| 158 |
+
"primaria": {"emocao": emocao_primaria, "confianca": confianca_primaria},
|
| 159 |
+
"polaridade": "positiva" if emocao_primaria in ["joy", "love"] else "negativa" if emocao_primaria in ["anger", "sadness"] else "neutra"
|
| 160 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
+
self.memoria_emocional.adicionar_interacao(mensagem_limpa, emocao_primaria, confianca_primaria)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
+
resultado = (emocao_primaria, confianca_primaria, detalhes)
|
| 165 |
+
EMOTION_CACHE[cache_key] = resultado
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
+
return resultado
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
except Exception as e:
|
| 170 |
+
logger.warning(f"Erro no DistilBERT: {e}")
|
| 171 |
+
return self._detectar_emocao_fallback(mensagem_limpa)
|
| 172 |
+
|
| 173 |
+
def _detectar_emocao_fallback(self, mensagem: str) -> Tuple[str, float, Dict]:
|
| 174 |
+
"""Fallback para detecção de emoção"""
|
| 175 |
+
mensagem_lower = mensagem.lower()
|
| 176 |
+
positivas = ['bom', 'ótimo', 'feliz', 'adorei']
|
| 177 |
+
negativas = ['ruim', 'péssimo', 'triste', 'raiva']
|
| 178 |
+
|
| 179 |
+
pos = sum(1 for p in positivas if p in mensagem_lower)
|
| 180 |
+
neg = sum(1 for n in negativas if n in mensagem_lower)
|
| 181 |
+
|
| 182 |
+
if pos > neg and pos >= 2:
|
| 183 |
+
return ("joy", 0.7, {"primaria": {"emocao": "joy", "confianca": 0.7}})
|
| 184 |
+
elif neg > pos and neg >= 2:
|
| 185 |
+
return ("anger", 0.7, {"primaria": {"emocao": "anger", "confianca": 0.7}})
|
| 186 |
+
else:
|
| 187 |
+
return ("neutral", 0.5, {"primaria": {"emocao": "neutral", "confianca": 0.5}})
|
| 188 |
+
|
| 189 |
+
def atualizar_humor_gradual(self, emocao: str, confianca: float, tom_usuario: str,
|
| 190 |
+
usuario_privilegiado: bool = False) -> str:
|
| 191 |
+
"""Atualiza humor gradualmente"""
|
| 192 |
+
humor_anterior = self.humor_atual
|
| 193 |
+
|
| 194 |
+
# Sugere humor
|
| 195 |
+
humor_sugerido = EMOTION_TO_HUMOR.get(emocao, "normal_ironico")
|
| 196 |
+
|
| 197 |
+
if usuario_privilegiado and tom_usuario == "formal":
|
| 198 |
+
humor_sugerido = "tecnico_formal"
|
| 199 |
+
|
| 200 |
+
# Inicia transição
|
| 201 |
+
if self.humor_alvo != humor_sugerido:
|
| 202 |
+
self.humor_alvo = humor_sugerido
|
| 203 |
+
self.nivel_transicao = 0
|
| 204 |
+
|
| 205 |
+
# Transição
|
| 206 |
+
taxa = 0.5
|
| 207 |
+
if confianca > 0.8:
|
| 208 |
+
taxa += 0.3
|
| 209 |
+
if tom_usuario == "rude":
|
| 210 |
+
taxa += 0.4
|
| 211 |
+
|
| 212 |
+
self.nivel_transicao = min(3, self.nivel_transicao + taxa)
|
| 213 |
+
|
| 214 |
+
# Novo humor
|
| 215 |
+
if self.nivel_transicao >= 3:
|
| 216 |
+
novo_humor = self.humor_alvo
|
| 217 |
+
else:
|
| 218 |
+
novo_humor = self.humor_atual
|
| 219 |
+
|
| 220 |
+
# Salva transição se mudou
|
| 221 |
+
if novo_humor != humor_anterior and hasattr(self.db, 'salvar_transicao_humor'):
|
| 222 |
+
try:
|
| 223 |
+
self.db.salvar_transicao_humor(
|
| 224 |
+
self.usuario,
|
| 225 |
+
humor_anterior,
|
| 226 |
+
novo_humor,
|
| 227 |
+
emocao,
|
| 228 |
+
confianca,
|
| 229 |
+
self.nivel_transicao,
|
| 230 |
+
f"Emoção: {emocao} ({confianca:.2f})"
|
| 231 |
)
|
| 232 |
+
except Exception as e:
|
| 233 |
+
logger.warning(f"Erro ao salvar transição: {e}")
|
| 234 |
+
|
| 235 |
+
self.humor_atual = novo_humor
|
| 236 |
+
return novo_humor
|
|
|
|
| 237 |
|
| 238 |
+
def detectar_tom_usuario(self, mensagem: str) -> Tuple[str, float]:
|
| 239 |
+
"""Detecta tom do usuário"""
|
| 240 |
+
mensagem_lower = mensagem.lower()
|
| 241 |
+
|
| 242 |
+
# Formal
|
| 243 |
+
if any(x in mensagem_lower for x in ["senhor", "doutor", "por favor"]):
|
| 244 |
+
return ("formal", 0.8)
|
| 245 |
+
|
| 246 |
+
# Rude
|
| 247 |
+
rudes = ['burro', 'idiota', 'merda', 'caralho']
|
| 248 |
+
if any(x in mensagem_lower for x in rudes):
|
| 249 |
+
return ("rude", 0.9)
|
| 250 |
+
|
| 251 |
+
# Informal
|
| 252 |
+
if any(x in mensagem_lower for x in ['puto', 'mano', 'fixe']):
|
| 253 |
+
return ("informal", 0.7)
|
| 254 |
+
|
| 255 |
+
return ("neutro", 0.5)
|
| 256 |
+
|
| 257 |
+
def detectar_modo_resposta(self, mensagem: str, tom_usuario: str,
|
| 258 |
+
usuario_privilegiado: bool = False) -> str:
|
| 259 |
+
"""Detecta modo de resposta"""
|
| 260 |
+
mensagem_lower = mensagem.lower()
|
| 261 |
+
|
| 262 |
+
if usuario_privilegiado and tom_usuario == "formal":
|
| 263 |
+
return "tecnico_formal"
|
| 264 |
+
|
| 265 |
+
if tom_usuario == "rude":
|
| 266 |
+
return "agressivo_direto"
|
| 267 |
+
|
| 268 |
+
if '?' in mensagem and len(mensagem) > 100:
|
| 269 |
+
return "filosofico_ironico"
|
| 270 |
+
|
| 271 |
+
palavras_romanticas = ['amor', 'paixão', 'gosto de ti']
|
| 272 |
+
if any(p in mensagem_lower for p in palavras_romanticas):
|
| 273 |
+
return "romantico_carinhoso"
|
| 274 |
+
|
| 275 |
+
return "normal_ironico"
|
| 276 |
+
|
| 277 |
+
def analisar_intencao_e_normalizar(self, mensagem: str, historico: List[Dict] = None,
|
| 278 |
+
mensagem_citada: str = None,
|
| 279 |
+
reply_metadata: Dict = None) -> Dict[str, Any]:
|
| 280 |
+
"""Análise principal - COMPATÍVEL COM INDEX.JS"""
|
| 281 |
+
if not isinstance(mensagem, str):
|
| 282 |
+
mensagem = str(mensagem)
|
| 283 |
+
|
| 284 |
+
if historico is None:
|
| 285 |
+
historico = self.obter_historico_para_llm()
|
| 286 |
+
|
| 287 |
+
# Verifica privilégio
|
| 288 |
+
usuario_privilegiado = False
|
| 289 |
+
if self.numero_usuario and hasattr(self.db, 'is_usuario_privilegiado'):
|
| 290 |
+
try:
|
| 291 |
+
usuario_privilegiado = self.db.is_usuario_privilegiado(self.numero_usuario)
|
| 292 |
+
except:
|
| 293 |
+
pass
|
| 294 |
+
|
| 295 |
+
# Detecta emoção
|
| 296 |
+
emocao, confianca, detalhes_emocao = self.detectar_emocao_avancada(mensagem)
|
| 297 |
+
|
| 298 |
+
# Detecta tom
|
| 299 |
+
tom_usuario, intensidade_tom = self.detectar_tom_usuario(mensagem)
|
| 300 |
+
|
| 301 |
+
# Atualiza humor
|
| 302 |
+
humor_atualizado = self.atualizar_humor_gradual(
|
| 303 |
+
emocao, confianca, tom_usuario, usuario_privilegiado
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
# Detecta modo
|
| 307 |
+
modo_resposta = self.detectar_modo_resposta(mensagem, tom_usuario, usuario_privilegiado)
|
| 308 |
+
self.modo_resposta_atual = modo_resposta
|
| 309 |
+
|
| 310 |
+
# Analisa reply
|
| 311 |
+
reply_analysis = self._analisar_reply_context(mensagem_citada, reply_metadata)
|
| 312 |
+
|
| 313 |
+
# Resultado
|
| 314 |
+
resultado = {
|
| 315 |
+
"tom_usuario": tom_usuario,
|
| 316 |
+
"tom_intensidade": intensidade_tom,
|
| 317 |
+
"emocao_primaria": emocao,
|
| 318 |
+
"confianca_emocao": confianca,
|
| 319 |
+
"detalhes_emocao": detalhes_emocao,
|
| 320 |
+
"modo_resposta": modo_resposta,
|
| 321 |
+
"humor_atualizado": humor_atualizado,
|
| 322 |
+
"nivel_transicao": self.nivel_transicao,
|
| 323 |
+
"humor_alvo": self.humor_alvo,
|
| 324 |
+
"usuario_privilegiado": usuario_privilegiado,
|
| 325 |
+
"nome_usuario": self.nome_usuario,
|
| 326 |
+
"numero_usuario": self.numero_usuario,
|
| 327 |
+
"eh_resposta": reply_analysis.get("is_reply", False),
|
| 328 |
+
"eh_resposta_ao_bot": reply_analysis.get("reply_to_bot", False),
|
| 329 |
+
"mensagem_citada_limpa": mensagem_citada or "",
|
| 330 |
+
"reply_analysis": reply_analysis,
|
| 331 |
+
"reply_metadata": reply_metadata,
|
| 332 |
+
"tipo_conversa": self.tipo_conversa,
|
| 333 |
+
"is_grupo": self.is_grupo,
|
| 334 |
+
"tendencia_emocional": self.memoria_emocional.tendencia_emocional,
|
| 335 |
+
"volatilidade_usuario": self.memoria_emocional.volatilidade
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
return resultado
|
| 339 |
+
|
| 340 |
+
def _analisar_reply_context(self, mensagem_citada: str, reply_metadata: Dict) -> Dict[str, Any]:
|
| 341 |
+
"""Analisa contexto de reply"""
|
| 342 |
+
if reply_metadata:
|
| 343 |
+
return {
|
| 344 |
+
"is_reply": reply_metadata.get('is_reply', False),
|
| 345 |
+
"reply_to_bot": reply_metadata.get('reply_to_bot', False),
|
| 346 |
+
"quoted_author_name": reply_metadata.get('quoted_author_name', ''),
|
| 347 |
+
"texto_citado_completo": reply_metadata.get('texto_mensagem_citada', ''),
|
| 348 |
+
"context_hint": reply_metadata.get('context_hint', ''),
|
| 349 |
+
"source": "reply_metadata"
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
if mensagem_citada:
|
| 353 |
+
reply_to_bot = "AKIRA" in mensagem_citada.upper()
|
| 354 |
+
return {
|
| 355 |
+
"is_reply": True,
|
| 356 |
+
"reply_to_bot": reply_to_bot,
|
| 357 |
+
"quoted_author_name": "Akira" if reply_to_bot else "desconhecido",
|
| 358 |
+
"texto_citado_completo": mensagem_citada,
|
| 359 |
+
"context_hint": f"Citando {'Akira' if reply_to_bot else 'outra pessoa'}",
|
| 360 |
+
"source": "mensagem_citada"
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
return {
|
| 364 |
+
"is_reply": False,
|
| 365 |
+
"reply_to_bot": False,
|
| 366 |
+
"quoted_author_name": "",
|
| 367 |
+
"texto_citado_completo": "",
|
| 368 |
+
"context_hint": "",
|
| 369 |
+
"source": "nenhum"
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
def obter_historico_para_llm(self) -> List[Dict]:
|
| 373 |
+
"""Retorna histórico formatado"""
|
| 374 |
+
return [
|
| 375 |
+
{"role": msg["role"], "content": msg["content"][:500]}
|
| 376 |
+
for msg in self.historico_mensagens[-10:]
|
| 377 |
+
]
|
| 378 |
+
|
| 379 |
+
def atualizar_contexto(self, mensagem: str, resposta: str, numero: str,
|
| 380 |
+
is_reply: bool = False, mensagem_original: str = None,
|
| 381 |
+
reply_to_bot: bool = False):
|
| 382 |
+
"""Atualiza contexto após interação"""
|
| 383 |
try:
|
| 384 |
+
timestamp = time.time()
|
|
|
|
| 385 |
|
| 386 |
+
# Adiciona ao histórico
|
| 387 |
+
self.historico_mensagens.append({
|
| 388 |
+
"role": "user",
|
| 389 |
+
"content": mensagem,
|
| 390 |
+
"timestamp": timestamp,
|
| 391 |
+
"is_reply": is_reply,
|
| 392 |
+
"reply_to_bot": reply_to_bot
|
| 393 |
+
})
|
| 394 |
+
self.historico_mensagens.append({
|
| 395 |
+
"role": "assistant",
|
| 396 |
+
"content": resposta,
|
| 397 |
+
"timestamp": timestamp
|
| 398 |
+
})
|
| 399 |
|
| 400 |
+
# Limita
|
| 401 |
+
if len(self.historico_mensagens) > 20:
|
| 402 |
+
self.historico_mensagens = self.historico_mensagens[-20:]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
|
| 404 |
+
self.ultima_mensagem_akira = resposta
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
+
# Salva no banco
|
| 407 |
+
if hasattr(self.db, 'salvar_mensagem'):
|
| 408 |
+
try:
|
| 409 |
+
self.db.salvar_mensagem(
|
| 410 |
+
usuario=self.nome_usuario,
|
| 411 |
+
mensagem=mensagem,
|
| 412 |
+
resposta=resposta,
|
| 413 |
+
numero=numero,
|
| 414 |
+
is_reply=is_reply,
|
| 415 |
+
mensagem_original=mensagem_original or '',
|
| 416 |
+
reply_to_bot=reply_to_bot,
|
| 417 |
+
humor=self.humor_atual,
|
| 418 |
+
modo_resposta=self.modo_resposta_atual,
|
| 419 |
+
usuario_nome=self.nome_usuario,
|
| 420 |
+
tipo_conversa=self.tipo_conversa
|
| 421 |
+
)
|
| 422 |
+
except Exception as e:
|
| 423 |
+
logger.warning(f"Erro ao salvar mensagem: {e}")
|
| 424 |
|
| 425 |
except Exception as e:
|
| 426 |
+
logger.error(f"Erro ao atualizar contexto: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
|
| 428 |
+
def atualizar_informacoes_usuario(self, nome: str, numero: str,
|
| 429 |
+
grupo_id: str = "", grupo_nome: str = "",
|
| 430 |
+
tipo_conversa: str = "pv"):
|
| 431 |
+
"""Atualiza informações do usuário"""
|
| 432 |
+
self.nome_usuario = nome or self.nome_usuario
|
| 433 |
+
self.numero_usuario = numero or self.numero_usuario
|
| 434 |
+
self.grupo_id = grupo_id or self.grupo_id
|
| 435 |
+
self.grupo_nome = grupo_nome or self.grupo_nome
|
| 436 |
+
self.tipo_conversa = tipo_conversa
|
| 437 |
+
self.is_grupo = tipo_conversa == "grupo"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
|
| 439 |
+
def criar_contexto(db: Any, identificador: str, tipo: str = "pv") -> Contexto:
|
| 440 |
+
"""Cria contexto isolado"""
|
| 441 |
+
try:
|
| 442 |
+
if tipo == "grupo":
|
| 443 |
+
usuario_id = f"grupo_{identificador}"
|
| 444 |
+
else:
|
| 445 |
+
usuario_id = f"pv_{identificador}"
|
| 446 |
+
|
| 447 |
+
contexto = Contexto(db, usuario_id)
|
| 448 |
+
contexto.tipo_conversa = tipo
|
| 449 |
+
contexto.is_grupo = (tipo == "grupo")
|
| 450 |
+
|
| 451 |
+
return contexto
|
| 452 |
+
except Exception as e:
|
| 453 |
+
logger.error(f"Erro ao criar contexto: {e}")
|
| 454 |
+
return Contexto(db, "fallback")
|