diff --git a/backend/app/main.py b/backend/app/main.py index b675f03..29c6eb4 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -276,7 +276,7 @@ def extract_pdf_text_smart(pdf_content: bytes, max_chars: int = None) -> dict: } -BACKEND_VERSION = "1.2.0" +BACKEND_VERSION = "1.2.1" app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) # S3/MinIO configuration @@ -2782,20 +2782,50 @@ def get_ai_configuration( return config +@app.get("/api/ai/api-keys") +def get_all_api_keys( + db: Session = Depends(get_db), + current_user: models.User = Depends(get_current_user) +): + """Obtener todas las API keys guardadas (sin mostrar las keys completas)""" + if current_user.role != "admin": + raise HTTPException(status_code=403, detail="Solo administradores pueden ver API keys") + + configs = db.query(models.AIConfiguration).all() + + result = {} + for config in configs: + # Solo devolver si tiene API key guardada (enmascarada) + if config.api_key: + masked_key = config.api_key[:8] + "..." + config.api_key[-4:] if len(config.api_key) > 12 else "***" + result[config.provider] = { + "has_key": True, + "masked_key": masked_key, + "is_active": config.is_active + } + + return result + + @app.post("/api/ai/configuration", response_model=schemas.AIConfiguration) def create_ai_configuration( config: schemas.AIConfigurationCreate, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user) ): - """Crear o actualizar configuración de IA""" + """Crear o actualizar configuración de IA - ACTIVA el proveedor seleccionado""" if current_user.role != "admin": raise HTTPException(status_code=403, detail="Solo administradores pueden configurar IA") - # Desactivar configuraciones anteriores + # Desactivar TODAS las configuraciones db.query(models.AIConfiguration).update({"is_active": False}) - # Determinar modelo por defecto según el proveedor si no se especifica + # Buscar si ya existe configuración para este proveedor + existing_config = db.query(models.AIConfiguration).filter( + models.AIConfiguration.provider == config.provider + ).first() + + # Determinar modelo por defecto si no se especifica model_name = config.model_name if not model_name: if config.provider == "openai": @@ -2807,19 +2837,31 @@ def create_ai_configuration( else: model_name = "default" - # Crear nueva configuración - new_config = models.AIConfiguration( - provider=config.provider, - api_key=config.api_key, - model_name=model_name, - is_active=True - ) - - db.add(new_config) - db.commit() - db.refresh(new_config) - - return new_config + if existing_config: + # Actualizar configuración existente + # Solo actualizar API key si se proporciona una nueva (no vacía) + if config.api_key and config.api_key.strip(): + existing_config.api_key = config.api_key + existing_config.model_name = model_name + existing_config.is_active = True # Activar este proveedor + db.commit() + db.refresh(existing_config) + return existing_config + else: + # Crear nueva configuración (requiere API key) + if not config.api_key or not config.api_key.strip(): + raise HTTPException(status_code=400, detail="API key es requerida para nuevo proveedor") + + new_config = models.AIConfiguration( + provider=config.provider, + api_key=config.api_key, + model_name=model_name, + is_active=True # Activar este proveedor + ) + db.add(new_config) + db.commit() + db.refresh(new_config) + return new_config @app.put("/api/ai/configuration/{config_id}", response_model=schemas.AIConfiguration) diff --git a/frontend/package.json b/frontend/package.json index 5186aaf..25d7c97 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.2.5", + "version": "1.2.6", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/service-worker.js b/frontend/public/service-worker.js index c0c6293..01e74d1 100644 --- a/frontend/public/service-worker.js +++ b/frontend/public/service-worker.js @@ -1,6 +1,6 @@ // Service Worker para PWA con detección de actualizaciones // IMPORTANTE: Actualizar esta versión cada vez que se despliegue una nueva versión -const CACHE_NAME = 'ayutec-v1.2.5'; +const CACHE_NAME = 'ayutec-v1.2.6'; const urlsToCache = [ '/', '/index.html' diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2fcecb0..d7d7cec 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -505,6 +505,14 @@ function SettingsTab({ user }) { api_key: '', model_name: 'gpt-4o' }); + + // Estado para guardar todas las API keys y proveedor activo + const [savedApiKeys, setSavedApiKeys] = useState({ + openai: '', + anthropic: '', + gemini: '' + }); + const [activeProvider, setActiveProvider] = useState(null); // Proveedor actualmente activo useEffect(() => { const fetchLogo = async () => { @@ -541,7 +549,7 @@ function SettingsTab({ user }) { setAvailableModels(models); } - // Cargar configuración actual + // Cargar configuración activa const configRes = await fetch(`${API_URL}/api/ai/configuration`, { headers: { 'Authorization': `Bearer ${token}` } }); @@ -549,6 +557,7 @@ function SettingsTab({ user }) { if (configRes.ok) { const config = await configRes.json(); setAiConfig(config); + setActiveProvider(config.provider); setFormData({ provider: config.provider || 'openai', api_key: config.api_key || '', @@ -557,6 +566,30 @@ function SettingsTab({ user }) { } else if (configRes.status === 404) { // No hay configuración guardada, usar valores por defecto console.log('No hay configuración de IA guardada'); + setActiveProvider(null); + } + + // Cargar todas las API keys guardadas + const keysRes = await fetch(`${API_URL}/api/ai/api-keys`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (keysRes.ok) { + const keys = await keysRes.json(); + const newSavedKeys = { + openai: '', + anthropic: '', + gemini: '' + }; + + // Las keys vienen enmascaradas, solo indicamos que existen + Object.keys(keys).forEach(provider => { + if (keys[provider].has_key) { + newSavedKeys[provider] = keys[provider].masked_key; + } + }); + + setSavedApiKeys(newSavedKeys); } } catch (error) { console.error('Error loading settings:', error); @@ -647,30 +680,48 @@ function SettingsTab({ user }) {
{formData.provider === 'openai' ? ( @@ -707,22 +764,27 @@ function SettingsTab({ user }) {
- Ayutec v1.2.5 + Ayutec v1.2.6