akra35567 commited on
Commit
ef85f09
·
verified ·
1 Parent(s): 2708f3d

Update modules/contexto.py

Browse files
Files changed (1) hide show
  1. modules/contexto.py +418 -686
modules/contexto.py CHANGED
@@ -1,722 +1,454 @@
1
- # modules/database.py — AKIRA V21 FINAL CORRIGIDO (Dezembro 2025)
2
  """
3
- ✅ TOTALMENTE ADAPTADO ao index.js atualizado
4
- CORREÇÃO: Métodos corretos para api.py, contexto.py, treinamento.py
5
- Estrutura completa com reply_metadata
6
- Todos os métodos necessários implementados
7
  """
8
 
9
- import sqlite3
 
 
10
  import time
11
- import os
12
  import json
13
- import hashlib
14
- from datetime import datetime
15
- from typing import Optional, List, Dict, Any, Tuple
16
- from loguru import logger
17
 
18
- class Database:
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
- def _get_connection(self) -> sqlite3.Connection:
33
- conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
34
- conn.execute('PRAGMA journal_mode=WAL')
35
- conn.execute('PRAGMA synchronous=NORMAL')
36
- conn.execute('PRAGMA cache_size=2000')
37
- return conn
 
 
 
 
 
 
 
 
 
 
38
 
39
- def _execute_with_retry(self, query: str, params: Optional[tuple] = None,
40
- commit: bool = False, fetch: bool = True):
41
- for attempt in range(self.max_retries):
42
- try:
43
- with self._get_connection() as conn:
44
- c = conn.cursor()
45
- if params:
46
- c.execute(query, params)
47
- else:
48
- c.execute(query)
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
- def _init_db(self):
70
- """Cria todas as tabelas necessárias"""
71
- try:
72
- with self._get_connection() as conn:
73
- c = conn.cursor()
74
-
75
- # Tabela principal de mensagens
76
- c.execute('''
77
- CREATE TABLE IF NOT EXISTS mensagens (
78
- id INTEGER PRIMARY KEY AUTOINCREMENT,
79
- usuario TEXT NOT NULL,
80
- usuario_nome TEXT DEFAULT '',
81
- numero TEXT NOT NULL,
82
- mensagem TEXT NOT NULL,
83
- resposta TEXT NOT NULL,
84
- contexto_id TEXT NOT NULL,
85
- tipo_contexto TEXT DEFAULT 'pv',
86
- tipo_conversa TEXT DEFAULT 'pv',
87
- tipo_mensagem TEXT DEFAULT 'texto',
88
-
89
- -- Reply info
90
- is_reply BOOLEAN DEFAULT 0,
91
- mensagem_original TEXT,
92
- mensagem_citada_limpa TEXT,
93
- reply_to_bot BOOLEAN DEFAULT 0,
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
- # MÉTODOS DE SALVAMENTO (ADAPTADOS AO INDEX.JS)
304
- # ========================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
- def salvar_mensagem(self, usuario: str, mensagem: str, resposta: str,
307
- numero: str = '', is_reply: bool = False,
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
- numero_final = str(numero or usuario).strip()
333
- contexto_id = self._gerar_contexto_id(numero_final, tipo_conversa)
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
- # Gera message_id se não fornecido
340
- if not message_id:
341
- message_id = f"{numero_final}_{int(time.time() * 1000)}"
342
 
343
- self._execute_with_retry(
344
- """
345
- INSERT INTO mensagens
346
- (usuario, usuario_nome, mensagem, resposta, numero, contexto_id, tipo_contexto,
347
- tipo_conversa, tipo_mensagem, is_reply, mensagem_original, mensagem_citada_limpa,
348
- reply_to_bot, reply_info_json, humor, modo_resposta, emocao_detectada,
349
- confianca_emocao, grupo_id, grupo_nome, audio_transcricao, fonte_stt,
350
- confianca_stt, comando_executado, has_media, media_type, message_id,
351
- bot_response_time_ms, is_mention)
352
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
353
- """,
354
- (
355
- usuario[:50], usuario_nome[:100] or usuario[:100],
356
- mensagem[:4000], resposta[:4000], numero_final,
357
- contexto_id, tipo_conversa, tipo_conversa, tipo_mensagem,
358
- int(is_reply), mensagem_original, mensagem_citada_limpa,
359
- int(reply_to_bot), reply_info_json, humor, modo_resposta,
360
- emocao_detectada, confianca_emocao, grupo_id[:50], grupo_nome[:100],
361
- audio_transcricao[:2000] if audio_transcricao else None,
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
- logger.debug(f"✅ Mensagem salva: {numero_final} | {tipo_mensagem}")
371
- return True
372
 
373
  except Exception as e:
374
- logger.error(f"Erro ao salvar mensagem: {e}")
375
- return False
376
-
377
- def salvar_training_example(self, input_text: str, output_text: str,
378
- humor: str = "normal_ironico",
379
- modo_resposta: str = "normal_ironico",
380
- emocao_contexto: str = None,
381
- qualidade_score: float = 1.0,
382
- contexto_super_claro: Dict = None,
383
- tipo_interacao: str = "normal",
384
- score_relevancia: float = 1.0,
385
- tags: List[str] = None) -> bool:
386
- """Salva exemplo de treinamento"""
387
  try:
388
- contexto_json = json.dumps(contexto_super_claro, ensure_ascii=False) if contexto_super_claro else None
389
- tags_str = ",".join(tags) if tags else ""
390
 
391
- self._execute_with_retry(
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
- self._execute_with_retry(
423
- """
424
- INSERT INTO transicoes_humor
425
- (numero, contexto_id, humor_anterior, humor_novo, emocao_trigger,
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
- result = self._execute_with_retry(
448
- "SELECT id, frequencia FROM girias_aprendidas WHERE numero = ? AND giria = ?",
449
- (numero_final, giria),
450
- fetch=True
451
- )
452
 
453
- if result:
454
- self._execute_with_retry(
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
- if results:
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.error(f"Erro ao recuperar exemplos: {e}")
562
- return []
563
-
564
- def marcar_examples_como_usados(self, ids: List[int] = None):
565
- """Marca exemplos como usados"""
566
- try:
567
- if ids:
568
- placeholders = ','.join(['?'] * len(ids))
569
- query = f"UPDATE training_examples SET usado = 1 WHERE id IN ({placeholders})"
570
- self._execute_with_retry(query, tuple(ids), commit=True, fetch=False)
571
- else:
572
- self._execute_with_retry(
573
- "UPDATE training_examples SET usado = 1 WHERE usado = 0",
574
- commit=True,
575
- fetch=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  )
577
- except Exception as e:
578
- logger.error(f"Erro ao marcar exemplos: {e}")
579
-
580
- # ========================================================================
581
- # PRIVILÉGIOS
582
- # ========================================================================
583
 
584
- def is_usuario_privilegiado(self, numero: str) -> bool:
585
- """Verifica se usuário é privilegiado"""
586
- try:
587
- result = self._execute_with_retry(
588
- "SELECT 1 FROM usuarios_privilegiados WHERE numero = ?",
589
- (str(numero).strip(),),
590
- fetch=True
591
- )
592
- return bool(result)
593
- except Exception:
594
- return False
595
-
596
- def pode_usar_reset(self, numero: str) -> bool:
597
- """Verifica se pode usar reset"""
598
- try:
599
- result = self._execute_with_retry(
600
- "SELECT pode_usar_reset FROM usuarios_privilegiados WHERE numero = ?",
601
- (str(numero).strip(),),
602
- fetch=True
603
- )
604
- return bool(result and result[0][0])
605
- except Exception:
606
- return False
607
-
608
- def registrar_comando(self, numero: str, comando: str, parametros: str = None,
609
- sucesso: bool = True, resposta: str = None,
610
- tipo_conversa: str = 'pv', grupo_id: str = ''):
611
- """Registra comando executado"""
612
- try:
613
- self._execute_with_retry(
614
- """
615
- INSERT INTO comandos_executados
616
- (numero, comando, parametros, sucesso, resposta, tipo_conversa, grupo_id)
617
- VALUES (?, ?, ?, ?, ?, ?, ?)
618
- """,
619
- (str(numero).strip(), comando, parametros, int(sucesso), resposta, tipo_conversa, grupo_id),
620
- commit=True,
621
- fetch=False
622
- )
623
- except Exception as e:
624
- logger.error(f"❌ Erro ao registrar comando: {e}")
625
-
626
- def resetar_contexto_usuario(self, numero: str, tipo: str = "completo") -> Dict:
627
- """Reseta contexto do usuário"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
  try:
629
- if not self.pode_usar_reset(numero):
630
- return {"sucesso": False, "erro": "Sem permissão", "itens_apagados": 0}
631
 
632
- itens = 0
 
 
 
 
 
 
 
 
 
 
 
 
633
 
634
- # Remove mensagens
635
- self._execute_with_retry(
636
- "DELETE FROM mensagens WHERE numero = ?",
637
- (str(numero).strip(),),
638
- commit=True,
639
- fetch=False
640
- )
641
- itens += 1
642
 
643
- # Remove contexto
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
- logger.info(f"✅ Reset completo para {numero}: {itens} itens")
653
- return {"sucesso": True, "itens_apagados": itens}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
 
655
  except Exception as e:
656
- logger.error(f"Erro ao resetar: {e}")
657
- return {"sucesso": False, "erro": str(e), "itens_apagados": 0}
658
-
659
- # ========================================================================
660
- # AUXILIARES
661
- # ========================================================================
662
 
663
- def _gerar_contexto_id(self, numero: str, tipo: str = 'auto') -> str:
664
- """Gera ID único para contexto"""
665
- if tipo == 'auto':
666
- num_str = str(numero).lower()
667
- if "@g.us" in num_str or "grupo_" in num_str or "120363" in num_str:
668
- tipo = "grupo"
669
- else:
670
- tipo = "pv"
671
-
672
- data_semana = datetime.now().strftime("%Y-%W")
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
- # Singleton
716
- _db_instance = None
717
-
718
- def get_database(db_path: str = "akira.db") -> Database:
719
- global _db_instance
720
- if _db_instance is None:
721
- _db_instance = Database(db_path)
722
- return _db_instance
 
 
 
 
 
 
 
 
 
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")