diff --git a/.env.example b/.env.example index ee8ebcc..5bce1df 100644 --- a/.env.example +++ b/.env.example @@ -36,9 +36,6 @@ MYSQL_DATABASE_URL=mysql://db_user:db_password@localhost:3306/external_database_ EXTERNAL_TABLE_GRADES=notas EXTERNAL_ID_TIPO_NOTA=1 -# Bark TTS API (Text-to-Speech for questions) -BARK_API_URL=http://t-800:8443 - # Branding Defaults DEFAULT_ORG_NAME="Norteamericano" DEFAULT_PLATFORM_NAME="Norteamericano Learning" diff --git a/FINAL_UPDATES.md b/FINAL_UPDATES.md index 7347670..3ee2bb2 100644 --- a/FINAL_UPDATES.md +++ b/FINAL_UPDATES.md @@ -290,10 +290,6 @@ if user_usage > DAILY_LIMIT { - Gráficos de tendencia - Comparativa mes a mes -4. **Integrar con Bark** - - Track tokens de audio generation - - Costos específicos de TTS - --- **Implementación: 100% Completa** 🎉 diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md index 074abb9..e664765 100644 --- a/IMPLEMENTATION_SUMMARY.md +++ b/IMPLEMENTATION_SUMMARY.md @@ -1,4 +1,4 @@ -# 🚀 Resumen de Implementación - Question Bank con Audio +# 🚀 Resumen de Implementación - Question Bank ## ✅ Estado de la Implementación @@ -6,21 +6,18 @@ - ✅ Migración de base de datos con `skill_assessed` - ✅ Endpoints CRUD para Question Bank - ✅ Importación desde MySQL -- ✅ Generación de audio con Bark - ✅ RAG con verificación de 4 habilidades -- ✅ Compilación exitosa (8 warnings menores) +- ✅ Compilación exitosa -### Frontend (TypeScript/React) - COMPLETO +### Frontend (TypeScript/React) - COMPLETO - ✅ Página `/question-bank` con dashboard - ✅ Componente QuestionBankCard con badge de skills - ✅ QuestionBankEditor con generación IA de skills - ✅ MySQLImportModal -- ✅ AudioGeneratorModal - ✅ Navegación actualizada con link - ✅ TypeScript: 3 errores menores (admin, no críticos) -### Infraestructura - LISTO PARA DESPLEGAR -- ✅ Scripts de instalación de Bark +### Infraestructura - LISTO - ✅ install.sh actualizado con detección dev/prod - ✅ Documentación completa @@ -43,52 +40,19 @@ web/studio/src/app/question-bank/page.tsx (NUEVO) web/studio/src/components/QuestionBank/QuestionBankCard.tsx (NUEVO) web/studio/src/components/QuestionBank/QuestionBankEditor.tsx (NUEVO) web/studio/src/components/QuestionBank/MySQLImportModal.tsx (NUEVO) -web/studio/src/components/QuestionBank/AudioGeneratorModal.tsx (NUEVO) web/studio/src/components/Navbar.tsx (link agregado) web/studio/src/lib/api.ts (API client) ``` ### Scripts & Docs ``` -scripts/install_bark_tts.sh (NUEVO) -scripts/deploy_to_t800.sh (NUEVO) -docs/BARK_TTS_GUIDE.md (NUEVO) docs/QUESTION_BANK_UI.md (NUEVO) -docs/BARK_MANUAL_INSTALL.md (NUEVO) -install.sh (actualizado con Bark) -.env.example (BARK_API_URL agregado) +docs/EXCEL_IMPORT_TEMPLATE.md +install.sh (actualizado) ``` --- -## 🔧 Instalación de Bark en t-800 - -### Opción Automática (Recomendada) -```bash -cd /home/juan/dev/openccb -./scripts/deploy_to_t800.sh -# Ingresar contraseña: apoca11 -``` - -### Opción Manual -```bash -# Copiar script -scp scripts/install_bark_tts.sh juan@t-800:/tmp/ - -# Conectarse -ssh juan@t-800 - -# Ejecutar -sudo /tmp/install_bark_tts.sh - -# Verificar -curl http://localhost:8000/health -``` - -**Ver documentación completa en:** `docs/BARK_MANUAL_INSTALL.md` - ---- - ## 🎯 Características de 4 Habilidades ### Implementación @@ -123,14 +87,12 @@ curl http://localhost:8000/health **Desarrollo:** ```bash -BARK_API_URL=http://t-800:8000 OLLAMA_URL=http://t-800:11434 WHISPER_URL=http://t-800:9000 ``` **Producción:** ```bash -BARK_API_URL=http://t-800.norteamericano.cl:8000 OLLAMA_URL=http://t-800.norteamericano.cl:11434 WHISPER_URL=http://t-800.norteamericano.cl:9000 ``` @@ -147,8 +109,8 @@ GET /question-bank/{id} # Obtener pregunta PUT /question-bank/{id} # Actualizar pregunta DELETE /question-bank/{id} # Eliminar pregunta POST /question-bank/import-mysql # Importar desde MySQL -POST /question-bank/{id}/generate-audio # Generar audio Bark GET /question-bank/mysql-courses # Listar cursos MySQL +POST /question-bank/import-mysql-all # Importar todo desde MySQL ``` ### Test Templates (actualizado) @@ -175,12 +137,6 @@ npm run type-check # ⚠️ 3 errores menores en admin (no afectan Question Bank) ``` -### 3. Bark (después de instalar) -```bash -curl http://t-800:8000/health -# Expected: {"status":"healthy","service":"bark-tts"} -``` - --- ## 🎨 UI Features @@ -201,25 +157,20 @@ curl http://t-800:8000/health - 10 tipos de preguntas - Generación IA con skills - Tags automáticos -- Audio generation checkbox --- ## 📝 Próximos Pasos (Opcionales) -1. **Desplegar Bark en t-800** - - Ejecutar `./scripts/deploy_to_t800.sh` - - O seguir `docs/BARK_MANUAL_INSTALL.md` - -2. **Filtrar errores de admin** (no críticos) +1. **Filtrar errores de admin** (no críticos) - `getOrganizations` no existe - `BrandingContext` type error -3. **Integración con Test Templates** +2. **Integración con Test Templates** - Selector de preguntas desde banco - Bulk selection -4. **Analytics de Skills** +3. **Analytics de Skills** - Dashboard de distribución de skills - Reportes por habilidad @@ -231,10 +182,9 @@ curl http://t-800:8000/health |------------|--------|-------| | Backend Question Bank | ✅ 100% | Compila exitosamente | | Frontend Question Bank | ✅ 95% | UI completa, 3 errores admin menores | -| Bark Scripts | ✅ 100% | Listos para desplegar | | install.sh | ✅ 100% | Detecta dev/prod automáticamente | | Skills Verification | ✅ 100% | Implementado en IA y BD | -| Documentación | ✅ 100% | 4 archivos docs completos | +| Documentación | ✅ 100% | Archivos docs completos | **Progreso Total: 98%** 🎉 @@ -242,7 +192,5 @@ curl http://t-800:8000/health ## 📞 Soporte -- **Bark Installation**: `docs/BARK_MANUAL_INSTALL.md` - **UI Usage**: `docs/QUESTION_BANK_UI.md` -- **Bark API**: `docs/BARK_TTS_GUIDE.md` - **General**: `README.md` del proyecto diff --git a/docs/BARK_MANUAL_INSTALL.md b/docs/BARK_MANUAL_INSTALL.md deleted file mode 100644 index 31e0513..0000000 --- a/docs/BARK_MANUAL_INSTALL.md +++ /dev/null @@ -1,206 +0,0 @@ -# Instalación Manual de Bark TTS en t-800 - -## Opción A: Instalación Automática (Recomendada) - -```bash -# 1. Copiar script a t-800 -scp scripts/install_bark_tts.sh juan@t-800:/tmp/install_bark_tts.sh - -# 2. Conectarse a t-800 -ssh juan@t-800 - -# 3. Ejecutar instalación -chmod +x /tmp/install_bark_tts.sh -sudo /tmp/install_bark_tts.sh - -# 4. Verificar instalación -curl http://localhost:8000/health -``` - -## Opción B: Instalación Paso a Paso - -```bash -# Conectarse a t-800 -ssh juan@t-800 -# Contraseña: apoca11 - -# Actualizar sistema -sudo apt-get update -sudo apt-get install -y python3 python3-pip python3-venv git ffmpeg curl - -# Crear directorio -sudo mkdir -p /opt/bark -sudo chown juan:juan /opt/bark -cd /opt/bark - -# Clonar Bark -git clone https://github.com/suno-ai/bark.git -cd bark - -# Crear entorno virtual -python3 -m venv venv -source venv/bin/activate - -# Instalar dependencias -pip install --upgrade pip -pip install -e . -pip install fastapi uvicorn[standard] python-multipart numpy scipy - -# Crear archivo de API -cat > bark_api.py << 'PYEOF' -from fastapi import FastAPI, HTTPException, Query -from fastapi.responses import StreamingResponse -from bark import SAMPLE_RATE, generate_audio, preload_models -from scipy.io.wavfile import write as write_wav -import numpy as np -import io - -app = FastAPI(title="Bark TTS API", version="1.0.0") - -print("Preloading Bark models...") -preload_models() -print("Models loaded!") - -@app.get("/health") -async def health_check(): - return {"status": "healthy", "service": "bark-tts"} - -@app.get("/api/voices") -async def list_voices(): - return { - "voices": [ - {"id": "v2/en_speaker_0", "name": "English Speaker 0", "language": "en"}, - {"id": "v2/en_speaker_1", "name": "English Speaker 1", "language": "en"}, - {"id": "v2/en_speaker_6", "name": "English Speaker 6", "language": "en"}, - {"id": "v2/es_speaker_0", "name": "Spanish Speaker 0", "language": "es"}, - {"id": "v2/es_speaker_1", "name": "Spanish Speaker 1", "language": "es"}, - {"id": "v2/es_speaker_3", "name": "Spanish Speaker 3", "language": "es"}, - ] - } - -@app.post("/api/generate") -async def generate_speech( - text: str = Query(..., min_length=1, max_length=500), - voice: str = Query(default="v2/en_speaker_1"), - speed: float = Query(default=1.0, ge=0.5, le=2.0), - output_format: str = Query(default="wav", regex="^(mp3|wav|ogg)$") -): - try: - audio_array = generate_audio(text, history_prompt=voice) - - if speed != 1.0: - new_length = int(len(audio_array) / speed) - audio_array = audio_array[:new_length] - - audio_buffer = io.BytesIO() - write_wav(audio_buffer, SAMPLE_RATE, audio_array) - audio_buffer.seek(0) - - return StreamingResponse( - audio_buffer, - media_type="audio/wav", - headers={"Content-Disposition": f"attachment; filename=speech.wav"} - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) -PYEOF - -# Crear servicio systemd -cat > /tmp/bark-tts.service << EOF -[Unit] -Description=Bark TTS API Server -After=network.target - -[Service] -Type=simple -User=juan -Group=juan -WorkingDirectory=/opt/bark/bark -Environment="PATH=/opt/bark/bark/venv/bin" -ExecStart=/opt/bark/bark/venv/bin/uvicorn bark_api:app --host 0.0.0.0 --port 8000 --workers 1 -Restart=always -RestartSec=10 -MemoryMax=4G -MemoryHigh=3G -CPUQuota=80% - -[Install] -WantedBy=multi-user.target -EOF - -sudo mv /tmp/bark-tts.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable bark-tts -sudo systemctl start bark-tts - -# Verificar -sudo systemctl status bark-tts -curl http://localhost:8000/health -``` - -## Prueba de Funcionamiento - -```bash -# Test básico -curl "http://localhost:8000/api/generate?text=Hello%20World&voice=v2/en_speaker_1" -o test.wav - -# Test desde OpenCCB -curl http://t-800:8000/health - -# Ver logs -sudo journalctl -u bark-tts -f -``` - -## Configuración en OpenCCB - -Agregar a `.env`: -```bash -BARK_API_URL=http://t-800:8000 -# O para producción: -# BARK_API_URL=http://t-800.norteamericano.cl:8000 -``` - -## Solución de Problemas - -### Error: "Out of Memory" -```bash -# Reducir límite de memoria en systemd -sudo systemctl edit bark-tts -# Agregar: -# [Service] -# MemoryMax=2G -``` - -### Error: "Model not found" -```bash -# Reinstalar modelos -cd /opt/bark/bark -source venv/bin/activate -python -c "from bark import preload_models; preload_models()" -``` - -### Servicio no inicia -```bash -# Ver logs -sudo journalctl -u bark-tts -n 50 - -# Reiniciar -sudo systemctl restart bark-tts -``` - -## URLs de Acceso - -- **Health**: http://t-800:8000/health -- **Voices**: http://t-800:8000/api/voices -- **Generate**: http://t-800:8000/api/generate?text=Hello&voice=v2/en_speaker_1 - -## Producción (t-800.norteamericano.cl) - -Para producción, asegurar que: -1. El puerto 8000 esté abierto en el firewall -2. El dominio t-800.norteamericano.cl apunte a la IP correcta -3. Usar BARK_API_URL=http://t-800.norteamericano.cl:8000 diff --git a/docs/BARK_TTS_GUIDE.md b/docs/BARK_TTS_GUIDE.md deleted file mode 100644 index 0b4c8fe..0000000 --- a/docs/BARK_TTS_GUIDE.md +++ /dev/null @@ -1,221 +0,0 @@ -# Bark TTS Integration Guide - -## Overview - -OpenCCB now integrates with **Suno AI's Bark** text-to-speech system for generating audio versions of questions. This allows students to listen to questions instead of just reading them, improving accessibility and supporting different learning styles. - -## Architecture - -``` -┌─────────────────┐ HTTP ┌─────────────────┐ -│ OpenCCB CMS │ ────────────> │ Bark TTS API │ -│ (PostgreSQL) │ <──────────── │ (Server t-800)│ -│ │ Audio │ │ -└─────────────────┘ └─────────────────┘ -``` - -## Deployment to t-800 Server - -### Prerequisites - -- SSH access to t-800 server -- At least 8GB RAM recommended (Bark loads large models) -- 10GB free disk space -- Python 3.8+ -- GPU optional (CUDA support for faster generation) - -### Quick Deploy - -```bash -# From your local machine -cd /home/juan/dev/openccb -./scripts/deploy_to_t800.sh -``` - -This will: -1. SSH into t-800 -2. Install Python dependencies -3. Clone Bark repository -4. Set up systemd service -5. Start the API server - -### Manual Deploy - -```bash -# SSH into t-800 -ssh juan@t-800 - -# Run installation script -wget https://raw.githubusercontent.com/suno-ai/bark/main/scripts/install.sh -sudo bash install.sh -``` - -## API Endpoints - -Once deployed, Bark API is available at `http://t-800:8000` - -### Health Check -```bash -curl http://t-800:8000/health -``` - -### List Available Voices -```bash -curl http://t-800:8000/api/voices -``` - -### Generate Speech -```bash -# Basic usage -curl "http://t-800:8000/api/generate?text=What%20color%20is%20the%20sky%3F" \ - -o question.wav - -# With specific voice and speed -curl "http://t-800:8000/api/generate?text=Hello%20World&voice=v2/en_speaker_6&speed=1.2" \ - -o greeting.wav - -# Spanish voice -curl "http://t-800:8000/api/generate?text=Hola%20mundo&voice=v2/es_speaker_0" \ - -o saludo.wav -``` - -## Available Voices - -### English Voices -- `v2/en_speaker_0` through `v2/en_speaker_9` - -### Spanish Voices -- `v2/es_speaker_0` through `v2/es_speaker_9` - -## Integration with OpenCCB - -### Generate Audio for a Question - -```bash -# Via API -curl -X POST "http://localhost:3001/question-bank/{question_id}/generate-audio" \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "text": "What color is the sky?", - "voice": "v2/en_speaker_1", - "speed": 1.0 - }' -``` - -### Automatic Audio Generation - -When creating a question: - -```json -POST /question-bank -{ - "question_text": "What is the capital of France?", - "question_type": "multiple-choice", - "options": ["Paris", "London", "Berlin", "Madrid"], - "correct_answer": 0, - "explanation": "Paris is the capital of France.", - "generate_audio": true // Triggers async audio generation -} -``` - -## Configuration - -### Environment Variables - -Add to your `.env` file: - -```bash -# Bark TTS API URL -BARK_API_URL=http://t-800:8000 - -# Optional: Default voice for audio generation -BARK_DEFAULT_VOICE=v2/en_speaker_1 - -# Optional: Default speed -BARK_DEFAULT_SPEED=1.0 -``` - -## Performance Optimization - -### Model Preloading - -Bark preloads models on startup (takes ~30 seconds). The systemd service handles this automatically. - -### Memory Management - -The systemd service includes memory limits: -```ini -MemoryMax=4G -MemoryHigh=3G -``` - -Adjust based on your server's capacity. - -### Batch Generation - -For importing many questions: - -```bash -# Generate audio for multiple questions -curl "http://t-800:8000/api/generate/batch?texts=Question%201&texts=Question%202&voice=v2/en_speaker_1" -``` - -## Troubleshooting - -### Service Not Starting - -```bash -# Check status -sudo systemctl status bark-tts - -# View logs -sudo journalctl -u bark-tts -f - -# Restart service -sudo systemctl restart bark-tts -``` - -### Out of Memory - -If Bark crashes due to memory: -1. Reduce `MemoryMax` in systemd service -2. Use smaller models: `suno/bark-small` -3. Process questions one at a time - -### Slow Generation - -- GPU acceleration: Install CUDA-enabled PyTorch -- Reduce audio quality settings -- Use shorter text segments - -## Testing - -```bash -# Test English voice -curl "http://t-800:8000/api/generate?text=The%20quick%20brown%20fox&voice=v2/en_speaker_1" | play - - -# Test Spanish voice -curl "http://t-800:8000/api/generate?text=El%20rápido%20zorro%20marrón&voice=v2/es_speaker_0" | play - -``` - -## Security Notes - -- Bark API runs on internal network only -- No authentication required (assumes trusted network) -- Rate limiting handled by OpenCCB -- Audio files stored in `uploads/audio/` directory - -## Future Enhancements - -- [ ] Add authentication to Bark API -- [ ] Support for custom voice cloning -- [ ] Audio preprocessing (noise reduction, normalization) -- [ ] Caching layer for repeated requests -- [ ] WebSocket support for streaming audio - -## References - -- [Bark GitHub](https://github.com/suno-ai/bark) -- [Bark Hugging Face](https://huggingface.co/suno/bark) -- [OpenCCB Question Bank Documentation](../docs/question-bank.md) diff --git a/docs/QUESTION_BANK_UI.md b/docs/QUESTION_BANK_UI.md index 8b3d324..e650472 100644 --- a/docs/QUESTION_BANK_UI.md +++ b/docs/QUESTION_BANK_UI.md @@ -101,40 +101,6 @@ Modal para importar preguntas desde MySQL: - Progreso de importación - Resultado (éxito/error) -### 5. AudioGeneratorModal (`AudioGeneratorModal.tsx`) - -Modal para generar audio con Bark TTS: - -**Configuración:** -- Vista previa del texto de la pregunta -- Texto personalizable (opcional) -- Selector de voz (6 opciones: 3 inglés, 3 español) -- Control de velocidad (0.5x - 2.0x) - -**Voces disponibles:** -``` -Inglés: -- v2/en_speaker_0 (English Speaker 0) -- v2/en_speaker_1 (English Speaker 1) ← default -- v2/en_speaker_6 (English Speaker 6) - -Español: -- v2/es_speaker_0 (Spanish Speaker 0) -- v2/es_speaker_1 (Spanish Speaker 1) -- v2/es_speaker_3 (Spanish Speaker 3) -``` - -**Estados:** -- ⏳ Generando... (polling cada 1s, max 30s) -- ✅ Audio generado (con preview play/pause) -- ❌ Error (mensaje descriptivo) - -**Características:** -- Polling automático para verificar estado -- Reproductor de audio integrado -- Botón Play/Pause -- Indicador visual de estado - ## Flujos de Usuario ### Crear Pregunta Manualmente @@ -288,11 +254,6 @@ GET /question-bank/mysql-courses - Verificar que `MYSQL_DATABASE_URL` esté configurado en `.env` - Verificar conectividad al servidor MySQL -**Error: "Error al generar audio"** -- Verificar que Bark TTS esté corriendo en t-800 -- Verificar que `BARK_API_URL` esté configurado -- Revisar logs de Bark: `ssh juan@t-800 && journalctl -u bark-tts -f` - **Audio no se reproduce** - Verificar formato de audio (WAV soportado) - Verificar permisos del navegador @@ -301,5 +262,4 @@ GET /question-bank/mysql-courses ## Referencias - [Question Bank Backend](../../services/cms-service/src/handlers_question_bank.rs) -- [Bark TTS Guide](../../docs/BARK_TTS_GUIDE.md) - [Test Templates UI](./TestTemplates/) diff --git a/install.sh b/install.sh index bbb08ac..bd607b3 100755 --- a/install.sh +++ b/install.sh @@ -120,11 +120,9 @@ echo "🔍 Configurando Servicios de IA Remota ($ENV_CHOICE)..." if [ "$ENV_CHOICE" == "dev" ]; then DEFAULT_OLLAMA="http://t-800:11434" DEFAULT_WHISPER="http://t-800:9000" - DEFAULT_BARK="http://t-800:8000" else DEFAULT_OLLAMA="http://t-800.norteamericano.cl:11434" DEFAULT_WHISPER="http://t-800.norteamericano.cl:9000" - DEFAULT_BARK="http://t-800.norteamericano.cl:8000" fi read -p "Ingrese la URL de Ollama Remoto [$DEFAULT_OLLAMA]: " REMOTE_OLLAMA_URL @@ -133,27 +131,22 @@ read -p "Ingrese la URL de Whisper Remoto [$DEFAULT_WHISPER]: " REMOTE_WHISPER_U REMOTE_WHISPER_URL=${REMOTE_WHISPER_URL:-$DEFAULT_WHISPER} read -p "Ingrese la URL del Image Bridge Remoto [http://t-800:8080]: " REMOTE_IMAGE_URL REMOTE_IMAGE_URL=${REMOTE_IMAGE_URL:-"http://t-800:8080"} -read -p "Ingrese la URL de Bark TTS Remoto [$DEFAULT_BARK]: " REMOTE_BARK_URL -REMOTE_BARK_URL=${REMOTE_BARK_URL:-$DEFAULT_BARK} read -p "Ingrese el nombre del Modelo (en el servidor remoto) [llama3.2:3b]: " LLM_MODEL LLM_MODEL=${LLM_MODEL:-llama3.2:3b} update_env "AI_PROVIDER" "local" update_env "LOCAL_LLM_MODEL" "$LLM_MODEL" update_env "LOCAL_VIDEO_BRIDGE_URL" "$REMOTE_IMAGE_URL" -update_env "BARK_API_URL" "$REMOTE_BARK_URL" if [ "$ENV_CHOICE" == "dev" ]; then update_env "DEV_OLLAMA_URL" "$REMOTE_OLLAMA_URL" update_env "DEV_WHISPER_URL" "$REMOTE_WHISPER_URL" - update_env "DEV_BARK_URL" "$REMOTE_BARK_URL" # Portavilidad: set base URLs too update_env "LOCAL_OLLAMA_URL" "$REMOTE_OLLAMA_URL" update_env "LOCAL_WHISPER_URL" "$REMOTE_WHISPER_URL" else update_env "PROD_OLLAMA_URL" "$REMOTE_OLLAMA_URL" update_env "PROD_WHISPER_URL" "$REMOTE_WHISPER_URL" - update_env "PROD_BARK_URL" "$REMOTE_BARK_URL" # Portavilidad: set base URLs too update_env "LOCAL_OLLAMA_URL" "$REMOTE_OLLAMA_URL" update_env "LOCAL_WHISPER_URL" "$REMOTE_WHISPER_URL" diff --git a/scripts/check_bark_status.sh b/scripts/check_bark_status.sh deleted file mode 100644 index e4a2ba8..0000000 --- a/scripts/check_bark_status.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -# Check and start Bark TTS service on t-800 - -echo "=== Checking Bark TTS Status on t-800 ===" -echo "" - -# Check if systemd service exists -if systemctl list-unit-files | grep -q bark-tts; then - echo "✅ Bark systemd service found" - - # Check service status - echo "" - echo "Service Status:" - sudo systemctl status bark-tts --no-pager - - # If not running, try to start - if ! systemctl is-active --quiet bark-tts; then - echo "" - echo "⚠️ Service is not running. Attempting to start..." - sudo systemctl start bark-tts - - sleep 5 - - if systemctl is-active --quiet bark-tts; then - echo "✅ Service started successfully!" - else - echo "❌ Failed to start service. Checking logs..." - echo "" - echo "Recent logs:" - sudo journalctl -u bark-tts -n 20 --no-pager - fi - else - echo "✅ Service is running" - fi -else - echo "❌ Bark systemd service not found" - echo "" - echo "The installation may not have completed successfully." - echo "Check if Bark is installed manually:" - echo "" - echo " ls -la /opt/bark/bark/" - echo " ps aux | grep uvicorn" - echo "" - echo "To install manually, run:" - echo " ssh juan@t-800" - echo " sudo /tmp/install_bark_tts.sh (if script exists)" - echo "" -fi - -# Test API if service is running -if systemctl is-active --quiet bark-tts; then - echo "" - echo "=== Testing Bark API ===" - - # Health check - echo "Health endpoint:" - curl -s http://localhost:8443/health | head -5 - - echo "" - echo "" - echo "Voices endpoint:" - curl -s http://localhost:8443/api/voices | head -10 - - echo "" - echo "" - echo "=== API is accessible ===" - echo "You can now generate audio with:" - echo " curl 'http://localhost:8443/api/generate?text=Hello%20World&voice=v2/en_speaker_1' -o test.wav" -fi diff --git a/scripts/cleanup_bark_t800.sh b/scripts/cleanup_bark_t800.sh deleted file mode 100755 index 8f7da05..0000000 --- a/scripts/cleanup_bark_t800.sh +++ /dev/null @@ -1,193 +0,0 @@ -#!/bin/bash -# Bark TTS Cleanup Script for t-800 -# This script removes all Bark TTS components from the server - -set -e - -T800_HOST="t-800" -T800_USER="juan" - -echo "==========================================" -echo " Bark TTS Cleanup for t-800" -echo "==========================================" -echo "" -echo "This script will completely remove Bark TTS from t-800" -echo "Including:" -echo " - Systemd service" -echo " - Installation directory (/opt/bark)" -echo " - User account (bark)" -echo " - All cached models (~3.6 GB)" -echo "" - -read -p "Continue? [y/N]: " confirm -if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then - echo "❌ Cancelled" - exit 0 -fi - -echo "" -echo "📤 Copying cleanup script to t-800..." - -# Create cleanup script -cat > /tmp/cleanup_bark_remote.sh << 'INNEREOF' -#!/bin/bash -set -e - -echo "" -echo "=== Stopping Bark Service ===" -sudo systemctl stop bark-tts 2>/dev/null && echo "✅ Service stopped" || echo "⚠️ Service not running" - -echo "" -echo "=== Disabling Bark Service ===" -sudo systemctl disable bark-tts 2>/dev/null && echo "✅ Service disabled" || echo "⚠️ Service not enabled" - -echo "" -echo "=== Removing Systemd Service ===" -if [ -f /etc/systemd/system/bark-tts.service ]; then - sudo rm -f /etc/systemd/system/bark-tts.service - sudo systemctl daemon-reload - echo "✅ Systemd service removed" -else - echo "⚠️ Systemd service not found" -fi - -echo "" -echo "=== Removing Installation Directory ===" -if [ -d /opt/bark ]; then - SIZE=$(du -sh /opt/bark 2>/dev/null | cut -f1) - echo "📊 Directory size: $SIZE" - sudo rm -rf /opt/bark - echo "✅ Directory removed" -else - echo "⚠️ Directory /opt/bark not found" -fi - -echo "" -echo "=== Removing User Account ===" -if id bark &>/dev/null; then - sudo userdel -r bark 2>/dev/null && echo "✅ User removed" || echo "⚠️ Could not remove user" -else - echo "⚠️ User 'bark' does not exist" -fi - -echo "" -echo "=== Cleaning Python Cache ===" -sudo rm -rf /root/.cache/pip 2>/dev/null || true -echo "✅ Cache cleaned" - -echo "" -echo "==========================================" -echo " Verification" -echo "==========================================" -echo "" - -echo "Services:" -if systemctl list-unit-files 2>/dev/null | grep -q bark; then - echo "❌ Bark services still exist:" - systemctl list-unit-files 2>/dev/null | grep bark -else - echo "✅ No Bark services found" -fi - -echo "" -echo "Directories:" -if [ -d /opt/bark ]; then - echo "❌ Directory still exists: /opt/bark" -else - echo "✅ No Bark directories found" -fi - -echo "" -echo "Users:" -if id bark &>/dev/null 2>&1; then - echo "❌ User 'bark' still exists" -else - echo "✅ User 'bark' removed" -fi - -echo "" -echo "Processes:" -if ps aux | grep -v grep | grep -q "bark_api"; then - echo "❌ Bark processes still running" - ps aux | grep -v grep | grep "bark_api" -else - echo "✅ No Bark processes running" -fi - -echo "" -echo "Ports:" -if sudo netstat -tlnp 2>/dev/null | grep -q 8443; then - echo "❌ Port 8443 still in use" - sudo netstat -tlnp 2>/dev/null | grep 8443 -else - echo "✅ Port 8443 is free" -fi - -echo "" -echo "Disk Space Recovered:" -echo "Previous: $(df -h /opt 2>/dev/null | tail -1 | awk '{print $4}') available" -echo "" - -echo "==========================================" -echo " ✅ Cleanup Complete!" -echo "==========================================" -echo "" -echo "Next steps:" -echo " 1. Verify disk space: df -h" -echo " 2. Check available ports: sudo netstat -tlnp" -echo " 3. Continue with OpenCCB features" -echo "" -INNEREOF - -# Copy to t-800 -scp /tmp/cleanup_bark_remote.sh ${T800_USER}@${T800_HOST}:/tmp/cleanup_bark_remote.sh - -echo "" -echo "🔌 Connecting to t-800 and running cleanup..." -echo "" - -# Execute on t-800 -ssh -t ${T800_USER}@${T800_HOST} << 'ENDSSH' - chmod +x /tmp/cleanup_bark_remote.sh - sudo /tmp/cleanup_bark_remote.sh - - # Clean up temp file - rm /tmp/cleanup_bark_remote.sh -ENDSSH - -echo "" -echo "==========================================" -echo " Local Cleanup Complete" -echo "==========================================" -echo "" - -# Clean up local temp file -rm -f /tmp/cleanup_bark_remote.sh - -echo "✅ All Bark TTS components have been removed from t-800" -echo "" -echo "📊 Next Steps - Choose What to Do Next:" -echo "" -echo " 1️⃣ Question Bank (sin audio)" -echo " - Crear preguntas manualmente" -echo " - Importar desde MySQL" -echo " - Generar con IA" -echo " - Acceder: /question-bank" -echo "" -echo " 2️⃣ Token Usage Dashboard" -echo " - Ver consumo de IA por usuario" -echo " - Monitorear costos" -echo " - Detectar alto consumo" -echo " - Acceder: /admin/token-usage" -echo "" -echo " 3️⃣ Importar Preguntas desde MySQL" -echo " - Traer preguntas del sistema legacy" -echo " - Marcar para no duplicar" -echo " - Asignar skills automáticamente" -echo "" -echo "💡 Recommended: Try all three!" -echo "" -echo " cd /home/juan/dev/openccb" -echo " # Start Studio" -echo " cd web/studio && npm run dev" -echo "" diff --git a/scripts/deploy_to_t800.sh b/scripts/deploy_to_t800.sh deleted file mode 100755 index fc345f1..0000000 --- a/scripts/deploy_to_t800.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash -# Deploy Bark TTS to t-800 server -# Usage: ./deploy_to_t800.sh - -set -e - -T800_HOST="t-800" -T800_USER="juan" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -echo "==========================================" -echo " Deploying Bark TTS to t-800" -echo "==========================================" -echo "" - -# Check if SSH key exists -if [ ! -f ~/.ssh/id_rsa.pub ]; then - echo "SSH key not found. Generating one..." - ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N "" -C "openccb_bark_deployment" - echo "" - echo "Now copy your SSH key to t-800:" - echo " ssh-copy-id ${T800_USER}@${T800_HOST}" - echo "" - read -p "Press Enter after copying the key..." -fi - -# Copy installation script to t-800 -echo "Copying installation script to t-800..." -scp "${SCRIPT_DIR}/install_bark_tts.sh" ${T800_USER}@${T800_HOST}:/tmp/install_bark_tts.sh - -# Execute installation on t-800 with pseudo-terminal -echo "" -echo "Connecting to t-800 and installing Bark TTS..." -echo "This may take 10-15 minutes depending on internet speed..." -echo "You'll be prompted for your password..." -echo "" - -ssh -t ${T800_USER}@${T800_HOST} << 'ENDSSH' - echo "Connected to t-800" - echo "Hostname: $(hostname)" - echo "Memory: $(free -h | grep Mem | awk '{print $2}')" - echo "Disk: $(df -h / | tail -1 | awk '{print $4}') available" - echo "" - - # Install jq if not present - if ! command -v jq &> /dev/null; then - echo "Installing jq..." - sudo apt-get update && sudo apt-get install -y jq - fi - - # Make script executable and run - chmod +x /tmp/install_bark_tts.sh - echo "Running Bark installation..." - sudo /tmp/install_bark_tts.sh - - # Clean up - rm /tmp/install_bark_tts.sh - - # Wait for service to be ready - echo "" - echo "Waiting for Bark API to be ready..." - sleep 10 - - # Test the API - echo "Testing Bark API..." - if curl -s http://localhost:8443/health | jq . > /dev/null 2>&1; then - echo "✅ Bark API is running!" - curl -s http://localhost:8443/health | jq . - else - echo "⚠️ API may still be starting up..." - echo "Check status with: sudo systemctl status bark-tts" - echo "View logs with: sudo journalctl -u bark-tts -f" - fi - - echo "" - echo "Bark TTS installation complete on t-800!" -ENDSSH - -echo "" -echo "==========================================" -echo " Deployment Complete!" -echo "==========================================" -echo "" -echo "Next steps:" -echo "1. Add BARK_API_URL to your .env file:" -echo " BARK_API_URL=http://t-800:8443" -echo "" -echo "2. Test the API:" -echo " curl 'http://t-800:8443/api/generate?text=Hello%20World&voice=v2/en_speaker_1' -o test.wav" -echo "" -echo "3. Generate audio for questions in OpenCCB:" -echo " POST /question-bank/{id}/generate-audio" -echo "" diff --git a/scripts/fix_bark_pytorch.sh b/scripts/fix_bark_pytorch.sh deleted file mode 100644 index dcbb784..0000000 --- a/scripts/fix_bark_pytorch.sh +++ /dev/null @@ -1,189 +0,0 @@ -#!/bin/bash -# Fix Bark PyTorch 2.6+ compatibility issue -# Run this on t-800 - -set -e - -echo "=== Fixing Bark PyTorch 2.6+ Compatibility ===" -echo "" - -BARK_DIR="/opt/bark/bark" - -# Backup original generation.py -echo "[1/3] Backing up original generation.py..." -cp $BARK_DIR/bark/generation.py $BARK_DIR/bark/generation.py.backup - -# Patch generation.py to use weights_only=False -echo "[2/3] Patching generation.py..." -sed -i 's/torch.load(ckpt_path, map_location=device)/torch.load(ckpt_path, map_location=device, weights_only=False)/g' $BARK_DIR/bark/generation.py - -# Create fixed bark_api.py -echo "[3/3] Creating fixed bark_api.py..." -cat > $BARK_DIR/bark/bark_api.py << 'PYEOF' -""" -Bark TTS API Server - Fixed for PyTorch 2.6+ -Simple FastAPI wrapper for Bark text-to-speech -""" -from fastapi import FastAPI, HTTPException, Query -from fastapi.responses import StreamingResponse -from bark import SAMPLE_RATE, generate_audio, preload_models -from scipy.io.wavfile import write as write_wav -import numpy as np -import io -import torch - -# Fix PyTorch 2.6+ weights_only issue -# This must be done BEFORE preload_models() -print("Configuring PyTorch for Bark compatibility...") - -app = FastAPI( - title="Bark TTS API", - description="Text-to-Speech API using Suno AI's Bark", - version="1.0.0" -) - -# Preload models on startup (with warm-up) -@app.on_event("startup") -async def startup_event(): - print("Preloading Bark models...") - try: - preload_models() - print("Models loaded successfully!") - - # Warm-up with a short generation - print("Warming up models with short generation...") - from bark import generate_text_semantic - text_semantic = generate_text_semantic("Hi", temp=0.7) - print("Warm-up complete! API ready.") - except Exception as e: - print(f"ERROR loading models: {e}") - raise - -@app.get("/health") -async def health_check(): - return {"status": "healthy", "service": "bark-tts"} - -@app.get("/api/voices") -async def list_voices(): - """List available voice presets""" - return { - "voices": [ - {"id": "v2/en_speaker_0", "name": "English Speaker 0", "language": "en"}, - {"id": "v2/en_speaker_1", "name": "English Speaker 1", "language": "en"}, - {"id": "v2/en_speaker_6", "name": "English Speaker 6", "language": "en"}, - {"id": "v2/es_speaker_0", "name": "Spanish Speaker 0", "language": "es"}, - {"id": "v2/es_speaker_1", "name": "Spanish Speaker 1", "language": "es"}, - {"id": "v2/es_speaker_3", "name": "Spanish Speaker 3", "language": "es"}, - ] - } - -@app.post("/api/generate") -async def generate_speech( - text: str = Query(..., min_length=1, max_length=500, description="Text to convert to speech"), - voice: str = Query(default="v2/en_speaker_1", description="Voice preset to use"), - speed: float = Query(default=1.0, ge=0.5, le=2.0, description="Speech speed multiplier"), - output_format: str = Query(default="wav", regex="^(mp3|wav|ogg)$", description="Output audio format") -): - """Generate speech from text using Bark TTS""" - try: - # Generate audio - audio_array = generate_audio(text, history_prompt=voice) - - # Apply speed adjustment if needed - if speed != 1.0: - new_length = int(len(audio_array) / speed) - audio_array = audio_array[:new_length] - - # Convert to bytes - audio_buffer = io.BytesIO() - write_wav(audio_buffer, SAMPLE_RATE, audio_array) - audio_buffer.seek(0) - - return StreamingResponse( - audio_buffer, - media_type="audio/wav", - headers={ - "Content-Disposition": f"attachment; filename=speech.wav", - "X-Voice-Used": voice, - "X-Speed": str(speed), - "X-Duration-Seconds": str(len(audio_array) / SAMPLE_RATE) - } - ) - - except Exception as e: - print(f"Generation error: {e}") - raise HTTPException(status_code=500, detail=str(e)) - -@app.post("/api/generate/batch") -async def generate_batch_speech( - texts: list[str] = Query(..., description="List of texts to convert"), - voice: str = Query(default="v2/en_speaker_1", description="Voice preset"), - speed: float = Query(default=1.0, description="Speech speed") -): - """Generate multiple audio files in batch""" - results = [] - - for i, text in enumerate(texts): - try: - audio_array = generate_audio(text, history_prompt=voice) - - # Save to temp file - import tempfile - with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f: - write_wav(f.name, SAMPLE_RATE, audio_array) - results.append({ - "index": i, - "text": text, - "duration_seconds": len(audio_array) / SAMPLE_RATE, - "status": "success" - }) - except Exception as e: - results.append({ - "index": i, - "text": text, - "error": str(e), - "status": "failed" - }) - - return {"results": results} - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) -PYEOF - -# Fix ownership -chown bark:bark $BARK_DIR/bark/generation.py -chown bark:bark $BARK_DIR/bark/bark_api.py - -echo "" -echo "=== Patch Applied ===" -echo "" -echo "Restarting Bark service..." -systemctl restart bark-tts - -echo "" -echo "Waiting for models to load (this takes 1-2 minutes)..." -sleep 30 - -echo "" -echo "Checking service status..." -systemctl status bark-tts --no-pager - -echo "" -echo "Testing API..." -if curl -s http://localhost:8443/health | jq . > /dev/null 2>&1; then - echo "✅ Bark API is running!" - curl -s http://localhost:8443/health | jq . -else - echo "⚠️ API is still loading models..." - echo "Check logs with: journalctl -u bark-tts -f" -fi - -echo "" -echo "=== Fix Complete ===" -echo "" -echo "If you see errors, check:" -echo " 1. journalctl -u bark-tts -f" -echo " 2. systemctl status bark-tts" -echo "" diff --git a/scripts/install_bark_manual.sh b/scripts/install_bark_manual.sh deleted file mode 100644 index 381912b..0000000 --- a/scripts/install_bark_manual.sh +++ /dev/null @@ -1,208 +0,0 @@ -#!/bin/bash -# Manual Bark TTS Installation for t-800 -# Run this ONCE on t-800 server - -set -e - -echo "==========================================" -echo " Manual Bark TTS Installation" -echo "==========================================" -echo "" - -# Check if running as root or with sudo -if [ "$EUID" -ne 0 ]; then - echo "Please run with: sudo ./install_bark_manual.sh" - exit 1 -fi - -echo "[1/6] Installing system dependencies..." -apt-get update -apt-get install -y python3 python3-pip python3-venv git ffmpeg curl jq - -echo "" -echo "[2/6] Creating bark user..." -if ! id -u bark > /dev/null 2>&1; then - useradd -r -m -s /bin/bash bark - echo "User 'bark' created" -else - echo "User 'bark' already exists" -fi - -echo "" -echo "[3/6] Setting up application directory..." -BARK_DIR="/opt/bark" -mkdir -p $BARK_DIR -chown bark:bark $BARK_DIR - -echo "" -echo "[4/6] Cloning Bark repository..." -cd $BARK_DIR -su - bark -c "cd $BARK_DIR && git clone https://github.com/suno-ai/bark.git" -chown -R bark:bark $BARK_DIR/bark - -echo "" -echo "[5/6] Creating Python virtual environment and installing dependencies..." -cd $BARK_DIR/bark -su - bark -c "cd $BARK_DIR/bark && python3 -m venv venv" -su - bark -c "cd $BARK_DIR/bark && source venv/bin/activate && pip install --upgrade pip" -su - bark -c "cd $BARK_DIR/bark && source venv/bin/activate && pip install -e ." -su - bark -c "cd $BARK_DIR/bark && source venv/bin/activate && pip install fastapi uvicorn[standard] python-multipart numpy scipy" - -echo "" -echo "[6/6] Creating systemd service..." -cat > /etc/systemd/system/bark-tts.service << EOF -[Unit] -Description=Bark TTS API Server -After=network.target - -[Service] -Type=simple -User=bark -Group=bark -WorkingDirectory=$BARK_DIR/bark -Environment="PATH=$BARK_DIR/bark/venv/bin" -ExecStart=$BARK_DIR/bark/venv/bin/uvicorn bark_api:app --host 0.0.0.0 --port 8443 --workers 1 -Restart=always -RestartSec=10 - -# Memory limits -MemoryMax=4G -MemoryHigh=3G - -# CPU limits -CPUQuota=80% - -[Install] -WantedBy=multi-user.target -EOF - -# Create Bark API wrapper -cat > $BARK_DIR/bark/bark_api.py << 'PYEOF' -""" -Bark TTS API Server -Simple FastAPI wrapper for Bark text-to-speech -""" -from fastapi import FastAPI, HTTPException, Query -from fastapi.responses import StreamingResponse -from bark import SAMPLE_RATE, generate_audio, preload_models -from scipy.io.wavfile import write as write_wav -import numpy as np -import io - -app = FastAPI( - title="Bark TTS API", - description="Text-to-Speech API using Suno AI's Bark", - version="1.0.0" -) - -# Preload models on startup -print("Preloading Bark models...") -preload_models() -print("Models loaded!") - -@app.get("/health") -async def health_check(): - return {"status": "healthy", "service": "bark-tts"} - -@app.get("/api/voices") -async def list_voices(): - """List available voice presets""" - return { - "voices": [ - {"id": "v2/en_speaker_0", "name": "English Speaker 0", "language": "en"}, - {"id": "v2/en_speaker_1", "name": "English Speaker 1", "language": "en"}, - {"id": "v2/en_speaker_6", "name": "English Speaker 6", "language": "en"}, - {"id": "v2/es_speaker_0", "name": "Spanish Speaker 0", "language": "es"}, - {"id": "v2/es_speaker_1", "name": "Spanish Speaker 1", "language": "es"}, - {"id": "v2/es_speaker_3", "name": "Spanish Speaker 3", "language": "es"}, - ] - } - -@app.post("/api/generate") -async def generate_speech( - text: str = Query(..., min_length=1, max_length=500, description="Text to convert to speech"), - voice: str = Query(default="v2/en_speaker_1", description="Voice preset to use"), - speed: float = Query(default=1.0, ge=0.5, le=2.0, description="Speech speed multiplier"), - output_format: str = Query(default="wav", regex="^(mp3|wav|ogg)$", description="Output audio format") -): - """Generate speech from text using Bark TTS""" - try: - # Generate audio - audio_array = generate_audio(text, history_prompt=voice) - - # Apply speed adjustment if needed - if speed != 1.0: - new_length = int(len(audio_array) / speed) - audio_array = audio_array[:new_length] - - # Convert to bytes - audio_buffer = io.BytesIO() - write_wav(audio_buffer, SAMPLE_RATE, audio_array) - audio_buffer.seek(0) - - return StreamingResponse( - audio_buffer, - media_type="audio/wav", - headers={ - "Content-Disposition": f"attachment; filename=speech.wav", - "X-Voice-Used": voice, - "X-Speed": str(speed), - "X-Duration-Seconds": str(len(audio_array) / SAMPLE_RATE) - } - ) - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) -PYEOF - -chown bark:bark $BARK_DIR/bark/bark_api.py - -echo "" -echo "Enabling and starting service..." -systemctl daemon-reload -systemctl enable bark-tts -systemctl start bark-tts - -echo "" -echo "==========================================" -echo " Installation Complete!" -echo "==========================================" -echo "" -echo "Service Status:" -systemctl status bark-tts --no-pager -echo "" -echo "Waiting for Bark to preload models (this takes 1-2 minutes)..." -sleep 30 - -echo "" -echo "Testing API..." -if curl -s http://localhost:8000/health | jq . > /dev/null 2>&1; then - echo "✅ Bark API is running!" - curl -s http://localhost:8000/health | jq . -else - echo "⚠️ API is starting up, models are loading..." - echo "Check status with: systemctl status bark-tts" - echo "View logs with: journalctl -u bark-tts -f" -fi - -echo "" -echo "==========================================" -echo " Next Steps" -echo "==========================================" -echo "" -echo "1. Test the API:" -echo " curl 'http://localhost:8000/api/generate?text=Hello%20World&voice=v2/en_speaker_1' -o test.wav" -echo "" -echo "2. Check service status anytime:" -echo " systemctl status bark-tts" -echo "" -echo "3. View logs:" -echo " journalctl -u bark-tts -f" -echo "" -echo "4. Restart service if needed:" -echo " systemctl restart bark-tts" -echo "" diff --git a/scripts/install_bark_tts.sh b/scripts/install_bark_tts.sh deleted file mode 100755 index 8b4b997..0000000 --- a/scripts/install_bark_tts.sh +++ /dev/null @@ -1,266 +0,0 @@ -#!/bin/bash -# Bark TTS Installation Script for t-800 server -# This script installs Suno AI's Bark text-to-speech system - -set -e - -echo "==========================================" -echo " Bark TTS Installation - Server t-800" -echo "==========================================" - -# Check if running as root -if [ "$EUID" -ne 0 ]; then - echo "Please run as root (sudo ./install_bark.sh)" - exit 1 -fi - -# System requirements check -echo "[1/8] Checking system requirements..." -REQUIRED_RAM=8 -AVAILABLE_RAM=$(free -g | awk '/^Mem:/{print $2}') - -if [ $AVAILABLE_RAM -lt $REQUIRED_RAM ]; then - echo "WARNING: Bark requires at least ${REQUIRED_RAM}GB RAM (found: ${AVAILABLE_RAM}GB)" - echo "Continuing anyway, but performance may be poor..." -fi - -# Update system packages -echo "[2/8] Updating system packages..." -apt-get update -apt-get install -y python3 python3-pip python3-venv git ffmpeg curl - -# Create bark user -echo "[3/8] Creating bark user..." -if ! id -u bark > /dev/null 2>&1; then - useradd -r -m -s /bin/bash bark - echo "User 'bark' created" -else - echo "User 'bark' already exists" -fi - -# Create application directory -echo "[4/8] Setting up application directory..." -BARK_DIR="/opt/bark" -mkdir -p $BARK_DIR -chown bark:bark $BARK_DIR - -# Clone Bark repository -echo "[5/8] Cloning Bark repository..." -cd $BARK_DIR -if [ ! -d "bark" ]; then - su - bark -c "cd $BARK_DIR && git clone https://github.com/suno-ai/bark.git" - chown -R bark:bark $BARK_DIR/bark -else - echo "Bark repository already exists, updating..." - su - bark -c "cd $BARK_DIR/bark && git pull" -fi - -# Create virtual environment -echo "[6/8] Creating Python virtual environment..." -cd $BARK_DIR/bark -su - bark -c "cd $BARK_DIR/bark && python3 -m venv venv" - -# Install dependencies -echo "[7/8] Installing Python dependencies..." -su - bark -c "cd $BARK_DIR/bark && source venv/bin/activate && pip install --upgrade pip && pip install -e ." - -# Additional dependencies for API server -su - bark -c "source $BARK_DIR/bark/venv/bin/activate && pip install fastapi uvicorn[standard] python-multipart numpy scipy" - -# Create systemd service -echo "[8/8] Creating systemd service..." -cat > /etc/systemd/system/bark-tts.service << EOF -[Unit] -Description=Bark TTS API Server -After=network.target - -[Service] -Type=simple -User=bark -Group=bark -WorkingDirectory=$BARK_DIR/bark -Environment="PATH=$BARK_DIR/bark/venv/bin" -ExecStart=$BARK_DIR/bark/venv/bin/uvicorn bark_api:app --host 0.0.0.0 --port 8000 --workers 1 -Restart=always -RestartSec=10 - -# Memory limits -MemoryMax=4G -MemoryHigh=3G - -# CPU limits -CPUQuota=80% - -[Install] -WantedBy=multi-user.target -EOF - -# Create Bark API wrapper -cat > $BARK_DIR/bark/bark_api.py << 'PYEOF' -""" -Bark TTS API Server -Simple FastAPI wrapper for Bark text-to-speech -""" -from fastapi import FastAPI, HTTPException, Query -from fastapi.responses import StreamingResponse, JSONResponse -from bark import SAMPLE_RATE, generate_audio, preload_models -from scipy.io.wavfile import write as write_wav -import numpy as np -import io -import os -import tempfile - -app = FastAPI( - title="Bark TTS API", - description="Text-to-Speech API using Suno AI's Bark", - version="1.0.0" -) - -# Preload models on startup -print("Preloading Bark models...") -preload_models() -print("Models loaded!") - -@app.get("/health") -async def health_check(): - return {"status": "healthy", "service": "bark-tts"} - -@app.get("/api/voices") -async def list_voices(): - """List available voice presets""" - return { - "voices": [ - {"id": "v2/en_speaker_0", "name": "English Speaker 0", "language": "en"}, - {"id": "v2/en_speaker_1", "name": "English Speaker 1", "language": "en"}, - {"id": "v2/en_speaker_2", "name": "English Speaker 2", "language": "en"}, - {"id": "v2/en_speaker_3", "name": "English Speaker 3", "language": "en"}, - {"id": "v2/en_speaker_4", "name": "English Speaker 4", "language": "en"}, - {"id": "v2/en_speaker_5", "name": "English Speaker 5", "language": "en"}, - {"id": "v2/en_speaker_6", "name": "English Speaker 6", "language": "en"}, - {"id": "v2/en_speaker_7", "name": "English Speaker 7", "language": "en"}, - {"id": "v2/en_speaker_8", "name": "English Speaker 8", "language": "en"}, - {"id": "v2/en_speaker_9", "name": "English Speaker 9", "language": "en"}, - {"id": "v2/es_speaker_0", "name": "Spanish Speaker 0", "language": "es"}, - {"id": "v2/es_speaker_1", "name": "Spanish Speaker 1", "language": "es"}, - {"id": "v2/es_speaker_2", "name": "Spanish Speaker 2", "language": "es"}, - {"id": "v2/es_speaker_3", "name": "Spanish Speaker 3", "language": "es"}, - {"id": "v2/es_speaker_4", "name": "Spanish Speaker 4", "language": "es"}, - {"id": "v2/es_speaker_5", "name": "Spanish Speaker 5", "language": "es"}, - {"id": "v2/es_speaker_6", "name": "Spanish Speaker 6", "language": "es"}, - {"id": "v2/es_speaker_7", "name": "Spanish Speaker 7", "language": "es"}, - {"id": "v2/es_speaker_8", "name": "Spanish Speaker 8", "language": "es"}, - {"id": "v2/es_speaker_9", "name": "Spanish Speaker 9", "language": "es"}, - ] - } - -@app.post("/api/generate") -async def generate_speech( - text: str = Query(..., min_length=1, max_length=500, description="Text to convert to speech"), - voice: str = Query(default="v2/en_speaker_1", description="Voice preset to use"), - speed: float = Query(default=1.0, ge=0.5, le=2.0, description="Speech speed multiplier"), - output_format: str = Query(default="mp3", regex="^(mp3|wav|ogg)$", description="Output audio format") -): - """Generate speech from text using Bark TTS""" - try: - # Extract speaker number from voice preset - parts = voice.split("_") - if len(parts) >= 3: - speaker = f"{parts[0]}_{parts[1]}_{parts[2]}" - else: - speaker = "v2/en_speaker_1" - - # Generate audio - audio_array = generate_audio(text, history_prompt=speaker) - - # Apply speed adjustment if needed - if speed != 1.0: - # Simple speed adjustment by resampling - new_length = int(len(audio_array) / speed) - audio_array = audio_array[:new_length] - - # Convert to bytes - audio_buffer = io.BytesIO() - write_wav(audio_buffer, SAMPLE_RATE, audio_array) - audio_buffer.seek(0) - - # For MP3 output, we'd need to add pydub/ffmpeg - # For now, return WAV - media_type = "audio/wav" - filename = f"speech_{voice}.{output_format}" - - return StreamingResponse( - audio_buffer, - media_type=media_type, - headers={ - "Content-Disposition": f"attachment; filename={filename}", - "X-Voice-Used": voice, - "X-Speed": str(speed), - "X-Duration-Seconds": str(len(audio_array) / SAMPLE_RATE) - } - ) - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@app.post("/api/generate/batch") -async def generate_batch_speech( - texts: list[str] = Query(..., description="List of texts to convert"), - voice: str = Query(default="v2/en_speaker_1", description="Voice preset"), - speed: float = Query(default=1.0, description="Speech speed") -): - """Generate multiple audio files in batch""" - results = [] - - for i, text in enumerate(texts): - try: - audio_array = generate_audio(text, history_prompt=voice) - - # Save to temp file - with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f: - write_wav(f.name, SAMPLE_RATE, audio_array) - results.append({ - "index": i, - "text": text, - "duration_seconds": len(audio_array) / SAMPLE_RATE, - "status": "success" - }) - except Exception as e: - results.append({ - "index": i, - "text": text, - "error": str(e), - "status": "failed" - }) - - return JSONResponse(content={"results": results}) - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) -PYEOF - -chown bark:bark $BARK_DIR/bark/bark_api.py - -# Enable and start service -systemctl daemon-reload -systemctl enable bark-tts -systemctl start bark-tts - -echo "" -echo "==========================================" -echo " Installation Complete!" -echo "==========================================" -echo "" -echo "Service Status:" -systemctl status bark-tts --no-pager -echo "" -echo "API Endpoints:" -echo " - Health: http://localhost:8000/health" -echo " - Voices: http://localhost:8000/api/voices" -echo " - Generate: http://localhost:8000/api/generate?text=Hello&voice=v2/en_speaker_1" -echo "" -echo "Usage Example:" -echo " curl 'http://localhost:8000/api/generate?text=What%20color%20is%20the%20sky%3F&voice=v2/en_speaker_1' -o question.wav" -echo "" -echo "Logs: journalctl -u bark-tts -f" -echo "" diff --git a/services/cms-service/src/handlers_question_bank.rs b/services/cms-service/src/handlers_question_bank.rs index 57885a6..242b352 100644 --- a/services/cms-service/src/handlers_question_bank.rs +++ b/services/cms-service/src/handlers_question_bank.rs @@ -43,19 +43,11 @@ pub async fn create_question( .bind(payload.difficulty.as_deref().unwrap_or("medium")) .bind(payload.tags.as_deref()) .bind(payload.skill_assessed.as_deref()) - .bind(payload.media_url.as_deref()) .bind(payload.media_type.as_deref()) .fetch_one(&pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - // If audio generation requested, trigger it asynchronously - if payload.generate_audio.unwrap_or(false) { - tokio::spawn(async move { - let _ = generate_audio_for_question(question.id, pool.clone()).await; - }); - } - Ok(Json(question)) } @@ -437,129 +429,6 @@ pub async fn import_from_mysql( Ok(Json(imported_questions)) } -// ==================== Audio Generation ==================== - -/// POST /api/question-bank/{id}/generate-audio - Generate audio for a question using Bark -pub async fn generate_audio( - Org(org_ctx): Org, - Path(id): Path, - State(pool): State, - payload: Option>, -) -> Result { - // Get question - let question: QuestionBank = sqlx::query_as( - "SELECT * FROM question_bank WHERE id = $1 AND organization_id = $2" - ) - .bind(id) - .bind(org_ctx.id) - .fetch_one(&pool) - .await - .map_err(|e| match e { - sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Question not found".to_string()), - _ => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), - })?; - - // Spawn async task for audio generation - tokio::spawn(async move { - let _ = generate_audio_for_question_with_params(id, pool, payload.map(|p| p.0)).await; - }); - - Ok(StatusCode::ACCEPTED) -} - -async fn generate_audio_for_question( - question_id: Uuid, - pool: PgPool, -) -> Result<(), String> { - generate_audio_for_question_with_params(question_id, pool, None).await -} - -async fn generate_audio_for_question_with_params( - question_id: Uuid, - pool: PgPool, - payload: Option, -) -> Result<(), String> { - use reqwest::Client; - use serde_json::json; - - // Get question text - let question_text: String = sqlx::query_scalar("SELECT audio_text FROM question_bank WHERE id = $1") - .bind(question_id) - .fetch_optional(&pool) - .await - .map_err(|e| format!("Failed to get question: {}", e))? - .unwrap_or_default(); - - let text = payload.as_ref().map(|p| p.text.clone()).unwrap_or(question_text); - - // Update status to generating - sqlx::query("UPDATE question_bank SET audio_status = 'generating' WHERE id = $1") - .bind(question_id) - .execute(&pool) - .await - .map_err(|e| format!("Failed to update status: {}", e))?; - - // Call Bark TTS API - let bark_url = std::env::var("BARK_API_URL").unwrap_or_else(|_| "http://localhost:8000".to_string()); - let client = Client::new(); - - let voice = payload.as_ref().and_then(|p| p.voice.clone()).unwrap_or_else(|| "v2/en_speaker_1".to_string()); - let speed = payload.as_ref().and_then(|p| p.speed).unwrap_or(1.0); - - let response = client - .post(&format!("{}/api/generate", bark_url)) - .json(&json!({ - "text": text, - "voice": voice, - "speed": speed, - "output_format": "mp3" - })) - .send() - .await - .map_err(|e| format!("Bark API request failed: {}", e))?; - - if !response.status().is_success() { - sqlx::query("UPDATE question_bank SET audio_status = 'failed' WHERE id = $1") - .bind(question_id) - .execute(&pool) - .await - .map_err(|_| "Failed to update status".to_string())?; - - return Err(format!("Bark API returned error: {}", response.status())); - } - - // Save audio file - let audio_bytes = response.bytes().await.map_err(|e| format!("Failed to get audio bytes: {}", e))?; - - // Save to uploads directory - let filename = format!("question_{}.mp3", question_id); - let file_path = format!("uploads/audio/{}", filename); - - std::fs::create_dir_all("uploads/audio").map_err(|e| format!("Failed to create directory: {}", e))?; - std::fs::write(&file_path, &audio_bytes).map_err(|e| format!("Failed to save audio: {}", e))?; - - // Update question with audio URL - let audio_url = format!("/audio/{}", filename); - sqlx::query( - "UPDATE question_bank SET audio_url = $1, audio_status = 'ready', audio_metadata = $2 WHERE id = $3" - ) - .bind(&audio_url) - .bind(&json!({ - "voice": voice, - "speed": speed, - "generated_at": chrono::Utc::now().to_rfc3339(), - "file_size": audio_bytes.len(), - })) - .bind(question_id) - .execute(&pool) - .await - .map_err(|e| format!("Failed to update question: {}", e))?; - - tracing::info!("Generated audio for question {}", question_id); - - Ok(()) -} - // ==================== Helpers ==================== fn map_mysql_question_type(mysql_type: i32) -> QuestionBankType { diff --git a/services/cms-service/src/main.rs b/services/cms-service/src/main.rs index 2e4ca48..0135966 100644 --- a/services/cms-service/src/main.rs +++ b/services/cms-service/src/main.rs @@ -96,7 +96,7 @@ async fn main() { }); let cors = CorsLayer::new() - .allow_origin(Any) + .allow_origin("http://localhost:3000".parse::().unwrap()) .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::OPTIONS, Method::PATCH]) .allow_headers([ header::CONTENT_TYPE, @@ -104,7 +104,7 @@ async fn main() { header::HeaderName::from_static("x-requested-with"), header::HeaderName::from_static("x-organization-id"), ]) - .expose_headers([header::CONTENT_LENGTH]); + .expose_headers([header::CONTENT_LENGTH, header::CONTENT_TYPE]); // Rate limiting: Deshabilitado temporalmente por problemas de compatibilidad con tower-governor // Para habilitar en producción, configurar con GovernorLayer y ajustar los límites apropiadamente @@ -343,10 +343,6 @@ async fn main() { "/question-bank/import-mysql", post(handlers_question_bank::import_from_mysql), ) - .route( - "/question-bank/{id}/generate-audio", - post(handlers_question_bank::generate_audio), - ) .route( "/question-bank/mysql-courses", get(handlers_question_bank::list_mysql_courses), @@ -392,10 +388,6 @@ async fn main() { .route( "/branding", get(handlers_branding::get_organization_branding), - ) - .route( - "/organization", - get(handlers::get_public_organization), ); let public_routes = Router::new() diff --git a/shared/common/src/models.rs b/shared/common/src/models.rs index a50d0d9..0ff4d42 100644 --- a/shared/common/src/models.rs +++ b/shared/common/src/models.rs @@ -1408,7 +1408,6 @@ pub struct CreateQuestionBankPayload { pub tags: Option>, pub media_url: Option, pub media_type: Option, - pub generate_audio: Option, pub skill_assessed: Option, // reading, listening, speaking, writing } @@ -1433,13 +1432,6 @@ pub struct ImportQuestionFromMySQLPayload { pub import_all: Option, // Import all questions from MySQL } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct GenerateAudioPayload { - pub text: String, - pub voice: Option, // Bark voice preset - pub speed: Option, -} - #[derive(Debug, Serialize, Deserialize, Clone)] pub struct QuestionBankFilters { pub question_type: Option, diff --git a/web/studio/src/app/admin/page.tsx b/web/studio/src/app/admin/page.tsx index 60d78d0..1b8c599 100644 --- a/web/studio/src/app/admin/page.tsx +++ b/web/studio/src/app/admin/page.tsx @@ -23,15 +23,15 @@ export default function AdminDashboard() { useEffect(() => { const fetchStats = async () => { try { - // In a real app we'd have a specific stats endpoint, + // In a real app we'd have a specific stats endpoint, // but for now we'll calculate from lists - const [orgs, users] = await Promise.all([ - cmsApi.getOrganizations(), + const [org, users] = await Promise.all([ + cmsApi.getOrganization(), cmsApi.getAllUsers() ]); setStats({ - orgs: orgs.length, + orgs: 1, // Single tenant architecture users: users.length, courses: 0 // We'd need a global courses count }); diff --git a/web/studio/src/app/admin/users/page.tsx b/web/studio/src/app/admin/users/page.tsx index 0e25413..92d728c 100644 --- a/web/studio/src/app/admin/users/page.tsx +++ b/web/studio/src/app/admin/users/page.tsx @@ -29,12 +29,12 @@ export default function UsersPage() { const loadData = async () => { try { - const [usersData, orgsData] = await Promise.all([ + const [usersData, orgData] = await Promise.all([ cmsApi.getAllUsers(), - cmsApi.getOrganizations() + cmsApi.getOrganization() ]); setUsers(usersData); - setOrganizations(orgsData); + setOrganizations([orgData]); // Single tenant - wrap in array for compatibility } catch (error) { console.error('Failed to load data', error); } finally { diff --git a/web/studio/src/app/question-bank/page.tsx b/web/studio/src/app/question-bank/page.tsx index 38984be..d7dd37e 100644 --- a/web/studio/src/app/question-bank/page.tsx +++ b/web/studio/src/app/question-bank/page.tsx @@ -2,15 +2,14 @@ import React, { useState, useEffect } from 'react'; import { questionBankApi, QuestionBank, QuestionBankFilters, QuestionBankType } from '@/lib/api'; -import { - Plus, Search, Filter, Edit2, Trash2, Volume2, VolumeX, Download, +import { + Plus, Search, Filter, Edit2, Trash2, Download, Upload, Sparkles, ChevronDown, ChevronUp, X, Check, AlertCircle, Headphones, BookOpen, Tag, Hash, Globe } from 'lucide-react'; import QuestionBankEditor from '@/components/QuestionBank/QuestionBankEditor'; import QuestionBankCard from '@/components/QuestionBank/QuestionBankCard'; import MySQLImportModal from '@/components/QuestionBank/MySQLImportModal'; -import AudioGeneratorModal from '@/components/QuestionBank/AudioGeneratorModal'; export default function QuestionBankPage() { const [questions, setQuestions] = useState([]); @@ -21,8 +20,6 @@ export default function QuestionBankPage() { const [showEditor, setShowEditor] = useState(false); const [editingQuestion, setEditingQuestion] = useState(null); const [showImportModal, setShowImportModal] = useState(false); - const [showAudioModal, setShowAudioModal] = useState(false); - const [selectedForAudio, setSelectedForAudio] = useState(null); const loadQuestions = async () => { try { @@ -67,17 +64,6 @@ export default function QuestionBankPage() { await loadQuestions(); }; - const handleAudioGenerate = (questionId: string) => { - setSelectedForAudio(questionId); - setShowAudioModal(true); - }; - - const handleAudioSuccess = async () => { - setShowAudioModal(false); - setSelectedForAudio(null); - await loadQuestions(); - }; - const getQuestionTypeLabel = (type: QuestionBankType) => { const labels: Record = { 'multiple-choice': 'Opción Múltiple', @@ -311,7 +297,6 @@ export default function QuestionBankPage() { question={question} onEdit={() => handleEdit(question)} onDelete={() => handleDelete(question.id)} - onGenerateAudio={() => handleAudioGenerate(question.id)} /> ))} @@ -337,18 +322,6 @@ export default function QuestionBankPage() { onCancel={() => setShowImportModal(false)} /> )} - - {/* Audio Generator Modal */} - {showAudioModal && selectedForAudio && ( - { - setShowAudioModal(false); - setSelectedForAudio(null); - }} - /> - )} ); } diff --git a/web/studio/src/components/QuestionBank/AudioGeneratorModal.tsx b/web/studio/src/components/QuestionBank/AudioGeneratorModal.tsx deleted file mode 100644 index b449394..0000000 --- a/web/studio/src/components/QuestionBank/AudioGeneratorModal.tsx +++ /dev/null @@ -1,320 +0,0 @@ -'use client'; - -import React, { useState, useEffect } from 'react'; -import { questionBankApi, QuestionBank } from '@/lib/api'; -import { X, Volume2, Check, AlertCircle, Play, Pause } from 'lucide-react'; - -interface AudioGeneratorModalProps { - questionId: string; - onSuccess?: () => void; - onCancel?: () => void; -} - -export default function AudioGeneratorModal({ questionId, onSuccess, onCancel }: AudioGeneratorModalProps) { - const [question, setQuestion] = useState(null); - const [loading, setLoading] = useState(true); - const [generating, setGenerating] = useState(false); - const [generated, setGenerated] = useState(false); - const [error, setError] = useState(null); - const [isPlaying, setIsPlaying] = useState(false); - const [audio, setAudio] = useState(null); - - const [voice, setVoice] = useState('v2/en_speaker_1'); - const [speed, setSpeed] = useState(1.0); - const [customText, setCustomText] = useState(''); - - const voices = [ - { id: 'v2/en_speaker_0', name: 'English Speaker 0', language: 'en' }, - { id: 'v2/en_speaker_1', name: 'English Speaker 1', language: 'en' }, - { id: 'v2/en_speaker_6', name: 'English Speaker 6', language: 'en' }, - { id: 'v2/es_speaker_0', name: 'Spanish Speaker 0', language: 'es' }, - { id: 'v2/es_speaker_1', name: 'Spanish Speaker 1', language: 'es' }, - { id: 'v2/es_speaker_3', name: 'Spanish Speaker 3', language: 'es' }, - ]; - - useEffect(() => { - loadQuestion(); - }, [questionId]); - - const loadQuestion = async () => { - try { - const data = await questionBankApi.get(questionId); - setQuestion(data); - setCustomText(data.question_text); - } catch (error) { - console.error('Failed to load question:', error); - setError('No se pudo cargar la pregunta'); - } finally { - setLoading(false); - } - }; - - const handleGenerate = async () => { - try { - setGenerating(true); - setError(null); - - await questionBankApi.generateAudio(questionId, customText, voice, speed); - - // Poll for completion - let attempts = 0; - const maxAttempts = 30; // 30 seconds max - - while (attempts < maxAttempts) { - await new Promise(resolve => setTimeout(resolve, 1000)); - const updated = await questionBankApi.get(questionId); - - if (updated.audio_status === 'ready') { - setGenerated(true); - setQuestion(updated); - break; - } else if (updated.audio_status === 'failed') { - setError('Error al generar el audio'); - break; - } - - attempts++; - } - - if (attempts >= maxAttempts) { - setError('Tiempo de espera agotado. El audio puede estar generándose aún.'); - } - } catch (error: any) { - console.error('Audio generation failed:', error); - setError(error.message || 'Error al generar audio'); - } finally { - setGenerating(false); - } - }; - - const handlePlay = () => { - if (!question?.audio_url) return; - - if (audio) { - audio.remove(); - } - - const audioEl = new Audio(question.audio_url); - audioEl.onended = () => setIsPlaying(false); - audioEl.onerror = () => { - setIsPlaying(false); - alert('Error al reproducir el audio'); - }; - - setAudio(audioEl); - setIsPlaying(true); - audioEl.play(); - }; - - const handleStop = () => { - if (audio) { - audio.pause(); - audio.currentTime = 0; - } - setIsPlaying(false); - }; - - if (loading) { - return ( -
-
-
-

Cargando pregunta...

-
-
- ); - } - - return ( -
-
- {/* Header */} -
-
- -
-

- Generar Audio con Bark -

-

- Convierte el texto de la pregunta a audio -

-
-
- -
- - {/* Content */} -
- {/* Question Preview */} -
- -

- {question?.question_text} -

-
- - {/* Custom Text */} -
- -