File size: 7,345 Bytes
a9885dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfe8a2b
a9885dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfe8a2b
 
a9885dd
 
 
 
 
 
 
 
 
dfe8a2b
 
 
a9885dd
 
 
 
 
 
 
 
 
 
dfe8a2b
 
5fbca50
 
 
 
 
 
 
 
 
 
dfe8a2b
 
a9885dd
 
 
 
5fbca50
dfe8a2b
 
 
5fbca50
a9885dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebb0c65
a9885dd
 
 
 
 
 
 
5fbca50
 
 
a9885dd
dfe8a2b
a9885dd
 
dfe8a2b
a9885dd
 
 
 
 
5fbca50
a9885dd
 
5fbca50
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# modules/chatbot/chat_process.py
import os
import anthropic
import logging
from typing import Generator

logger = logging.getLogger(__name__)

class ChatProcessor:
    def __init__(self):
        """Inicializa el procesador de chat con la API de Claude"""
        self.client = anthropic.Anthropic(
            api_key=os.environ.get("ANTHROPIC_API_KEY")
        )
        self.conversation_history = []
        self.semantic_context = None
        self.current_lang = 'en'

    def set_semantic_context(self, text, metrics, graph_data, lang_code='en'):
        """Configura el contexto semántico completo para el chat"""
        if not text or not metrics:
            logger.error("Faltan datos esenciales para el contexto semántico")
            raise ValueError("Texto y métricas son requeridos")
            
        self.semantic_context = {
            'full_text': text,
            'key_concepts': metrics.get('key_concepts', []),
            'concept_centrality': metrics.get('concept_centrality', {}),
            'graph_available': graph_data is not None,
            'language': lang_code
        }
        self.current_lang = lang_code
        self.conversation_history = []
        logger.info("Contexto semántico configurado correctamente")

    def _get_system_prompt(self):
        """Genera el prompt del sistema con todo el contexto necesario"""
        if not self.semantic_context:
            return "You are a helpful assistant."
            
        concepts = self.semantic_context['key_concepts']
        top_concepts = ", ".join([f"{c[0]} ({c[1]:.2f})" for c in concepts[:5]])
        
        prompts = {
            'en': f"""You are a semantic analysis expert. The user analyzed a research article.
            Full text available (abbreviated for context).
            Key concepts: {top_concepts}
            Graph available: {self.semantic_context['graph_available']}
            
            Your tasks:
            1. Answer questions about concepts and their relationships
            2. Explain the semantic network structure
            3. Suggest text improvements
            4. Provide insights based on concept centrality
            5. IMPORTANT FORMATTING: Always format citations or numerical references using brackets with a leading space (e.g., text [1], concept [2]).""",
            
            'es': f"""Eres un experto en análisis semántico. El usuario analizó un artículo de investigación.
            Texto completo disponible (abreviado para contexto).
            Conceptos clave: {top_concepts}
            Gráfico disponible: {self.semantic_context['graph_available']}
            
            Tus tareas:
            1. Responder preguntas sobre conceptos y sus relaciones
            2. Explicar la estructura de la red semántica
            3. Sugerir mejoras al texto
            4. Proporcionar insights basados en centralidad de conceptos
            5. FORMATO IMPORTANTE: Formatea siempre las citas o referencias numéricas usando corchetes y asegurando un espacio antes (por ejemplo: texto [1], concepto [2]).""",
            
            'pt': f"""Você é um especialista em análise semântica. O usuário analisou um artigo de pesquisa.
            Texto completo disponível (abreviado para contexto).
            Conceitos-chave: {top_concepts}
            Gráfico disponível: {self.semantic_context['graph_available']}
            
            Suas tarefas:
            1. Responder perguntas sobre conceitos e suas relações
            2. Explicar a estrutura da rede semântica
            3. Sugerir melhorias no texto
            4. Fornecer insights com base na centralidade dos conceitos
            5. FORMATAÇÃO IMPORTANTE: Formate sempre as citações ou referências numéricas usando colchetes e garantindo um espaço antes (por exemplo: texto [1], conceito [2]).""",
            
            'fr': f"""Vous êtes un expert en analyse sémantique. L'utilisateur a analysé un article de recherche.
            Texte complet disponible (abrégé pour le contexte).
            Concepts clés: {top_concepts}
            Graphique disponible: {self.semantic_context['graph_available']}
            
            Vos tâches:
            1. Répondre aux questions sur les concepts et leurs relations
            2. Expliquer la structure du réseau sémantique
            3. Suggérer des améliorations de texte
            4. Fournir des insights basés sur la centralité des concepts
            5. FORMATAGE IMPORTANT: Formatez toujours les citations ou les références numériques en utilisant des crochets et en assurant un espace avant (par exemple: texte [1], concept [2])."""
        }
        
        return prompts.get(self.current_lang, prompts['en'])

    def clean_generated_text(self, text):
        """Limpia caracteres especiales del texto generado SIN eliminar espacios en blanco de los extremos."""
        # Se elimina .strip() para no romper los espacios entre chunks del stream
        return text.replace("\u2588", "").replace("▌", "")

    def process_chat_input(self, message: str, lang_code: str) -> Generator[str, None, None]:
        """Procesa el mensaje con todo el contexto disponible"""
        try:
            if not self.semantic_context:
                yield "Error: Contexto semántico no configurado. Recargue el análisis."
                return
                
            if lang_code != self.current_lang:
                self.current_lang = lang_code
                logger.info(f"Idioma cambiado a: {lang_code}")

            messages = [
                {
                    "role": "user",
                    "content": f"Documento analizado (extracto):\n{self.semantic_context['full_text'][:2000]}..."
                },
                *self.conversation_history,
                {"role": "user", "content": message}
            ]

            with self.client.messages.stream(
                model="claude-sonnet-4-5-20250929",
                max_tokens=4000,
                temperature=0.7,
                system=self._get_system_prompt(),
                messages=messages
            ) as stream:
                full_response = ""
                for chunk in stream.text_stream:
                    cleaned_chunk = self.clean_generated_text(chunk)
                    full_response += cleaned_chunk
                    yield cleaned_chunk
                
                # Opcional: Aplicar un strip final solo al guardarlo en el historial
                self.conversation_history.extend([
                    {"role": "user", "content": message},
                    {"role": "assistant", "content": full_response.strip()}
                ])
                logger.info("Respuesta generada y guardada en historial")

        except Exception as e:
            logger.error(f"Error en process_chat_input: {str(e)}", exc_info=True)
            error_messages = {
                'en': "Error processing message. Please reload the analysis.",
                'es': "Error al procesar mensaje. Recargue el análisis.",
                'pt': "Erro ao processar mensagem. Recarregue a análise.",
                'fr': "Erreur lors du traitement du message. Veuillez recharger l'analyse."
            }
            yield error_messages.get(self.current_lang, "Processing error")