Initial commit
This commit is contained in:
7
.gitattributes
vendored
Normal file
7
.gitattributes
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.m4a filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.blob filter=lfs diff=lfs merge=lfs -text
|
||||||
|
pdf_test/imperio5.pdf !text !filter !merge !diff
|
||||||
|
pdf_test/imperio5_ocr.pdf !text !filter !merge !diff
|
||||||
|
downloads/* !text !filter !merge !diff
|
||||||
71
.gitignore
vendored
Executable file
71
.gitignore
vendored
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
# Local environment files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Application-generated data
|
||||||
|
downloads/
|
||||||
|
resumenes/
|
||||||
|
resumenes_docx/
|
||||||
|
processed_files.txt
|
||||||
|
*_unificado.docx
|
||||||
|
resumen_*.md
|
||||||
|
downloads/**/*.md
|
||||||
|
downloads/**/*.docx
|
||||||
|
resumenes_docx/**/*.docx
|
||||||
|
resumenes_docx/**/*.md
|
||||||
|
resumenes/**/*.md
|
||||||
|
resumenes/**/*.docx
|
||||||
|
|
||||||
|
# Ollama data
|
||||||
|
ollama_data/
|
||||||
|
ollama_data/models/blobs/
|
||||||
|
|
||||||
|
# Node.js
|
||||||
|
.npm/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
pdf_test/
|
||||||
|
cereal*.txt
|
||||||
|
test_*.py
|
||||||
|
docker-compose.test.yml
|
||||||
|
Dockerfile.test
|
||||||
|
requirements_summaries.txt
|
||||||
|
|
||||||
|
# Runtime state
|
||||||
|
.main_service.lock
|
||||||
|
cbc-main.pid
|
||||||
|
*.pid
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.docker/buildx/
|
||||||
|
.dotnet/
|
||||||
|
.gemini/
|
||||||
|
.ssh/
|
||||||
|
.sudo_as_admin_successful
|
||||||
|
resumenes/
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
.aider*
|
||||||
240
ARQUITECTURA.md
Normal file
240
ARQUITECTURA.md
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
# 🏗️ Arquitectura del Sistema Integrado
|
||||||
|
|
||||||
|
## 📊 Diagrama General
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ SISTEMA COMPLETO │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ SERVICIO │ │ DASHBOARD │ │
|
||||||
|
│ │ PRINCIPAL │◄────── Comparte ───────►│ WEB │ │
|
||||||
|
│ │ (main.py) │ contexto │ (dashboard) │ │
|
||||||
|
│ └──────┬───────┘ └──────┬───────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Polling cada 5s │ API REST │
|
||||||
|
│ ▼ ▼ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ PROCESAMIENTO AUTOMÁTICO │ │
|
||||||
|
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||||
|
│ │ │ AUDIO │ │ PDF │ │ TXT │ │ WEBDAV │ │ │
|
||||||
|
│ │ │Whisper+IA│ │ OCR+IA │ │ IA │ │ Sync │ │ │
|
||||||
|
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
|
||||||
|
│ └──────┼─────────────┼─────────────┼────────────┼─────────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ └─────────────┴─────────────┴────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌────────────────┐ │
|
||||||
|
│ │ NEXTCLOUD │ │
|
||||||
|
│ │ (WebDAV) │ │
|
||||||
|
│ └────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Flujo de Datos
|
||||||
|
|
||||||
|
### 1. Servicio Principal (main.py)
|
||||||
|
```
|
||||||
|
Inicio → start_dashboard() → main() [Bucle infinito]
|
||||||
|
↓ ↓ ↓
|
||||||
|
Dashboard Hilo separado Polling 5s
|
||||||
|
Web (5000) (daemon) ↓
|
||||||
|
Check Archivos
|
||||||
|
↓
|
||||||
|
Procesar
|
||||||
|
↓
|
||||||
|
Subir a Nextcloud
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Dashboard Web (dashboard.py)
|
||||||
|
```
|
||||||
|
HTTP Request → Flask App → API Endpoints
|
||||||
|
↓ ↓ ↓
|
||||||
|
localhost:5000 Routing file_manager
|
||||||
|
↓ ↓ ↓
|
||||||
|
Browser Python Code Operar archivos
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📡 Comunicación entre Componentes
|
||||||
|
|
||||||
|
### Imports Compartidos
|
||||||
|
```python
|
||||||
|
# main.py importa del dashboard
|
||||||
|
import dashboard
|
||||||
|
dashboard.app.run() # En hilo separado
|
||||||
|
|
||||||
|
# dashboard.py importa de main
|
||||||
|
from main import (
|
||||||
|
AUDIO_EXTENSIONS,
|
||||||
|
LOCAL_DOWNLOADS_PATH,
|
||||||
|
load_processed_files,
|
||||||
|
process_audio_file
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Estado Compartido
|
||||||
|
- ✅ Variables de configuración
|
||||||
|
- ✅ Funciones de procesamiento
|
||||||
|
- ✅ Registro de archivos procesados
|
||||||
|
- ✅ Conexión WebDAV a Nextcloud
|
||||||
|
|
||||||
|
## 🎯 Modos de Ejecución
|
||||||
|
|
||||||
|
### Modo 1: Servicio Completo
|
||||||
|
```bash
|
||||||
|
python3 main.py
|
||||||
|
```
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ THREAD PRINCIPAL │
|
||||||
|
│ ┌──────────────────────────────┐ │
|
||||||
|
│ │ main() - Bucle principal │ │
|
||||||
|
│ │ - Polling Nextcloud │ │
|
||||||
|
│ │ - Procesar archivos │ │
|
||||||
|
│ └──────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
│ threading.Thread
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ DASHBOARD THREAD (daemon) │
|
||||||
|
│ ┌──────────────────────────────┐ │
|
||||||
|
│ │ Flask Web Server │ │
|
||||||
|
│ │ - Puerto 5000 │ │
|
||||||
|
│ │ - API REST │ │
|
||||||
|
│ │ - Interfaz web │ │
|
||||||
|
│ └──────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modo 2: Solo Dashboard
|
||||||
|
```bash
|
||||||
|
python3 main.py dashboard-only
|
||||||
|
```
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ MAIN THREAD │
|
||||||
|
│ ┌──────────────────────────────┐ │
|
||||||
|
│ │ Flask Web Server ONLY │ │
|
||||||
|
│ │ - Puerto 5000 │ │
|
||||||
|
│ │ - Sin bucle principal │ │
|
||||||
|
│ └──────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗂️ Estructura de Archivos
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/ren/cbc/
|
||||||
|
├── main.py # Servicio principal + integra dashboard
|
||||||
|
├── dashboard.py # Aplicación Flask independiente
|
||||||
|
├── test_dashboard.py # Script de pruebas
|
||||||
|
├── templates/
|
||||||
|
│ └── index.html # Interfaz web del dashboard
|
||||||
|
├── downloads/ # Archivos temporales locales
|
||||||
|
├── processed_files.txt # Registro de procesados
|
||||||
|
├── QUICKSTART.md # Guía de inicio rápido
|
||||||
|
├── DASHBOARD_INSTRUCTIONS.md # Manual detallado
|
||||||
|
└── ARQUITECTURA.md # Este archivo
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔌 API Endpoints del Dashboard
|
||||||
|
|
||||||
|
| Método | Endpoint | Función |
|
||||||
|
|--------|----------|---------|
|
||||||
|
| GET | `/` | Página principal |
|
||||||
|
| GET | `/api/files` | Obtener lista de archivos |
|
||||||
|
| POST | `/api/reprocess` | Reprocesar archivo |
|
||||||
|
| POST | `/api/mark-unprocessed` | Marcar como no procesado |
|
||||||
|
| GET | `/api/refresh` | Refrescar lista |
|
||||||
|
| GET | `/health` | Health check |
|
||||||
|
| GET | `/downloads/<archivo>` | Descargar archivo |
|
||||||
|
|
||||||
|
## 🚀 Proceso de Inicio
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Usuario ejecuta: python3 main.py
|
||||||
|
│
|
||||||
|
├─► main.py inicia
|
||||||
|
│ │
|
||||||
|
│ ├─► acquire_lock() [Evitar múltiples instancias]
|
||||||
|
│ │
|
||||||
|
│ ├─► start_dashboard() [INICIA DASHBOARD]
|
||||||
|
│ │ │
|
||||||
|
│ │ ├─► import dashboard
|
||||||
|
│ │ │
|
||||||
|
│ │ ├─► threading.Thread(target=run_dashboard)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├─► dashboard_thread.start()
|
||||||
|
│ │ │
|
||||||
|
│ │ └─► ✅ Dashboard en http://localhost:5000
|
||||||
|
│ │
|
||||||
|
│ ├─► time.sleep(2) [Pausa para dashboard]
|
||||||
|
│ │
|
||||||
|
│ └─► main() [INICIA BUCLE PRINCIPAL]
|
||||||
|
│ │
|
||||||
|
│ └─► while True:
|
||||||
|
│ │
|
||||||
|
│ ├─► Polling Nextcloud
|
||||||
|
│ ├─► Verificar archivos nuevos
|
||||||
|
│ ├─► Procesar automáticamente
|
||||||
|
│ └─► sleep(5) [esperar 5s]
|
||||||
|
│
|
||||||
|
└─► Servicio corriendo indefinidamente
|
||||||
|
│
|
||||||
|
├─► Dashboard accesible
|
||||||
|
└─► Procesamiento activo
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Características de Seguridad
|
||||||
|
|
||||||
|
1. **Threading Daemon**
|
||||||
|
- Dashboard se cierra automáticamente con main.py
|
||||||
|
- No impide el cierre del programa
|
||||||
|
|
||||||
|
2. **Lock File**
|
||||||
|
- Evita múltiples instancias de main.py
|
||||||
|
- Protección automática via `fcntl`
|
||||||
|
|
||||||
|
3. **Error Handling**
|
||||||
|
- Dashboard puede fallar sin afectar main
|
||||||
|
- Logging detallado de errores
|
||||||
|
|
||||||
|
4. **CORS Enabled**
|
||||||
|
- Flask-CORS configurado
|
||||||
|
- Acceso desde cualquier origen
|
||||||
|
|
||||||
|
## 💡 Ventajas de la Integración
|
||||||
|
|
||||||
|
### ✅ Beneficios
|
||||||
|
- **Un solo comando** para todo
|
||||||
|
- **Contexto compartido** (config, funciones)
|
||||||
|
- **Cierre automático** (hilo daemon)
|
||||||
|
- **Logs unificados** (consola única)
|
||||||
|
- **Sin dependencias externas** (todo en main.py)
|
||||||
|
|
||||||
|
### ⚡ Rendimiento
|
||||||
|
- **Dashboard**: ~5-10MB RAM
|
||||||
|
- **Main**: Variable según procesamiento
|
||||||
|
- **Comunicación**: Directa (mismo proceso)
|
||||||
|
- **Latencia**: Mínima (sin red)
|
||||||
|
|
||||||
|
### 🛠️ Mantenimiento
|
||||||
|
- **Código unificado** en main.py
|
||||||
|
- **Menos archivos** de configuración
|
||||||
|
- **Debugging simplificado** (una sola consola)
|
||||||
|
- **Actualización fácil** (un solo archivo)
|
||||||
|
|
||||||
|
## 🎉 Resumen
|
||||||
|
|
||||||
|
El sistema está **completamente integrado**:
|
||||||
|
- ✅ Un solo comando: `python3 main.py`
|
||||||
|
- ✅ Dashboard automático en puerto 5000
|
||||||
|
- ✅ Servicio principal procesando 24/7
|
||||||
|
- ✅ Interfaz web moderna y responsive
|
||||||
|
- ✅ API REST completa
|
||||||
|
- ✅ Gestión de archivos en tiempo real
|
||||||
|
|
||||||
|
**¡Simplemente ejecuta `python3 main.py` y visita http://localhost:5000!** 🚀
|
||||||
206
DASHBOARD_INSTRUCTIONS.md
Normal file
206
DASHBOARD_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# 🎛️ Dashboard Integrado - Instrucciones de Uso
|
||||||
|
|
||||||
|
## 🚀 Inicio Rápido
|
||||||
|
|
||||||
|
### Ejecutar servicio completo con dashboard
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**¡Listo!** Tendrás:
|
||||||
|
- ✅ Servicio principal procesando archivos automáticamente
|
||||||
|
- ✅ Dashboard web accesible en **http://localhost:5000**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 ¿Qué es el Dashboard?
|
||||||
|
|
||||||
|
El dashboard es una **interfaz web moderna** que te permite:
|
||||||
|
|
||||||
|
### 🔍 Monitoreo
|
||||||
|
- Ver todos los archivos de audio en tiempo real
|
||||||
|
- Filtrar por origen (Local/WebDAV)
|
||||||
|
- Buscar archivos por nombre
|
||||||
|
- Ver estadísticas: total, procesados, pendientes
|
||||||
|
|
||||||
|
### ⚡ Control
|
||||||
|
- **Reprocesar archivos** con un solo click
|
||||||
|
- **Resetear archivos** para forzar reprocesamiento
|
||||||
|
- **Descargar resultados** en múltiples formatos
|
||||||
|
|
||||||
|
### 📁 Gestión de Archivos
|
||||||
|
Ver formatos disponibles para cada archivo:
|
||||||
|
- 📝 TXT (transcripción)
|
||||||
|
- 📋 MD (Markdown)
|
||||||
|
- 📄 DOCX (documento editable)
|
||||||
|
- 📑 PDF (documento PDF)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Uso del Dashboard
|
||||||
|
|
||||||
|
### 1. Acceder
|
||||||
|
Abre tu navegador en: **http://localhost:5000**
|
||||||
|
|
||||||
|
### 2. Ver Archivos
|
||||||
|
La página principal muestra:
|
||||||
|
- **Header**: Título del dashboard
|
||||||
|
- **Estadísticas**: Cards con total, procesados, pendientes
|
||||||
|
- **Controles**: Botones para refrescar y reprocesar
|
||||||
|
- **Lista**: Grid de archivos con información
|
||||||
|
|
||||||
|
### 3. Filtrar
|
||||||
|
Usa los filtros en la parte superior:
|
||||||
|
- ☑️ **Local**: Mostrar archivos de la carpeta downloads
|
||||||
|
- ☑️ **WebDAV**: Mostrar archivos de Nextcloud
|
||||||
|
- 🔍 **Búsqueda**: Escribir para filtrar por nombre
|
||||||
|
|
||||||
|
### 4. Acciones por Archivo
|
||||||
|
|
||||||
|
#### Para archivos **Pendientes**:
|
||||||
|
1. Click en botón **🚀 Procesar**
|
||||||
|
2. Confirmar si hay archivos existentes
|
||||||
|
3. El archivo se encola para procesamiento
|
||||||
|
4. El estado se actualiza automáticamente
|
||||||
|
|
||||||
|
#### Para archivos **Procesados**:
|
||||||
|
1. Click en botón **🔄 Resetear**
|
||||||
|
2. Confirmar la acción
|
||||||
|
3. El archivo se marca como no procesado
|
||||||
|
4. Podrás reprocesarlo cuando quieras
|
||||||
|
|
||||||
|
### 5. Descargar Resultados
|
||||||
|
Si un archivo tiene formatos disponibles, verás enlaces:
|
||||||
|
- 📝 TXT, 📋 MD, 📄 DOCX, 📑 PDF
|
||||||
|
- Click directo para descargar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Comandos Alternativos
|
||||||
|
|
||||||
|
### Solo Dashboard (sin servicio principal)
|
||||||
|
```bash
|
||||||
|
python3 main.py dashboard-only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Otros comandos disponibles
|
||||||
|
```bash
|
||||||
|
# Servicio completo
|
||||||
|
python3 main.py
|
||||||
|
|
||||||
|
# Procesar audio individual
|
||||||
|
python3 main.py whisper audio.mp3 salida.txt
|
||||||
|
|
||||||
|
# Procesar PDF individual
|
||||||
|
python3 main.py pdf documento.pdf editable.docx
|
||||||
|
|
||||||
|
# Convertir texto a resumen
|
||||||
|
python3 main.py txt2docx texto.txt resumen.docx
|
||||||
|
|
||||||
|
# Generar quiz
|
||||||
|
python3 main.py quiz "texto del quiz" quiz.docx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 Interfaz Responsive
|
||||||
|
|
||||||
|
El dashboard funciona en:
|
||||||
|
- 💻 **Desktop**: Interfaz completa con todas las funciones
|
||||||
|
- 📱 **Móvil**: Adaptado para pantallas pequeñas
|
||||||
|
- 📲 **Tablet**: Experiencia optimizada para tablets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Características de la UI
|
||||||
|
|
||||||
|
- **Diseño moderno**: Gradientes y efectos glassmorphism
|
||||||
|
- **Animaciones suaves**: Transiciones y hover effects
|
||||||
|
- **Feedback visual**: Estados de carga y confirmación
|
||||||
|
- **Tema oscuro**: Colores elegantes y profesionales
|
||||||
|
- **Iconos**: Emojis para mejor identificación visual
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ API Endpoints
|
||||||
|
|
||||||
|
El dashboard expone una API REST:
|
||||||
|
|
||||||
|
- `GET /api/files` - Obtener lista de archivos
|
||||||
|
- `POST /api/reprocess` - Reprocesar archivo
|
||||||
|
- `POST /api/mark-unprocessed` - Marcar como no procesado
|
||||||
|
- `GET /api/refresh` - Refrescar lista
|
||||||
|
- `GET /health` - Health check
|
||||||
|
- `GET /downloads/<archivo>` - Descargar archivo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Notas Importantes
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
Los logs del dashboard aparecen en la consola donde ejecutaste `main.py`
|
||||||
|
|
||||||
|
### Errores Comunes
|
||||||
|
1. **Puerto 5000 en uso**
|
||||||
|
- Solución: Cambiar puerto en `dashboard.py` o terminar proceso anterior
|
||||||
|
|
||||||
|
2. **No aparecen archivos**
|
||||||
|
- Verificar conexión a Nextcloud
|
||||||
|
- Revisar variables de entorno NEXTCLOUD_*
|
||||||
|
- Comprobar que hay archivos en la carpeta Audios
|
||||||
|
|
||||||
|
3. **Error al procesar**
|
||||||
|
- Revisar logs en la consola
|
||||||
|
- Verificar dependencias (Flask, etc.)
|
||||||
|
- Comprobar espacio en disco
|
||||||
|
|
||||||
|
### Rendimiento
|
||||||
|
- El dashboard se ejecuta en **hilo separado**
|
||||||
|
- **No bloquea** el procesamiento principal
|
||||||
|
- Se inicia **automáticamente** con main.py
|
||||||
|
- **Cierre seguro** al terminar main.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Casos de Uso
|
||||||
|
|
||||||
|
### 📚 Estudio Personal
|
||||||
|
1. Sube audios de clases a Nextcloud
|
||||||
|
2. El sistema los procesa automáticamente
|
||||||
|
3. Usa el dashboard para descargar resúmenes
|
||||||
|
4. Estudia con los documentos DOCX generados
|
||||||
|
|
||||||
|
### 🏢 Oficina
|
||||||
|
1. Configura Nextcloud empresarial
|
||||||
|
2. Comparte carpeta Audios con el equipo
|
||||||
|
3. Todos pueden subir archivos para procesar
|
||||||
|
4. Gestiona todo desde el dashboard web
|
||||||
|
|
||||||
|
### 🔄 Reprocesamiento
|
||||||
|
1. Un archivo falló al procesarse
|
||||||
|
2. Usa el dashboard para resetearlo
|
||||||
|
3. Reprocesa con un click
|
||||||
|
4. Descarga el resultado actualizado
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 ¡Disfruta!
|
||||||
|
|
||||||
|
**Simplemente ejecuta:**
|
||||||
|
```bash
|
||||||
|
python3 main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Y visita:** http://localhost:5000
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ¿Necesitas Ayuda?
|
||||||
|
|
||||||
|
1. Revisa los logs en la consola
|
||||||
|
2. Verifica que Flask esté instalado: `pip3 install flask flask-cors`
|
||||||
|
3. Comprueba que el puerto 5000 esté libre
|
||||||
|
4. Asegúrate de tener conexión a Internet para APIs de IA
|
||||||
|
|
||||||
|
**¡El dashboard está listo para usar!** 🚀
|
||||||
43
Dockerfile
Executable file
43
Dockerfile
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
# Usar una imagen base de NVIDIA con CUDA 12.1.1 y Python 3.10
|
||||||
|
FROM nvidia/cuda:12.1.1-runtime-ubuntu22.04
|
||||||
|
|
||||||
|
# Evitar que los cuadros de diálogo de apt se bloqueen
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Instalar Python, pip, ffmpeg, Node.js y otras dependencias del sistema
|
||||||
|
RUN apt-get update && apt-get install -y python3.10 python3-pip ffmpeg poppler-utils tesseract-ocr tesseract-ocr-spa curl && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Instalar Node.js 20 usando NodeSource repository
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||||
|
apt-get install -y nodejs && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Crear un enlace simbólico para que python3 -> python
|
||||||
|
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||||
|
|
||||||
|
# Establecer el directorio de trabajo
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copiar el archivo de requerimientos e instalar PyTorch con soporte para CUDA
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Instalar PyTorch y las dependencias de audio/visión compatibles con CUDA 12.1
|
||||||
|
RUN python3 -m pip install --no-cache-dir --upgrade pip
|
||||||
|
RUN python3 -m pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
||||||
|
|
||||||
|
# Instalar el resto de las dependencias
|
||||||
|
RUN python3 -m pip install --no-cache-dir -r requirements.txt
|
||||||
|
RUN apt-get update && apt-get install -y tesseract-ocr tesseract-ocr-spa libgl1 libglib2.0-0
|
||||||
|
RUN python3 -m pip install --no-cache-dir easyocr pytesseract opencv-python-headless pdf2image transformers
|
||||||
|
|
||||||
|
# Instalar Claude CLI
|
||||||
|
RUN npm install -g @anthropic-ai/claude-code
|
||||||
|
|
||||||
|
# Instalar Gemini CLI como root
|
||||||
|
RUN npm install -g @google/gemini-cli
|
||||||
|
|
||||||
|
# Copiar todo el código de la aplicación al contenedor
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Comando por defecto para iniciar el servicio principal unificado
|
||||||
|
CMD ["python3", "main.py"]
|
||||||
32
Dockerfile.dashboard
Normal file
32
Dockerfile.dashboard
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Establecer directorio de trabajo
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Instalar dependencias del sistema
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
g++ \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copiar requirements y dependencias de Python
|
||||||
|
COPY requirements.txt requirements-dashboard.txt ./
|
||||||
|
|
||||||
|
# Instalar dependencias de Python
|
||||||
|
RUN pip install --no-cache-dir -r requirements-dashboard.txt
|
||||||
|
|
||||||
|
# Copiar archivos de la aplicación
|
||||||
|
COPY main.py dashboard.py ./
|
||||||
|
COPY templates/ ./templates/
|
||||||
|
|
||||||
|
# Crear directorios necesarios
|
||||||
|
RUN mkdir -p downloads resumenes_docx
|
||||||
|
|
||||||
|
# Establecer permisos
|
||||||
|
RUN chmod +x dashboard.py
|
||||||
|
|
||||||
|
# Exponer puerto
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Comando para ejecutar el dashboard
|
||||||
|
CMD ["python", "dashboard.py"]
|
||||||
88
QUICKSTART.md
Normal file
88
QUICKSTART.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# 🚀 Quick Start - Dashboard Integrado
|
||||||
|
|
||||||
|
## ✅ Estado Actual
|
||||||
|
- ✅ Flask instalado y configurado
|
||||||
|
- ✅ Dashboard funcionando correctamente
|
||||||
|
- ✅ 210 archivos procesados detectados
|
||||||
|
- ✅ Todas las pruebas pasaron
|
||||||
|
|
||||||
|
## 🎯 Uso Inmediato
|
||||||
|
|
||||||
|
### Opción 1: Servicio Completo (Recomendado)
|
||||||
|
```bash
|
||||||
|
python3 main.py
|
||||||
|
```
|
||||||
|
**Resultado:**
|
||||||
|
- ✅ Servicio principal procesando archivos automáticamente
|
||||||
|
- ✅ Dashboard web disponible en http://localhost:5000
|
||||||
|
|
||||||
|
### Opción 2: Solo Dashboard
|
||||||
|
```bash
|
||||||
|
python3 main.py dashboard-only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opción 3: Probar Dashboard
|
||||||
|
```bash
|
||||||
|
python3 test_dashboard.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Dashboard Web
|
||||||
|
|
||||||
|
**URL:** http://localhost:5000
|
||||||
|
|
||||||
|
**Funciones:**
|
||||||
|
- 🔍 Ver archivos de audio en tiempo real
|
||||||
|
- 🔎 Filtrar por origen (Local/WebDAV)
|
||||||
|
- 🚀 Reprocesar archivos con 1 click
|
||||||
|
- 🔄 Resetear archivos procesados
|
||||||
|
- 📥 Descargar resultados (TXT, MD, DOCX, PDF)
|
||||||
|
- 📱 Interfaz responsive (móvil/tablet/desktop)
|
||||||
|
|
||||||
|
## 📁 Ubicaciones Importantes
|
||||||
|
|
||||||
|
- **Servicio Principal:** `/home/ren/cbc/main.py`
|
||||||
|
- **Dashboard:** `/home/ren/cbc/dashboard.py`
|
||||||
|
- **Interfaz Web:** `/home/ren/cbc/templates/index.html`
|
||||||
|
- **Pruebas:** `/home/ren/cbc/test_dashboard.py`
|
||||||
|
- **Instrucciones:** `/home/ren/cbc/DASHBOARD_INSTRUCTIONS.md`
|
||||||
|
|
||||||
|
## 🔧 Comandos Disponibles
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Servicio completo (main + dashboard)
|
||||||
|
python3 main.py
|
||||||
|
|
||||||
|
# Solo dashboard
|
||||||
|
python3 main.py dashboard-only
|
||||||
|
|
||||||
|
# Transcribir audio
|
||||||
|
python3 main.py whisper audio.mp3 salida.txt
|
||||||
|
|
||||||
|
# Procesar PDF
|
||||||
|
python3 main.py pdf documento.pdf editable.docx
|
||||||
|
|
||||||
|
# Texto a resumen
|
||||||
|
python3 main.py txt2docx texto.txt resumen.docx
|
||||||
|
|
||||||
|
# Generar quiz
|
||||||
|
python3 main.py quiz "texto" quiz.docx
|
||||||
|
|
||||||
|
# Marcar archivos como procesados
|
||||||
|
python3 main.py seed-processed
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📞 Soporte
|
||||||
|
|
||||||
|
Si algo no funciona:
|
||||||
|
1. Ejecutar: `python3 test_dashboard.py`
|
||||||
|
2. Verificar logs en consola
|
||||||
|
3. Revisar: `/home/ren/cbc/DASHBOARD_INSTRUCTIONS.md`
|
||||||
|
|
||||||
|
## 🎉 ¡Listo para Usar!
|
||||||
|
|
||||||
|
**Simplemente ejecuta:**
|
||||||
|
```bash
|
||||||
|
python3 main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Y visita:** http://localhost:5000
|
||||||
194
README.md
Normal file
194
README.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Nextcloud AI Service v8 Final
|
||||||
|
|
||||||
|
Sistema completo de procesamiento de audio, PDF y generación de resúmenes colaborativos mediante 3 modelos de IA.
|
||||||
|
|
||||||
|
## 🚀 Características Principales
|
||||||
|
|
||||||
|
### 🤖 Sistema Unificado de 3 IAs
|
||||||
|
- **GPT-OSS-120B**: Extracción de puntos clave mediante DeepInfra API
|
||||||
|
- **Claude CLI**: Desarrollo de resúmenes integrales mediante Z.ai API
|
||||||
|
- **Gemini CLI**: Formato y estilo final mediante CLI de Google
|
||||||
|
|
||||||
|
### 📁 Procesamiento de Archivos
|
||||||
|
- **Audio**: Transcripción con Whisper y generación de resúmenes
|
||||||
|
- **PDF**: OCR avanzado y conversión a documentos editables
|
||||||
|
- **Documentos**: Generación de resúmenes automáticos
|
||||||
|
- **Sincronización**: Integración completa con Nextcloud via WebDAV
|
||||||
|
|
||||||
|
### 🎯 Características Técnicas
|
||||||
|
- **Docker Multi-etapa**: Optimizado para producción
|
||||||
|
- **GPU CUDA 12.1**: Aceleración por hardware NVIDIA
|
||||||
|
- **CLI Tools**: Claude CLI y Gemini CLI para máxima compatibilidad
|
||||||
|
- **Unificación**: Sistema colaborativo que genera un único documento final
|
||||||
|
|
||||||
|
## 🛠️ Instalación
|
||||||
|
|
||||||
|
### Requisitos
|
||||||
|
- Docker y Docker Compose
|
||||||
|
- NVIDIA GPU con drivers CUDA 12.1+
|
||||||
|
- 16GB+ RAM recomendado
|
||||||
|
- 20GB+ espacio en disco
|
||||||
|
|
||||||
|
### Configuración
|
||||||
|
|
||||||
|
1. **Clonar el repositorio**
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.cbcren.online/ren/nextcloud-ai-v8-final.git
|
||||||
|
cd nextcloud-ai-v8-final
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configurar variables de entorno**
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Editar .env con tus credenciales
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Iniciar servicios**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Configuración de Variables de Entorno
|
||||||
|
|
||||||
|
### Nextcloud
|
||||||
|
```env
|
||||||
|
NEXTCLOUD_URL=https://tu-nextcloud.com
|
||||||
|
NEXTCLOUD_USER=tu_usuario
|
||||||
|
NEXTCLOUD_PASS=tu_contraseña
|
||||||
|
```
|
||||||
|
|
||||||
|
### APIs de IA
|
||||||
|
```env
|
||||||
|
GEMINI_API_KEY=tu_gemini_key
|
||||||
|
DEEPINFRA_API_KEY=tu_deepinfra_key
|
||||||
|
ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
|
||||||
|
ANTHROPIC_AUTH_TOKEN=tu_z_ai_token
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notificaciones (Opcional)
|
||||||
|
```env
|
||||||
|
TELEGRAM_TOKEN=tu_bot_token
|
||||||
|
TELEGRAM_CHAT_ID=tu_chat_id
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Arquitectura del Sistema
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ GPT-OSS-120B │───▶│ Claude CLI │───▶│ Gemini CLI │
|
||||||
|
│ (DeepInfra) │ │ (Z.ai) │ │ (Google) │
|
||||||
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Sistema Unificado │
|
||||||
|
│ (Documento Final Único) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Nextcloud Service │
|
||||||
|
│ (Sincronización WebDAV) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Uso
|
||||||
|
|
||||||
|
### Procesamiento Automático
|
||||||
|
1. Sube archivos de audio a la carpeta `Audios` en Nextcloud
|
||||||
|
2. El sistema detecta automáticamente y genera resúmenes
|
||||||
|
3. Los documentos finales se guardan en `Documentos`
|
||||||
|
4. Las versiones Markdown se sincronizan con `Notes`
|
||||||
|
|
||||||
|
### Monitoreo
|
||||||
|
- **Logs**: `docker-compose logs -f app`
|
||||||
|
- **Estado**: `docker-compose ps`
|
||||||
|
- **Telegram**: Notificaciones automáticas (si está configurado)
|
||||||
|
|
||||||
|
## 🎨 Salida del Sistema
|
||||||
|
|
||||||
|
El sistema genera documentos unificados con:
|
||||||
|
|
||||||
|
1. **Puntos Clave**: Extraídos por GPT-OSS-120B
|
||||||
|
2. **Resumen Integral**: Desarrollado por Claude CLI (500+ palabras)
|
||||||
|
3. **Quiz de Evaluación**: 10 preguntas de opción múltiple
|
||||||
|
4. **Metadatos**: Información del proceso colaborativo
|
||||||
|
|
||||||
|
## 🐳 Docker
|
||||||
|
|
||||||
|
### Estructura de Contenedores
|
||||||
|
- **app**: Servicio principal con soporte GPU
|
||||||
|
- **ollama**: Servidor Ollama para modelos locales
|
||||||
|
|
||||||
|
### Personalización
|
||||||
|
```bash
|
||||||
|
# Reconstruir contenedores
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Reiniciar servicios
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
# Ver logs en tiempo real
|
||||||
|
docker-compose logs -f app
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 Troubleshooting
|
||||||
|
|
||||||
|
### Problemas Comunes
|
||||||
|
|
||||||
|
1. **Error de permisos Claude CLI**
|
||||||
|
- Solución: `CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=1` en docker-compose.yml
|
||||||
|
|
||||||
|
2. **Timeout en procesamiento**
|
||||||
|
- Aumentar `MODEL_TIMEOUT_SECONDS` en .env
|
||||||
|
|
||||||
|
3. **Problemas de GPU**
|
||||||
|
- Verificar drivers NVIDIA y CUDA 12.1+
|
||||||
|
- Comprobar `nvidia-smi`
|
||||||
|
|
||||||
|
4. **Error de APIs**
|
||||||
|
- Verificar keys y endpoints en .env
|
||||||
|
- Comprobar límites de las APIs
|
||||||
|
|
||||||
|
## 📈 Métricas y Optimización
|
||||||
|
|
||||||
|
### Rendimiento
|
||||||
|
- **Tiempo de procesamiento**: ~2-5 minutos por audio de 5min
|
||||||
|
- **Uso de VRAM**: ~8-12GB con modelos GPU
|
||||||
|
- **Calidad de resúmenes**: Formato académico con análisis profundo
|
||||||
|
|
||||||
|
### Optimización
|
||||||
|
- **VRAM Management**: Limpieza automática cada 5 minutos
|
||||||
|
- **Error Handling**: Reintentos automáticos con backoff exponencial
|
||||||
|
- **Timeout Configurable**: Adaptarse a diferentes cargas de trabajo
|
||||||
|
|
||||||
|
## 🔐 Seguridad
|
||||||
|
|
||||||
|
- **API Keys**: Almacenadas como variables de entorno
|
||||||
|
- **WebDAV**: Autenticación básica con HTTPS
|
||||||
|
- **CLI Tools**: Configuración segura sin permisos de root
|
||||||
|
- **Redes**: Aislamiento de contenedores Docker
|
||||||
|
|
||||||
|
## 🤝 Contribuciones
|
||||||
|
|
||||||
|
1. Fork del repositorio
|
||||||
|
2. Crear rama feature
|
||||||
|
3. Commit con cambios descriptivos
|
||||||
|
4. Pull Request para revisión
|
||||||
|
|
||||||
|
## 📄 Licencia
|
||||||
|
|
||||||
|
MIT License - Ver archivo LICENSE para detalles
|
||||||
|
|
||||||
|
## 📞 Soporte
|
||||||
|
|
||||||
|
Para problemas o preguntas:
|
||||||
|
- Crear issue en el repositorio
|
||||||
|
- Revisar logs del sistema
|
||||||
|
- Verificar documentación de variables de entorno
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Versión**: 8.0 Final
|
||||||
|
**Última Actualización**: Septiembre 2024
|
||||||
|
**Estado**: Producción estable
|
||||||
285
README_AI_ASSISTANT.md
Normal file
285
README_AI_ASSISTANT.md
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
# 🤖 CBC Nextcloud AI Service - Documentación para Asistentes IA
|
||||||
|
|
||||||
|
## 📋 Resumen Ejecutivo
|
||||||
|
|
||||||
|
Este es un sistema automatizado de procesamiento de contenido académico que utiliza inteligencia artificial para transcribir audios, procesar PDFs y generar resúmenes colaborativos mediante la integración de 3 modelos de IA diferentes.
|
||||||
|
|
||||||
|
**Estado Actual**: ✅ ACTIVO y FUNCIONANDO
|
||||||
|
- **Servicio**: `cbc-main.service` (systemd)
|
||||||
|
- **Proceso Principal**: PID 49696 (única instancia)
|
||||||
|
- **GPU**: NVIDIA RTX 3050 (1.5GB/8GB en uso)
|
||||||
|
- **Última Actualización**: 2025-09-26
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ Especificaciones del Sistema
|
||||||
|
|
||||||
|
### Hardware
|
||||||
|
- **CPU**: AMD Ryzen 5 5600X 6-Core Processor (12 hilos)
|
||||||
|
- **RAM**: 15GB DDR4 (9.7GB disponible)
|
||||||
|
- **GPU**: NVIDIA GeForce RTX 3050 (8GB VRAM)
|
||||||
|
- **Almacenamiento**: 439GB SSD (330GB libre)
|
||||||
|
- **Sistema Operativo**: Linux x86_64
|
||||||
|
|
||||||
|
### Software
|
||||||
|
- **Python**: 3.10.12
|
||||||
|
- **CUDA**: 12.2 (Driver 535.247.01)
|
||||||
|
- **Gestor de Servicios**: systemd
|
||||||
|
- **Directorio Principal**: `/home/ren/cbc/`
|
||||||
|
|
||||||
|
### Librerías Clave
|
||||||
|
- `torch` 2.8.0 (PyTorch con soporte CUDA)
|
||||||
|
- `transformers` 4.56.1 (Hugging Face)
|
||||||
|
- `whisper` 20240930 (OpenAI)
|
||||||
|
- `easyocr` 1.7.2 (OCR)
|
||||||
|
- `openai` 1.107.0 (API clientes)
|
||||||
|
- `python-docx` 1.2.0 (manipulación Word)
|
||||||
|
- `pdf2image` 1.17.0 (procesamiento PDF)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Funcionalidades Principales
|
||||||
|
|
||||||
|
### 1. Procesamiento de Audio
|
||||||
|
- **Formatos Soportados**: MP3, WAV, M4A, OGG
|
||||||
|
- **Transcripción**: Whisper (medium) con GPU aceleración
|
||||||
|
- **Salida**: Archivos de texto con timestamps
|
||||||
|
- **Ubicación**: Carpeta `Audios` en Nextcloud
|
||||||
|
|
||||||
|
### 2. Procesamiento de PDF
|
||||||
|
- **OCR Múltiple**: EasyOCR + Tesseract + TrOCR
|
||||||
|
- **Procesamiento por Lotes**: 3 páginas por chunk
|
||||||
|
- **Corrección IA**: Gemini para limpieza de texto
|
||||||
|
- **Salida**: Documentos DOCX editables
|
||||||
|
- **Ubicación**: Carpeta `Pdf` en Nextcloud
|
||||||
|
|
||||||
|
### 3. Sistema de Resúmenes Colaborativos
|
||||||
|
**3 Modelos de IA trabajando en secuencia**:
|
||||||
|
|
||||||
|
1. **GPT-OSS-120B** (DeepInfra API):
|
||||||
|
- Genera bullet points clave
|
||||||
|
- Análisis inicial del contenido
|
||||||
|
|
||||||
|
2. **Claude/Zhai** (CLI - API Z.ai):
|
||||||
|
- Desarrolla resumen integral
|
||||||
|
- 400-500 palabras estructuradas
|
||||||
|
|
||||||
|
3. **Gemini** (CLI - Google):
|
||||||
|
- Aplica formato final
|
||||||
|
- Optimiza presentación
|
||||||
|
|
||||||
|
### 4. Clasificación Inteligente de Contenido
|
||||||
|
El sistema clasifica automáticamente en 4 categorías temáticas:
|
||||||
|
- `historia` - Eventos históricos, cronologías
|
||||||
|
- `analisis_contable` - Contabilidad, finanzas, balances
|
||||||
|
- `instituciones_gobierno` - Política, gobierno, leyes
|
||||||
|
- `otras_clases` - Ciencias, tecnología, literatura, etc.
|
||||||
|
|
||||||
|
### 5. Generación de Quizzes
|
||||||
|
- **10 preguntas** de opción múltiple por documento
|
||||||
|
- **4 opciones** (A, B, C, D) por pregunta
|
||||||
|
- **Respuestas incluidas** en documentos separados
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Configuración Actual
|
||||||
|
|
||||||
|
### Variables de Entorno
|
||||||
|
```bash
|
||||||
|
CUDA_LAUNCH_BLOCKING=1 # Depuración CUDA síncrona
|
||||||
|
PYTHONPATH=/home/ren/cbc # Ruta del proyecto
|
||||||
|
HOME=/home/ren # Directorio home
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rutas del Sistema
|
||||||
|
- **Descargas**: `/app/downloads/`
|
||||||
|
- **Resúmenes**: `./downloads/`
|
||||||
|
- **Documentos DOCX**: `./resumenes_docx/`
|
||||||
|
- **Archivos Procesados**: `/app/processed_files.txt`
|
||||||
|
|
||||||
|
### Límites de Recursos
|
||||||
|
- **Memoria RAM**: 8GB máximo
|
||||||
|
- **CPU**: 90% de cuota
|
||||||
|
- **Archivos Abiertos**: 65536
|
||||||
|
- **Reinicio Automático**: Siempre (10 segundos de espera)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Estructura de Archivos Principal
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/ren/cbc/
|
||||||
|
├── main.py # Servicio principal (72,405 líneas)
|
||||||
|
├── .env # Variables de entorno
|
||||||
|
├── requirements.txt # Dependencias Python
|
||||||
|
├── README_AI_ASSISTANT.md # Este documento
|
||||||
|
├── MEGA_HISTORIA_parte*.txt # Historiales académicos
|
||||||
|
├── resumen_*.py # Scripts de resumen
|
||||||
|
├── procesador_academico.py # Procesamiento especializado
|
||||||
|
├── analizador_*.py # Análisis de contenido
|
||||||
|
├── config_telegram.txt # Configuración Telegram
|
||||||
|
└── txt/ # Directorio de textos
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Gestión del Servicio
|
||||||
|
|
||||||
|
### Comandos Essenciales
|
||||||
|
```bash
|
||||||
|
# Ver estado del servicio
|
||||||
|
sudo systemctl status cbc-main
|
||||||
|
|
||||||
|
# Ver logs en tiempo real
|
||||||
|
sudo journalctl -u cbc-main -f
|
||||||
|
|
||||||
|
# Detener servicio
|
||||||
|
sudo systemctl stop cbc-main
|
||||||
|
|
||||||
|
# Iniciar servicio
|
||||||
|
sudo systemctl start cbc-main
|
||||||
|
|
||||||
|
# Reiniciar servicio
|
||||||
|
sudo systemctl restart cbc-main
|
||||||
|
|
||||||
|
# Ver uso de GPU
|
||||||
|
nvidia-smi
|
||||||
|
|
||||||
|
# Ver uso de memoria del sistema
|
||||||
|
free -h
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoreo de Salud
|
||||||
|
```bash
|
||||||
|
# Verificar proceso único
|
||||||
|
ps aux | grep "python3.*main.py" | grep -v grep
|
||||||
|
|
||||||
|
# Verificar memoria GPU
|
||||||
|
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
|
||||||
|
|
||||||
|
# Verificar espacio en disco
|
||||||
|
df -h /
|
||||||
|
|
||||||
|
# Verificar actividad de red
|
||||||
|
sudo netstat -tlnp | grep :80
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Problemas Comunes y Soluciones
|
||||||
|
|
||||||
|
### Error CUDA: "CUDA-capable device(s) is/are busy or unavailable"
|
||||||
|
**Causa**: Múltiples procesos compitiendo por recursos GPU
|
||||||
|
**Solución**:
|
||||||
|
```bash
|
||||||
|
# Verificar procesos múltiples
|
||||||
|
ps aux | grep "python3.*main.py"
|
||||||
|
|
||||||
|
# Si hay múltiples, reiniciar servicio
|
||||||
|
sudo systemctl restart cbc-main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error de Permiso: "Permission denied"
|
||||||
|
**Causa**: El servicio no tiene permisos para escribir en `/app/`
|
||||||
|
**Solución**:
|
||||||
|
```bash
|
||||||
|
# Crear directorios con permisos correctos
|
||||||
|
sudo mkdir -p /app/downloads /app/resumenes
|
||||||
|
sudo chown -R ren:ren /app/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alto Uso de Memoria GPU
|
||||||
|
**Configuración actual optimizada**:
|
||||||
|
- `MAX_PAGES_PER_CHUNK = 3` (reducido de 5)
|
||||||
|
- `PDF_DPI = 200` (reducido de 300)
|
||||||
|
- `batch_size = 1` (procesamiento individual)
|
||||||
|
- `_MODEL_TIMEOUT_SECONDS = 300` (liberación rápida)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Flujo de Trabajo Automático
|
||||||
|
|
||||||
|
### 1. Detección de Archivos
|
||||||
|
- Monitorea carpetas Nextcloud cada 5 segundos
|
||||||
|
- Verifica archivos no procesados en `processed_files.txt`
|
||||||
|
|
||||||
|
### 2. Procesamiento según Tipo
|
||||||
|
**Audio** → Transcripción → Resumen Colaborativo → Clasificación → Subida
|
||||||
|
**PDF** → OCR → Corrección IA → Documento Editable → Resumen → Subida
|
||||||
|
|
||||||
|
### 3. Clasificación y Organización
|
||||||
|
- Análisis de contenido con Gemini
|
||||||
|
- Clasificación en categorías temáticas
|
||||||
|
- Renombrado automático con temas extraídos
|
||||||
|
- Organización en carpetas específicas
|
||||||
|
|
||||||
|
### 4. Notificaciones
|
||||||
|
- Envío de alertas por Telegram
|
||||||
|
- Reportes de progreso y errores
|
||||||
|
- Confirmación de procesamiento completado
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Métricas de Rendimiento
|
||||||
|
|
||||||
|
### Rendimiento Actual
|
||||||
|
- **Procesamiento PDF**: ~3-5 minutos por documento (12 páginas)
|
||||||
|
- **Transcripción Audio**: ~2-3 minutos por 10 minutos de audio
|
||||||
|
- **Generación Resúmenes**: ~1-2 minutos por documento
|
||||||
|
- **Uso GPU**: 1.5GB (estable)
|
||||||
|
- **Uso CPU**: 44% promedio
|
||||||
|
- **Memoria RAM**: 1.1GB utilizada
|
||||||
|
|
||||||
|
### Optimizaciones Aplicadas
|
||||||
|
- ✅ Procesamiento único (sin duplicados)
|
||||||
|
- ✅ Gestión agresiva de VRAM
|
||||||
|
- ✅ Límites de recursos controlados
|
||||||
|
- ✅ Reintentos automáticos para errores CUDA
|
||||||
|
- ✅ Sistema de clasificación inteligente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔮 Estado y Próximos Pasos
|
||||||
|
|
||||||
|
### Estado Actual: SISTEMA ESTABLE
|
||||||
|
- ✅ Servicio systemd funcionando correctamente
|
||||||
|
- ✅ GPU operativa sin errores
|
||||||
|
- ✅ Procesamiento de PDF activo
|
||||||
|
- ✅ Clasificación automática funcionando
|
||||||
|
- ✅ Notificaciones Telegram operativas
|
||||||
|
|
||||||
|
### Posibles Mejoras Futuras
|
||||||
|
- Implementar cola de procesamiento para manejar carga pesada
|
||||||
|
- Añadir interfaz web para monitoreo
|
||||||
|
- Optimizar tiempos de procesamiento con modelos más eficientes
|
||||||
|
- Implementar sistema de backup automático
|
||||||
|
- Añadir métricas detalladas de rendimiento
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Información de Contacto y Soporte
|
||||||
|
|
||||||
|
### Para Emergencias del Sistema
|
||||||
|
```bash
|
||||||
|
# Reinicio completo del sistema
|
||||||
|
sudo systemctl restart cbc-main
|
||||||
|
|
||||||
|
# Liberación forzada de VRAM
|
||||||
|
sudo pkill -f python3
|
||||||
|
sudo systemctl start cbc-main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Archivos de Log Importantes
|
||||||
|
- **Journal Systemd**: `sudo journalctl -u cbc-main`
|
||||||
|
- **Log GPU**: `nvidia-smi --query-gpu=timestamp,memory.used --format=csv -l 1`
|
||||||
|
- **Log Memoria**: `free -h -s 5`
|
||||||
|
|
||||||
|
### Puntos Críticos del Sistema
|
||||||
|
1. **Disponibilidad GPU**: NVIDIA RTX 3050 (8GB)
|
||||||
|
2. **Espacio en Disco**: 330GB disponibles
|
||||||
|
3. **Conexión Nextcloud**: WebDAV funcional
|
||||||
|
4. **API Keys**: Configuradas y operativas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Este documento fue generado automáticamente el 2025-09-26 para proporcionar a asistentes IA una comprensión completa del estado y funcionalidad del sistema CBC Nextcloud AI Service.*
|
||||||
205
claude_code_zai_env.sh
Normal file
205
claude_code_zai_env.sh
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Define Constants
|
||||||
|
# ========================
|
||||||
|
SCRIPT_NAME=$(basename "$0")
|
||||||
|
NODE_MIN_VERSION=18
|
||||||
|
NODE_INSTALL_VERSION=22
|
||||||
|
NVM_VERSION="v0.40.3"
|
||||||
|
CLAUDE_PACKAGE="@anthropic-ai/claude-code"
|
||||||
|
CONFIG_DIR="$HOME/.claude"
|
||||||
|
CONFIG_FILE="$CONFIG_DIR/settings.json"
|
||||||
|
API_BASE_URL="https://api.z.ai/api/anthropic"
|
||||||
|
API_KEY_URL="https://z.ai/manage-apikey/apikey-list"
|
||||||
|
API_TIMEOUT_MS=3000000
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Functions
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo "🔹 $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo "✅ $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo "❌ $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_dir_exists() {
|
||||||
|
local dir="$1"
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
mkdir -p "$dir" || {
|
||||||
|
log_error "Failed to create directory: $dir"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Node.js Installation
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
install_nodejs() {
|
||||||
|
local platform=$(uname -s)
|
||||||
|
|
||||||
|
case "$platform" in
|
||||||
|
Linux|Darwin)
|
||||||
|
log_info "Installing Node.js on $platform..."
|
||||||
|
|
||||||
|
# Install nvm
|
||||||
|
log_info "Installing nvm ($NVM_VERSION)..."
|
||||||
|
curl -s https://raw.githubusercontent.com/nvm-sh/nvm/"$NVM_VERSION"/install.sh | bash
|
||||||
|
|
||||||
|
# Load nvm
|
||||||
|
log_info "Loading nvm environment..."
|
||||||
|
\. "$HOME/.nvm/nvm.sh"
|
||||||
|
|
||||||
|
# Install Node.js
|
||||||
|
log_info "Installing Node.js $NODE_INSTALL_VERSION..."
|
||||||
|
nvm install "$NODE_INSTALL_VERSION"
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
node -v &>/dev/null || {
|
||||||
|
log_error "Node.js installation failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log_success "Node.js installed: $(node -v)"
|
||||||
|
log_success "npm version: $(npm -v)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unsupported platform: $platform"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Node.js Check
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
check_nodejs() {
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
current_version=$(node -v | sed 's/v//')
|
||||||
|
major_version=$(echo "$current_version" | cut -d. -f1)
|
||||||
|
|
||||||
|
if [ "$major_version" -ge "$NODE_MIN_VERSION" ]; then
|
||||||
|
log_success "Node.js is already installed: v$current_version"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_info "Node.js v$current_version is installed but version < $NODE_MIN_VERSION. Upgrading..."
|
||||||
|
install_nodejs
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "Node.js not found. Installing..."
|
||||||
|
install_nodejs
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Claude Code Installation
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
install_claude_code() {
|
||||||
|
if command -v claude &>/dev/null; then
|
||||||
|
log_success "Claude Code is already installed: $(claude --version)"
|
||||||
|
else
|
||||||
|
log_info "Installing Claude Code..."
|
||||||
|
npm install -g "$CLAUDE_PACKAGE" || {
|
||||||
|
log_error "Failed to install claude-code"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log_success "Claude Code installed successfully"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_claude_json(){
|
||||||
|
node --eval '
|
||||||
|
const os = require("os");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const homeDir = os.homedir();
|
||||||
|
const filePath = path.join(homeDir, ".claude.json");
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify({ ...content, hasCompletedOnboarding: true }, null, 2), "utf-8");
|
||||||
|
} else {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify({ hasCompletedOnboarding: true }, null, 2), "utf-8");
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# API Key Configuration
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
configure_claude() {
|
||||||
|
log_info "Configuring Claude Code..."
|
||||||
|
echo " You can get your API key from: $API_KEY_URL"
|
||||||
|
read -s -p "🔑 Please enter your Z.AI API key: " api_key
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ -z "$api_key" ]; then
|
||||||
|
log_error "API key cannot be empty. Please run the script again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_dir_exists "$CONFIG_DIR"
|
||||||
|
|
||||||
|
# Write settings.json
|
||||||
|
node --eval '
|
||||||
|
const os = require("os");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const homeDir = os.homedir();
|
||||||
|
const filePath = path.join(homeDir, ".claude", "settings.json");
|
||||||
|
const apiKey = "'"$api_key"'";
|
||||||
|
|
||||||
|
const content = fs.existsSync(filePath)
|
||||||
|
? JSON.parse(fs.readFileSync(filePath, "utf-8"))
|
||||||
|
: {};
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify({
|
||||||
|
...content,
|
||||||
|
env: {
|
||||||
|
ANTHROPIC_AUTH_TOKEN: apiKey,
|
||||||
|
ANTHROPIC_BASE_URL: "'"$API_BASE_URL"'",
|
||||||
|
API_TIMEOUT_MS: "'"$API_TIMEOUT_MS"'",
|
||||||
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1
|
||||||
|
}
|
||||||
|
}, null, 2), "utf-8");
|
||||||
|
' || {
|
||||||
|
log_error "Failed to write settings.json"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success "Claude Code configured successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Main
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo "🚀 Starting $SCRIPT_NAME"
|
||||||
|
|
||||||
|
check_nodejs
|
||||||
|
install_claude_code
|
||||||
|
configure_claude_json
|
||||||
|
configure_claude
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_success "🎉 Installation completed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "🚀 You can now start using Claude Code with:"
|
||||||
|
echo " claude"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
21
config.yaml
Normal file
21
config.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
cache_ttl: 30
|
||||||
|
dashboard:
|
||||||
|
enabled: true
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 5000
|
||||||
|
http_timeout: 30
|
||||||
|
max_workers: 2
|
||||||
|
models:
|
||||||
|
claude: claude-3-haiku
|
||||||
|
gemini: gemini-1.5-flash
|
||||||
|
whisper: base
|
||||||
|
notifications:
|
||||||
|
telegram_enabled: true
|
||||||
|
verbose_logging: true
|
||||||
|
poll_interval: 5
|
||||||
|
processing:
|
||||||
|
batch_size: 3
|
||||||
|
parallel_uploads: 3
|
||||||
|
retry_attempts: 3
|
||||||
|
vram_threshold: 0.8
|
||||||
|
webdav_retries: 3
|
||||||
87
corregir_gallego.py
Normal file
87
corregir_gallego.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import re
|
||||||
|
|
||||||
|
def corregir_texto_gallego(input_file, output_file):
|
||||||
|
"""Aplica correcciones de gallego a español al archivo de transcripción"""
|
||||||
|
|
||||||
|
# Correcciones más comunes de gallego a español
|
||||||
|
correcciones = {
|
||||||
|
"xeo": "yo",
|
||||||
|
"non": "no",
|
||||||
|
"hai": "hay",
|
||||||
|
"entóns": "entonces",
|
||||||
|
"máis": "más",
|
||||||
|
"tamén": "también",
|
||||||
|
"sempre": "siempre",
|
||||||
|
"verdade": "verdad",
|
||||||
|
"cousa": "cosa",
|
||||||
|
"xente": "gente",
|
||||||
|
"tempo": "tiempo",
|
||||||
|
"lingua": "lengua",
|
||||||
|
"pode": "puede",
|
||||||
|
"xamón": "shogun",
|
||||||
|
"xomón": "shogun",
|
||||||
|
"unha": "una",
|
||||||
|
"dunha": "de una",
|
||||||
|
"nunha": "en una",
|
||||||
|
"xeral": "general",
|
||||||
|
"xeraria": "jerarquía",
|
||||||
|
"ximéas": "temas",
|
||||||
|
"ximeas": "temas",
|
||||||
|
"ronquera": "reunión",
|
||||||
|
"xocalizar": "juntar",
|
||||||
|
"oanxacular": "juntar",
|
||||||
|
"xocal": "junto",
|
||||||
|
"lúmulo": "grupo",
|
||||||
|
"lúmido": "grupo",
|
||||||
|
"lúmada": "grupos",
|
||||||
|
"nulunxación": "reunificación",
|
||||||
|
"xotalipa": "capitalista",
|
||||||
|
"crente": "gente",
|
||||||
|
"enxucar": "juntar",
|
||||||
|
"agora": "ahora",
|
||||||
|
"cando": "cuando",
|
||||||
|
"temos": "tenemos",
|
||||||
|
"habíamos": "habíamos",
|
||||||
|
"era": "era",
|
||||||
|
"había": "había",
|
||||||
|
"existía": "existía",
|
||||||
|
"también": "también",
|
||||||
|
"vamos": "vamos",
|
||||||
|
"teníamos": "teníamos",
|
||||||
|
"vimos": "vimos",
|
||||||
|
"estaba": "estaba",
|
||||||
|
"estaban": "estaban",
|
||||||
|
"podía": "podía",
|
||||||
|
"podemos": "podemos",
|
||||||
|
"somos": "somos"
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(input_file, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
corrected_lines = []
|
||||||
|
for line in lines:
|
||||||
|
corrected_line = line
|
||||||
|
# Aplicar correcciones
|
||||||
|
for gallego, espanol in correcciones.items():
|
||||||
|
corrected_line = corrected_line.replace(gallego, espanol)
|
||||||
|
|
||||||
|
# Normalizar espacios múltiples
|
||||||
|
corrected_line = re.sub(r'\s+', ' ', corrected_line)
|
||||||
|
|
||||||
|
# Eliminar líneas que son solo repeticiones de "e" o "¿no?"
|
||||||
|
if corrected_line.strip() and not re.match(r'^\s*\[?\d+:\d+:\d+\]\s+(e\s+)+\s*$', corrected_line) and not re.match(r'^\s*\[?\d+:\d+:\d+\]\s+¿no\?\s*$', corrected_line):
|
||||||
|
corrected_lines.append(corrected_line)
|
||||||
|
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.writelines(corrected_lines)
|
||||||
|
|
||||||
|
print(f"Archivo corregido guardado en: {output_file}")
|
||||||
|
print(f"Líneas procesadas: {len(lines)}")
|
||||||
|
print(f"Líneas finales: {len(corrected_lines)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
input_file = "downloads/1_5134218813469886295.txt"
|
||||||
|
output_file = "downloads/1_5134218813469886295_corregido.txt"
|
||||||
|
corregir_texto_gallego(input_file, output_file)
|
||||||
417
dashboard.py
Normal file
417
dashboard.py
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Dashboard Flask para gestión de archivos de audio
|
||||||
|
Interfaz web simple para reprocesar archivos MP3 con 1 click
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
from flask import Flask, render_template, request, jsonify, send_from_directory
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
# Importar configuraciones del main.py
|
||||||
|
import sys
|
||||||
|
sys.path.append('/home/ren/cbc')
|
||||||
|
from main import (
|
||||||
|
AUDIO_EXTENSIONS, LOCAL_DOWNLOADS_PATH, PROCESSED_FILES_PATH,
|
||||||
|
load_processed_files, save_processed_file, process_audio_file,
|
||||||
|
REMOTE_AUDIOS_FOLDER, webdav_list, normalize_remote_path
|
||||||
|
)
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
|
# Configuración
|
||||||
|
app.config['SECRET_KEY'] = os.getenv('DASHBOARD_SECRET_KEY', 'dashboard-secret-key-change-in-production')
|
||||||
|
app.config['DOWNLOADS_FOLDER'] = LOCAL_DOWNLOADS_PATH
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class FileManager:
|
||||||
|
"""Gestor de archivos para el dashboard"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.processed_files = set()
|
||||||
|
self.load_processed_files()
|
||||||
|
|
||||||
|
def load_processed_files(self):
|
||||||
|
"""Cargar archivos procesados desde el registro"""
|
||||||
|
try:
|
||||||
|
self.processed_files = load_processed_files()
|
||||||
|
logger.info(f"Cargados {len(self.processed_files)} archivos procesados")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error cargando archivos procesados: {e}")
|
||||||
|
self.processed_files = set()
|
||||||
|
|
||||||
|
def get_audio_files(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Obtener lista de archivos de audio disponibles"""
|
||||||
|
files = []
|
||||||
|
|
||||||
|
# Obtener archivos de WebDAV
|
||||||
|
try:
|
||||||
|
webdav_files = webdav_list(REMOTE_AUDIOS_FOLDER)
|
||||||
|
for file_path in webdav_files:
|
||||||
|
normalized_path = normalize_remote_path(file_path)
|
||||||
|
base_name = os.path.basename(normalized_path)
|
||||||
|
|
||||||
|
if any(normalized_path.lower().endswith(ext) for ext in AUDIO_EXTENSIONS):
|
||||||
|
available_formats = self._get_available_formats(base_name)
|
||||||
|
# Considerar procesado si está en el registro O si tiene archivos de salida
|
||||||
|
is_processed = (normalized_path in self.processed_files or
|
||||||
|
base_name in self.processed_files or
|
||||||
|
any(available_formats.values()))
|
||||||
|
|
||||||
|
files.append({
|
||||||
|
'filename': base_name,
|
||||||
|
'path': normalized_path,
|
||||||
|
'source': 'webdav',
|
||||||
|
'processed': is_processed,
|
||||||
|
'size': 'Unknown',
|
||||||
|
'last_modified': 'Unknown',
|
||||||
|
'available_formats': available_formats
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error obteniendo archivos WebDAV: {e}")
|
||||||
|
|
||||||
|
# Obtener archivos locales
|
||||||
|
try:
|
||||||
|
if os.path.exists(LOCAL_DOWNLOADS_PATH):
|
||||||
|
local_files = []
|
||||||
|
for ext in AUDIO_EXTENSIONS:
|
||||||
|
local_files.extend(Path(LOCAL_DOWNLOADS_PATH).glob(f"*{ext}"))
|
||||||
|
|
||||||
|
for file_path in local_files:
|
||||||
|
stat = file_path.stat()
|
||||||
|
available_formats = self._get_available_formats(file_path.name)
|
||||||
|
# Considerar procesado si está en el registro O si tiene archivos de salida
|
||||||
|
is_processed = (file_path.name in self.processed_files or
|
||||||
|
any(available_formats.values()))
|
||||||
|
|
||||||
|
files.append({
|
||||||
|
'filename': file_path.name,
|
||||||
|
'path': str(file_path),
|
||||||
|
'source': 'local',
|
||||||
|
'processed': is_processed,
|
||||||
|
'size': self._format_size(stat.st_size),
|
||||||
|
'last_modified': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'available_formats': available_formats
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error obteniendo archivos locales: {e}")
|
||||||
|
|
||||||
|
# Eliminar duplicados y ordenar
|
||||||
|
unique_files = {}
|
||||||
|
for file in files:
|
||||||
|
key = file['filename']
|
||||||
|
if key not in unique_files or file['source'] == 'webdav':
|
||||||
|
unique_files[key] = file
|
||||||
|
|
||||||
|
return sorted(unique_files.values(), key=lambda x: x['filename'])
|
||||||
|
|
||||||
|
def _get_available_formats(self, audio_filename: str) -> Dict[str, bool]:
|
||||||
|
"""Verificar qué formatos de salida existen para un archivo de audio"""
|
||||||
|
# Obtener el nombre base sin extensión
|
||||||
|
base_name = Path(audio_filename).stem
|
||||||
|
|
||||||
|
# Extensiones a verificar
|
||||||
|
formats = {
|
||||||
|
'txt': False,
|
||||||
|
'md': False,
|
||||||
|
'pdf': False,
|
||||||
|
'docx': False
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verificar en directorio local y resumenes_docx
|
||||||
|
directories_to_check = [LOCAL_DOWNLOADS_PATH, './resumenes_docx']
|
||||||
|
|
||||||
|
for directory in directories_to_check:
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for ext in formats.keys():
|
||||||
|
# Buscar variaciones del nombre del archivo
|
||||||
|
name_variants = [
|
||||||
|
base_name, # Nombre exacto
|
||||||
|
f"{base_name}_unificado", # Con sufijo _unificado
|
||||||
|
f"{base_name.replace(' ', '_')}", # Espacios reemplazados por guiones bajos
|
||||||
|
f"{base_name.replace(' ', '_')}_unificado", # Ambas variaciones
|
||||||
|
]
|
||||||
|
|
||||||
|
# También verificar variantes con espacios originales pero _unificado
|
||||||
|
if ' ' in base_name:
|
||||||
|
name_variants.append(f"{base_name}_unificado")
|
||||||
|
|
||||||
|
# Para cada variante, verificar si existe el archivo
|
||||||
|
for name_variant in name_variants:
|
||||||
|
file_path = os.path.join(directory, f"{name_variant}.{ext}")
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
formats[ext] = True
|
||||||
|
break # Encontrado, pasar al siguiente formato
|
||||||
|
|
||||||
|
return formats
|
||||||
|
|
||||||
|
def _format_size(self, size_bytes: int) -> str:
|
||||||
|
"""Formatear tamaño de archivo"""
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024.0:
|
||||||
|
return f"{size_bytes:.1f} {unit}"
|
||||||
|
size_bytes /= 1024.0
|
||||||
|
return f"{size_bytes:.1f} TB"
|
||||||
|
|
||||||
|
def reprocess_file(self, file_path: str, source: str) -> Dict[str, Any]:
|
||||||
|
"""Reprocesar un archivo específico"""
|
||||||
|
try:
|
||||||
|
# Verificar formatos existentes antes de reprocesar
|
||||||
|
filename = os.path.basename(file_path)
|
||||||
|
existing_formats = self._get_available_formats(filename)
|
||||||
|
has_existing_files = any(existing_formats.values())
|
||||||
|
|
||||||
|
if source == 'webdav':
|
||||||
|
# Para archivos WebDAV, llamar directamente a process_audio_file
|
||||||
|
logger.info(f"Iniciando reprocesamiento de WebDAV: {file_path}")
|
||||||
|
process_audio_file(file_path)
|
||||||
|
else:
|
||||||
|
# Para archivos locales, procesar directamente
|
||||||
|
logger.info(f"Iniciando reprocesamiento local: {file_path}")
|
||||||
|
# Aquí podrías agregar lógica adicional para archivos locales
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': f"Archivo {os.path.basename(file_path)} enviado a reprocesamiento",
|
||||||
|
'had_existing_files': has_existing_files,
|
||||||
|
'existing_formats': existing_formats
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reprocesando {file_path}: {e}")
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f"Error: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def mark_as_unprocessed(self, file_path: str) -> bool:
|
||||||
|
"""Marcar archivo como no procesado para forzar reprocesamiento"""
|
||||||
|
try:
|
||||||
|
# Eliminar del registro de procesados
|
||||||
|
processed_files = load_processed_files()
|
||||||
|
normalized_path = normalize_remote_path(file_path)
|
||||||
|
base_name = os.path.basename(normalized_path)
|
||||||
|
|
||||||
|
# Crear nuevo registro sin este archivo
|
||||||
|
temp_path = PROCESSED_FILES_PATH + '.temp'
|
||||||
|
with open(temp_path, 'w', encoding='utf-8') as f:
|
||||||
|
for line in open(PROCESSED_FILES_PATH, 'r', encoding='utf-8'):
|
||||||
|
if (line.strip() != normalized_path and
|
||||||
|
os.path.basename(line.strip()) != base_name and
|
||||||
|
line.strip() != file_path):
|
||||||
|
f.write(line)
|
||||||
|
|
||||||
|
# Reemplazar archivo original
|
||||||
|
os.replace(temp_path, PROCESSED_FILES_PATH)
|
||||||
|
self.load_processed_files() # Recargar
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error marcando como no procesado {file_path}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Instancia global del gestor de archivos
|
||||||
|
file_manager = FileManager()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
"""Página principal del dashboard"""
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/api/files')
|
||||||
|
def get_files():
|
||||||
|
"""API endpoint para obtener lista de archivos"""
|
||||||
|
try:
|
||||||
|
files = file_manager.get_audio_files()
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'files': files,
|
||||||
|
'total': len(files),
|
||||||
|
'processed': sum(1 for f in files if f['processed']),
|
||||||
|
'pending': sum(1 for f in files if not f['processed'])
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error obteniendo archivos: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': f"Error: {str(e)}"
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@app.route('/api/reprocess', methods=['POST'])
|
||||||
|
def reprocess_file():
|
||||||
|
"""API endpoint para reprocesar un archivo"""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
file_path = data.get('path')
|
||||||
|
source = data.get('source', 'local')
|
||||||
|
|
||||||
|
if not file_path:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': "Path del archivo es requerido"
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
result = file_manager.reprocess_file(file_path, source)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error en endpoint reprocesar: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': f"Error: {str(e)}"
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@app.route('/api/mark-unprocessed', methods=['POST'])
|
||||||
|
def mark_unprocessed():
|
||||||
|
"""API endpoint para marcar archivo como no procesado"""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
file_path = data.get('path')
|
||||||
|
|
||||||
|
if not file_path:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': "Path del archivo es requerido"
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
success = file_manager.mark_as_unprocessed(file_path)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': "Archivo marcado como no procesado"
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': "No se pudo marcar como no procesado"
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error marcando como no procesado: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': f"Error: {str(e)}"
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@app.route('/api/refresh')
|
||||||
|
def refresh_files():
|
||||||
|
"""API endpoint para refrescar lista de archivos"""
|
||||||
|
try:
|
||||||
|
file_manager.load_processed_files()
|
||||||
|
files = file_manager.get_audio_files()
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': "Lista de archivos actualizada",
|
||||||
|
'files': files
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error refrescando archivos: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': f"Error: {str(e)}"
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@app.route('/downloads/find-file')
|
||||||
|
def find_and_download_file():
|
||||||
|
"""Buscar y servir archivos con diferentes variaciones de nombre"""
|
||||||
|
try:
|
||||||
|
from flask import request
|
||||||
|
filename = request.args.get('filename')
|
||||||
|
ext = request.args.get('ext')
|
||||||
|
|
||||||
|
if not filename or not ext:
|
||||||
|
return jsonify({'error': 'Missing parameters'}), 400
|
||||||
|
|
||||||
|
# Generar posibles variaciones del nombre del archivo
|
||||||
|
from pathlib import Path
|
||||||
|
base_name = Path(filename).stem
|
||||||
|
|
||||||
|
possible_names = [
|
||||||
|
f"{base_name}.{ext}",
|
||||||
|
f"{base_name}_unificado.{ext}",
|
||||||
|
f"{base_name.replace(' ', '_')}.{ext}",
|
||||||
|
f"{base_name.replace(' ', '_')}_unificado.{ext}"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Directorios donde buscar
|
||||||
|
directories = [LOCAL_DOWNLOADS_PATH, './resumenes_docx']
|
||||||
|
|
||||||
|
# Intentar encontrar el archivo en cada directorio con cada variación
|
||||||
|
for directory in directories:
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for name in possible_names:
|
||||||
|
file_path = os.path.join(directory, name)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
return send_from_directory(directory, name)
|
||||||
|
|
||||||
|
# Si no se encuentra el archivo
|
||||||
|
return jsonify({'error': 'File not found'}), 404
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error buscando archivo: {e}")
|
||||||
|
return jsonify({'error': 'File not found'}), 404
|
||||||
|
|
||||||
|
@app.route('/downloads/<path:filename>')
|
||||||
|
def download_file(filename):
|
||||||
|
"""Servir archivos de descarga desde downloads o resumenes_docx"""
|
||||||
|
try:
|
||||||
|
# Primero intentar en downloads
|
||||||
|
try:
|
||||||
|
return send_from_directory(LOCAL_DOWNLOADS_PATH, filename)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Si no se encuentra en downloads, intentar en resumenes_docx
|
||||||
|
try:
|
||||||
|
return send_from_directory('./resumenes_docx', filename)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Si no se encuentra en ninguna ubicación
|
||||||
|
return jsonify({'error': 'File not found'}), 404
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sirviendo archivo {filename}: {e}")
|
||||||
|
return jsonify({'error': 'File not found'}), 404
|
||||||
|
|
||||||
|
@app.route('/health')
|
||||||
|
def health_check():
|
||||||
|
"""Health check endpoint"""
|
||||||
|
return jsonify({
|
||||||
|
'status': 'healthy',
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'processed_files_count': len(file_manager.processed_files)
|
||||||
|
})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logger.info("🚀 Iniciando Dashboard de Gestión de Audio")
|
||||||
|
logger.info(f"📁 Carpeta de descargas: {LOCAL_DOWNLOADS_PATH}")
|
||||||
|
logger.info(f"📊 Servidor web en http://localhost:5000")
|
||||||
|
|
||||||
|
try:
|
||||||
|
app.run(
|
||||||
|
host='0.0.0.0',
|
||||||
|
port=5000,
|
||||||
|
debug=False,
|
||||||
|
threaded=True,
|
||||||
|
use_reloader=False # Evitar problemas con threading
|
||||||
|
)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("🛑 Dashboard detenido por el usuario")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Error en dashboard: {e}")
|
||||||
|
raise
|
||||||
86
docker-compose.yml
Executable file
86
docker-compose.yml
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: nextcloud_ai_app
|
||||||
|
volumes:
|
||||||
|
- ./downloads:/app/downloads
|
||||||
|
- ./resumenes_docx:/app/resumenes_docx
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
- OLLAMA_HOST=http://ollama:11434
|
||||||
|
- CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=1
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: 1
|
||||||
|
capabilities: [gpu]
|
||||||
|
depends_on:
|
||||||
|
- ollama
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
ollama:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: ollama_server
|
||||||
|
volumes:
|
||||||
|
- ./ollama_data:/root/.ollama
|
||||||
|
ports:
|
||||||
|
- "11434:11434"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: 1
|
||||||
|
capabilities: [gpu]
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.dashboard
|
||||||
|
container_name: audio_dashboard
|
||||||
|
volumes:
|
||||||
|
- ./downloads:/app/downloads
|
||||||
|
- ./templates:/app/templates
|
||||||
|
- ./processed_files.txt:/app/processed_files.txt
|
||||||
|
- ./.main_service.lock:/app/.main_service.lock
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DASHBOARD_SECRET_KEY=${DASHBOARD_SECRET_KEY:-dashboard-secret-key-change-me}
|
||||||
|
- NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
- ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL:-https://api.z.ai/api/anthropic}
|
||||||
|
- ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN}
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
filebrowser:
|
||||||
|
image: filebrowser/filebrowser
|
||||||
|
container_name: downloads_filebrowser
|
||||||
|
volumes:
|
||||||
|
- ./downloads:/srv
|
||||||
|
- ./filebrowser_config:/config
|
||||||
|
command: [
|
||||||
|
"--address", "0.0.0.0",
|
||||||
|
"--port", "8080",
|
||||||
|
"--root", "/srv",
|
||||||
|
"--database", "/config/filebrowser.db",
|
||||||
|
"--username", "ren",
|
||||||
|
"--password", "$$2b$$10$$KbFwEuIb3g26kYCxVzl0Ju81OxhK1KHQNUZCLAPDg298XQBOvhoHS"
|
||||||
|
]
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
restart: always
|
||||||
8
filebrowser_config/settings.json
Normal file
8
filebrowser_config/settings.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"port": 80,
|
||||||
|
"baseURL": "",
|
||||||
|
"address": "",
|
||||||
|
"log": "stdout",
|
||||||
|
"database": "/database/filebrowser.db",
|
||||||
|
"root": "/srv"
|
||||||
|
}
|
||||||
18
requirements-dashboard.txt
Normal file
18
requirements-dashboard.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Flask==2.3.3
|
||||||
|
Flask-CORS==4.0.0
|
||||||
|
requests==2.31.0
|
||||||
|
webdavclient3
|
||||||
|
opencv-python-headless
|
||||||
|
python-dotenv
|
||||||
|
easyocr
|
||||||
|
pytesseract
|
||||||
|
Pillow
|
||||||
|
python-docx
|
||||||
|
openai
|
||||||
|
ollama
|
||||||
|
pdf2image
|
||||||
|
transformers
|
||||||
|
pypdf
|
||||||
|
reportlab
|
||||||
|
torch
|
||||||
|
numpy
|
||||||
17
requirements.txt
Executable file
17
requirements.txt
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
webdavclient3
|
||||||
|
requests
|
||||||
|
torch
|
||||||
|
openai-whisper
|
||||||
|
pytesseract
|
||||||
|
Pillow
|
||||||
|
python-docx
|
||||||
|
openai
|
||||||
|
ollama
|
||||||
|
pdf2image
|
||||||
|
easyocr
|
||||||
|
opencv-python-headless
|
||||||
|
numpy
|
||||||
|
transformers
|
||||||
|
pypdf
|
||||||
|
python-dotenv
|
||||||
|
reportlab
|
||||||
158
start_with_info.py
Executable file
158
start_with_info.py
Executable file
@@ -0,0 +1,158 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script mejorado para iniciar el servicio completo con mejor información
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Añadir path
|
||||||
|
sys.path.append('/home/ren/cbc')
|
||||||
|
|
||||||
|
def print_banner():
|
||||||
|
"""Muestra banner de inicio"""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("🚀 NEXTCLOUD AI SERVICE - DASHBOARD INTEGRADO")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
|
||||||
|
def print_step(step, message):
|
||||||
|
"""Imprime un paso con formato"""
|
||||||
|
print(f" [{step}] {message}")
|
||||||
|
|
||||||
|
def check_dependencies():
|
||||||
|
"""Verifica dependencias"""
|
||||||
|
print_step("1", "Verificando dependencias...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import flask
|
||||||
|
print_step(" ✓", f"Flask {flask.__version__}")
|
||||||
|
except ImportError:
|
||||||
|
print_step(" ✗", "Flask no está instalado")
|
||||||
|
print("\n 💡 Instalar: pip3 install flask flask-cors")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import flask_cors
|
||||||
|
print_step(" ✓", "Flask-CORS")
|
||||||
|
except ImportError:
|
||||||
|
print_step(" ✗", "Flask-CORS no está instalado")
|
||||||
|
print("\n 💡 Instalar: pip3 install flask-cors")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_dashboard():
|
||||||
|
"""Verifica dashboard"""
|
||||||
|
print_step("2", "Verificando dashboard...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dashboard
|
||||||
|
print_step(" ✓", "Dashboard importado correctamente")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print_step(" ✗", f"Error importando dashboard: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_main():
|
||||||
|
"""Verifica main.py"""
|
||||||
|
print_step("3", "Verificando servicio principal...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import main
|
||||||
|
print_step(" ✓", "Servicio principal importado")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print_step(" ✗", f"Error importando servicio principal: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def start_dashboard():
|
||||||
|
"""Inicia dashboard en hilo separado"""
|
||||||
|
print_step("4", "Iniciando dashboard en hilo separado...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dashboard
|
||||||
|
import threading
|
||||||
|
|
||||||
|
def run_dashboard():
|
||||||
|
"""Función para ejecutar dashboard"""
|
||||||
|
print("\n 🌐 Iniciando servidor Flask...")
|
||||||
|
dashboard.app.run(
|
||||||
|
host='0.0.0.0',
|
||||||
|
port=5000,
|
||||||
|
debug=False,
|
||||||
|
threaded=True,
|
||||||
|
use_reloader=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Crear hilo
|
||||||
|
dashboard_thread = threading.Thread(target=run_dashboard, daemon=True)
|
||||||
|
dashboard_thread.start()
|
||||||
|
|
||||||
|
# Dar tiempo para que inicie
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
print_step(" ✓", "Dashboard iniciado")
|
||||||
|
print("\n 📱 Dashboard disponible en:")
|
||||||
|
print(" http://localhost:5000")
|
||||||
|
print()
|
||||||
|
|
||||||
|
return dashboard_thread
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print_step(" ✗", f"Error iniciando dashboard: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def start_main_service():
|
||||||
|
"""Inicia servicio principal"""
|
||||||
|
print_step("5", "Iniciando servicio principal...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import main
|
||||||
|
|
||||||
|
print("\n ⏳ Iniciando bucle principal...")
|
||||||
|
print(" 📊 El servicio procesará archivos automáticamente")
|
||||||
|
print(" ⏹️ Para detener: Ctrl+C")
|
||||||
|
print()
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
# Iniciar servicio
|
||||||
|
main.main()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n🛑 Servicio detenido por el usuario")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n\n❌ Error en servicio principal: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Función principal"""
|
||||||
|
print_banner()
|
||||||
|
|
||||||
|
# Verificar todo
|
||||||
|
if not check_dependencies():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not check_dashboard():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not check_main():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Iniciar dashboard
|
||||||
|
dashboard_thread = start_dashboard()
|
||||||
|
if not dashboard_thread:
|
||||||
|
print("\n⚠️ Continuando sin dashboard...")
|
||||||
|
|
||||||
|
# Iniciar servicio principal
|
||||||
|
start_main_service()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
1586
templates/index.html
Normal file
1586
templates/index.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user