jamalinu commited on
Commit
ea4b97d
·
verified ·
1 Parent(s): 2f67e47

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +44 -300
index.html CHANGED
@@ -3,48 +3,21 @@ import json
3
  import logging
4
  import io
5
  import time
6
- from typing import Optional
7
 
8
- from fastapi import FastAPI, UploadFile, File, HTTPException
9
  from fastapi.responses import FileResponse, JSONResponse
10
  from fastapi.middleware.cors import CORSMiddleware
11
- from pydantic import BaseModel, validator
12
  from groq import Groq, APIError
13
  import pdfplumber
14
 
15
  # ==================== CONFIGURACIÓN ====================
16
- logging.basicConfig(
17
- level=logging.INFO,
18
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
19
- )
20
  logger = logging.getLogger(__name__)
21
 
22
- # Obtener API key de los secrets de Hugging Face
23
- GROQ_API_KEY = os.getenv("GROQ_API_KEY")
24
- if not GROQ_API_KEY:
25
- logger.warning("GROQ_API_KEY no está configurada en los secrets de Hugging Face")
26
-
27
- # ==================== CONSTANTES ====================
28
- MAX_TEXT_LENGTH = 4000
29
- MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
30
- MAX_RETRIES = 2
31
- EXPECTED_KEYS = {'grice_maxims', 'pragmatics', 'ambiguity', 'fallacies'}
32
-
33
- # ==================== INICIALIZACIÓN ====================
34
- app = FastAPI(
35
- title="PragmaLens API",
36
- description="API para análisis pragmático de textos",
37
- version="1.0.0"
38
- )
39
 
40
- # Inicializar cliente solo si hay API key
41
- client = None
42
- if GROQ_API_KEY:
43
- client = Groq(api_key=GROQ_API_KEY)
44
- else:
45
- logger.warning("Groq client no inicializado: falta API key")
46
-
47
- # Middleware CORS
48
  app.add_middleware(
49
  CORSMiddleware,
50
  allow_origins=["*"],
@@ -52,293 +25,64 @@ app.add_middleware(
52
  allow_headers=["*"],
53
  )
54
 
55
- # Middleware para logging de tiempo de respuesta
56
- @app.middleware("http")
57
- async def add_process_time_header(request, call_next):
58
- start_time = time.time()
59
- response = await call_next(request)
60
- process_time = time.time() - start_time
61
- response.headers["X-Process-Time"] = str(process_time)
62
- logger.info(f"{request.method} {request.url.path} - {process_time:.3f}s")
63
- return response
64
 
65
- # ==================== MODELOS ====================
66
  class TextInput(BaseModel):
67
- text: str
68
 
69
- @validator('text')
70
- def text_not_empty(cls, v):
71
- if not v or not v.strip():
 
72
  raise ValueError('El texto no puede estar vacío')
73
  return v.strip()
74
 
75
- # ==================== FUNCIONES PRINCIPALES ====================
76
- def run_audit(text: str) -> dict:
77
- """
78
- Ejecuta el análisis del texto usando Groq API con reintentos
79
- """
80
- # Verificar que el cliente esté inicializado
81
- if not client:
82
- return {
83
- "grice_maxims": "⚠️ API key no configurada. Configura GROQ_API_KEY en los secrets de Hugging Face",
84
- "pragmatics": "⚠️ API key no configurada. Configura GROQ_API_KEY en los secrets de Hugging Face",
85
- "ambiguity": "⚠️ API key no configurada. Configura GROQ_API_KEY en los secrets de Hugging Face",
86
- "fallacies": "⚠️ API key no configurada. Configura GROQ_API_KEY en los secrets de Hugging Face"
87
- }
88
-
89
- # Truncar texto si es necesario
90
- if len(text) > MAX_TEXT_LENGTH:
91
- logger.warning(f"Texto truncado de {len(text)} a {MAX_TEXT_LENGTH} caracteres")
92
- text = text[:MAX_TEXT_LENGTH] + "..."
93
-
94
  system_prompt = (
95
- "You are a senior discourse analyst. Analyze the text for: "
96
- "1. Grice's Maxims violations (Quantity, Quality, Relation, Manner)\n"
97
- "2. Pragmatic markers and features (speech acts, implicature, deixis)\n"
98
- "3. Ambiguity cases (lexical, syntactic, pragmatic)\n"
99
- "4. Logical fallacies and argumentative flaws (ad hominem, straw man, etc.)\n\n"
100
- "Return a JSON object with these exact keys: "
101
- "grice_maxims, pragmatics, ambiguity, fallacies. "
102
- "Each value MUST be a single, continuous string description. "
103
- "No nested objects, no arrays, no lists. "
104
- "If no issues found, state 'No significant issues detected'. "
105
- "Be specific and cite examples from the text when possible."
106
  )
107
 
108
- for attempt in range(MAX_RETRIES):
109
- try:
110
- logger.info(f"Intentando análisis (intento {attempt + 1}/{MAX_RETRIES})")
111
-
112
- response = client.chat.completions.create(
113
- model="llama-3.3-70b-versatile",
114
- messages=[
115
- {"role": "system", "content": system_prompt},
116
- {"role": "user", "content": text}
117
- ],
118
- response_format={"type": "json_object"},
119
- temperature=0.1,
120
- max_tokens=1000,
121
- timeout=30.0
122
- )
123
-
124
- content = response.choices[0].message.content
125
- result = json.loads(content)
126
-
127
- # Validar y completar estructura
128
- for key in EXPECTED_KEYS:
129
- if key not in result:
130
- result[key] = "No information provided"
131
- elif not isinstance(result[key], str):
132
- # Si es objeto o array, convertir a string legible
133
- if isinstance(result[key], (dict, list)):
134
- result[key] = json.dumps(result[key], ensure_ascii=False)
135
- else:
136
- result[key] = str(result[key])
137
-
138
- logger.info("Análisis completado exitosamente")
139
- return result
140
-
141
- except (APIError, json.JSONDecodeError) as e:
142
- logger.warning(f"Intento {attempt + 1} fallido: {str(e)}")
143
- if attempt == MAX_RETRIES - 1:
144
- logger.error(f"Todos los intentos fallaron: {str(e)}")
145
- return {
146
- "grice_maxims": f"❌ Error: {str(e)[:150]}",
147
- "pragmatics": f"❌ Error: {str(e)[:150]}",
148
- "ambiguity": f"❌ Error: {str(e)[:150]}",
149
- "fallacies": f"❌ Error: {str(e)[:150]}"
150
- }
151
- time.sleep(2 ** attempt)
152
-
153
- except Exception as e:
154
- logger.error(f"Error inesperado: {str(e)}")
155
- return {
156
- "grice_maxims": f"❌ Error inesperado: {str(e)[:150]}",
157
- "pragmatics": f"❌ Error inesperado: {str(e)[:150]}",
158
- "ambiguity": f"❌ Error inesperado: {str(e)[:150]}",
159
- "fallacies": f"❌ Error inesperado: {str(e)[:150]}"
160
- }
161
-
162
- # Fallback
163
- return {
164
- "grice_maxims": "Error processing",
165
- "pragmatics": "Error processing",
166
- "ambiguity": "Error processing",
167
- "fallacies": "Error processing"
168
- }
169
-
170
- def extract_text_from_pdf(content: bytes) -> str:
171
- """
172
- Extrae texto de un archivo PDF
173
- """
174
  try:
175
- with pdfplumber.open(io.BytesIO(content)) as pdf:
176
- text_parts = []
177
- for page_num, page in enumerate(pdf.pages, 1):
178
- page_text = page.extract_text()
179
- if page_text:
180
- text_parts.append(page_text)
181
- else:
182
- logger.warning(f"Página {page_num} no tiene texto extraíble")
183
-
184
- full_text = "\n".join(text_parts)
185
-
186
- if not full_text.strip():
187
- raise HTTPException(
188
- status_code=400,
189
- detail="No se pudo extraer texto del PDF. El archivo podría estar vacío o ser una imagen escaneada."
190
- )
191
-
192
- return full_text
193
-
194
- except pdfplumber.PDFSyntaxError:
195
- raise HTTPException(
196
- status_code=400,
197
- detail="El archivo no es un PDF válido o está corrupto"
198
  )
 
199
  except Exception as e:
200
- logger.error(f"Error procesando PDF: {str(e)}")
201
- raise HTTPException(
202
- status_code=500,
203
- detail=f"Error al procesar el PDF: {str(e)}"
204
- )
205
 
206
  # ==================== ENDPOINTS ====================
207
  @app.get("/")
208
  async def root():
209
- """
210
- Endpoint raíz que devuelve la interfaz HTML
211
- """
212
- try:
213
- return FileResponse("index.html")
214
- except FileNotFoundError:
215
- logger.warning("index.html no encontrado")
216
- return JSONResponse(
217
- status_code=404,
218
- content={
219
- "detail": "index.html no encontrado",
220
- "message": "La interfaz web no está disponible"
221
- }
222
- )
223
-
224
- @app.get("/health")
225
- async def health_check():
226
- """
227
- Endpoint para verificar el estado del servicio
228
- """
229
- status = {
230
- "status": "healthy",
231
- "groq": "configured" if client else "not configured",
232
- "timestamp": time.time()
233
- }
234
-
235
- if client:
236
- try:
237
- # Verificar conexión con Groq
238
- client.models.list()
239
- status["groq"] = "connected"
240
- except Exception as e:
241
- logger.error(f"Health check - Groq error: {str(e)}")
242
- status["groq"] = "error"
243
- status["groq_error"] = str(e)
244
-
245
- return status
246
 
247
  @app.post("/analyze")
248
- async def analyze(payload: TextInput):
249
- """
250
- Analiza un texto enviado directamente
251
- """
252
- try:
253
- logger.info(f"Recibida solicitud de análisis de texto ({len(payload.text)} caracteres)")
254
- result = run_audit(payload.text)
255
- return result
256
- except Exception as e:
257
- logger.error(f"Error en /analyze: {str(e)}")
258
- raise HTTPException(
259
- status_code=500,
260
- detail=f"Error procesando la solicitud: {str(e)}"
261
- )
262
 
263
  @app.post("/analyze_pdf")
264
- async def analyze_pdf(file: UploadFile = File(...)):
265
- """
266
- Analiza un archivo PDF subido
267
- """
268
- # Validar tipo de archivo
269
- if not file.filename or not file.filename.lower().endswith('.pdf'):
270
- raise HTTPException(
271
- status_code=400,
272
- detail="Solo se aceptan archivos PDF"
273
- )
274
 
275
- # Leer y validar tamaño
276
- try:
277
- content = await file.read()
278
- file_size = len(content)
279
- logger.info(f"PDF recibido: {file.filename}, tamaño: {file_size} bytes")
280
-
281
- if file_size == 0:
282
- raise HTTPException(
283
- status_code=400,
284
- detail="El archivo está vacío"
285
- )
286
-
287
- if file_size > MAX_FILE_SIZE:
288
- raise HTTPException(
289
- status_code=400,
290
- detail=f"El archivo excede el tamaño máximo permitido ({MAX_FILE_SIZE // (1024*1024)}MB)"
291
- )
292
-
293
- # Extraer texto
294
- text = extract_text_from_pdf(content)
295
- logger.info(f"Texto extraído del PDF: {len(text)} caracteres")
296
-
297
- # Analizar
298
- result = run_audit(text)
299
- return result
300
-
301
- except HTTPException:
302
- raise
303
- except Exception as e:
304
- logger.error(f"Error en /analyze_pdf: {str(e)}")
305
- raise HTTPException(
306
- status_code=500,
307
- detail=f"Error procesando el PDF: {str(e)}"
308
- )
309
-
310
- # ==================== MANEJADORES DE ERRORES GLOBALES ====================
311
- @app.exception_handler(HTTPException)
312
- async def http_exception_handler(request, exc):
313
- logger.warning(f"HTTP Exception: {exc.detail}")
314
- return JSONResponse(
315
- status_code=exc.status_code,
316
- content={
317
- "detail": exc.detail,
318
- "status_code": exc.status_code
319
- }
320
- )
321
-
322
- @app.exception_handler(Exception)
323
- async def general_exception_handler(request, exc):
324
- logger.error(f"Excepción no manejada: {str(exc)}", exc_info=True)
325
- return JSONResponse(
326
- status_code=500,
327
- content={
328
- "detail": "Error interno del servidor",
329
- "status_code": 500
330
- }
331
- )
332
-
333
- # ==================== PUNTO DE ENTRADA ====================
334
- if __name__ == "__main__":
335
- import uvicorn
336
 
337
- logger.info("Iniciando PragmaLens API en Hugging Face...")
338
- port = int(os.getenv("PORT", 7860))
339
- uvicorn.run(
340
- "app:app",
341
- host="0.0.0.0",
342
- port=port,
343
- log_level="info"
344
- )
 
3
  import logging
4
  import io
5
  import time
6
+ from typing import Dict, Any
7
 
8
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Depends
9
  from fastapi.responses import FileResponse, JSONResponse
10
  from fastapi.middleware.cors import CORSMiddleware
11
+ from pydantic import BaseModel, Field, field_validator
12
  from groq import Groq, APIError
13
  import pdfplumber
14
 
15
  # ==================== CONFIGURACIÓN ====================
16
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
 
 
17
  logger = logging.getLogger(__name__)
18
 
19
+ app = FastAPI(title="PragmaLens API", version="1.1.0")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
 
 
 
 
 
 
 
 
21
  app.add_middleware(
22
  CORSMiddleware,
23
  allow_origins=["*"],
 
25
  allow_headers=["*"],
26
  )
27
 
28
+ # ==================== DEPENDENCIAS ====================
29
+ def get_groq_client():
30
+ api_key = os.getenv("GROQ_API_KEY")
31
+ if not api_key:
32
+ raise HTTPException(status_code=500, detail="GROQ_API_KEY no configurada")
33
+ return Groq(api_key=api_key)
 
 
 
34
 
35
+ # ==================== MODELOS (Pydantic V2) ====================
36
  class TextInput(BaseModel):
37
+ text: str = Field(..., min_length=1)
38
 
39
+ @field_validator('text')
40
+ @classmethod
41
+ def text_not_empty(cls, v: str) -> str:
42
+ if not v.strip():
43
  raise ValueError('El texto no puede estar vacío')
44
  return v.strip()
45
 
46
+ # ==================== LÓGICA ====================
47
+ def run_audit(text: str, client: Groq) -> Dict[str, str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  system_prompt = (
49
+ "You are a senior discourse analyst. Analyze for: 1. Grice's Maxims, "
50
+ "2. Pragmatic markers, 3. Ambiguity, 4. Logical fallacies. "
51
+ "Return ONLY a raw JSON object with keys: grice_maxims, pragmatics, ambiguity, fallacies. "
52
+ "Each value must be a single continuous string."
 
 
 
 
 
 
 
53
  )
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  try:
56
+ response = client.chat.completions.create(
57
+ model="llama-3.3-70b-versatile",
58
+ messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": text[:4000]}],
59
+ response_format={"type": "json_object"},
60
+ temperature=0.1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  )
62
+ return json.loads(response.choices[0].message.content)
63
  except Exception as e:
64
+ logger.error(f"Error en auditoría: {e}")
65
+ return {k: "Error procesando el análisis" for k in ['grice_maxims', 'pragmatics', 'ambiguity', 'fallacies']}
 
 
 
66
 
67
  # ==================== ENDPOINTS ====================
68
  @app.get("/")
69
  async def root():
70
+ return FileResponse("index.html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  @app.post("/analyze")
73
+ async def analyze(payload: TextInput, client: Groq = Depends(get_groq_client)):
74
+ return run_audit(payload.text, client)
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  @app.post("/analyze_pdf")
77
+ async def analyze_pdf(file: UploadFile = File(...), client: Groq = Depends(get_groq_client)):
78
+ if not file.filename.endswith('.pdf'):
79
+ raise HTTPException(status_code=400, detail="Solo archivos PDF")
 
 
 
 
 
 
 
80
 
81
+ content = await file.read()
82
+ with pdfplumber.open(io.BytesIO(content)) as pdf:
83
+ text = "\n".join([p.extract_text() or "" for p in pdf.pages[:10]])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ return run_audit(text, client)
86
+
87
+ # ==================== ENTRY POINT ====================
88
+ # Para ejecutar localmente: uvicorn main:app --reload