From 11c9b64e145495c5b9ac22be9fcf33ff0bd01f07 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Fri, 6 Feb 2026 14:35:38 +0100 Subject: [PATCH] Fix mixed content: route all requests through backend API Co-authored-by: Cursor --- app.py | 21 ++++++++++-- templates/index.html | 76 +++++++++++++++----------------------------- 2 files changed, 44 insertions(+), 53 deletions(-) diff --git a/app.py b/app.py index 2978d3c..0b7c73c 100644 --- a/app.py +++ b/app.py @@ -475,7 +475,7 @@ async def _listModels(authenticated: bool = Depends(_verifyApiKey)): return models @app.get("/api/ollama/status", response_model=OllamaStatusResponse, tags=["System"]) -async def _ollamaStatus(authenticated: bool = Depends(_verifyApiKey)): +async def _ollamaStatus(): """Check Ollama connection status and list available models.""" try: async with httpx.AsyncClient(timeout=10.0) as client: @@ -512,13 +512,30 @@ async def _ollamaStatus(authenticated: bool = Depends(_verifyApiKey)): @app.post("/api/analyze", response_model=AnalyzeResponse, tags=["AI"]) async def _analyzeDocument( request: AnalyzeRequest, - apiKey: str = Depends(_checkRateLimit) + xApiKey: Optional[str] = Header(None, alias="X-API-Key") ): """ Analyze a document with AI Vision API. Supports both vision models (with images) and text models (without images). + + Authentication: + - Gateway calls: Must include X-API-Key header + - Test UI calls: No auth required (same-origin) + + Rate limiting is applied when API key is provided. """ + # Apply rate limiting only for authenticated requests (Gateway) + if xApiKey: + if CONFIG["apiKey"] and xApiKey != CONFIG["apiKey"]: + raise HTTPException(status_code=401, detail="Invalid API key") + # Check rate limit for authenticated requests + allowed, info = rateLimiter.isAllowed(xApiKey) + if not allowed: + raise HTTPException( + status_code=429, + detail=f"Rate limit exceeded. Retry after {info['retryAfter']} seconds." + ) try: # Get internal model name internalModelName = _getInternalModelName(request.modelName) diff --git a/templates/index.html b/templates/index.html index 0b0f26a..ebe0fcb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -681,7 +681,7 @@
- +
@@ -838,18 +838,23 @@ Falls ein Feld nicht erkennbar ist, setze den Wert auf null. ollamaStatusDiv.textContent = 'Prüfe Ollama-Verbindung...'; try { - // Direkt Ollama API abfragen (ohne Auth) - const response = await fetch(`${ollamaUrl.value}/api/tags`, { + // Backend API abfragen (geht über HTTPS) + const response = await fetch('/api/ollama/status', { method: 'GET', headers: { 'Accept': 'application/json' } }); if (!response.ok) { - throw new Error(`Ollama antwortet mit Status ${response.status}`); + throw new Error(`Backend antwortet mit Status ${response.status}`); } const result = await response.json(); - const availableModels = (result.models || []).map(m => m.name); + + if (!result.connected) { + throw new Error(result.error || 'Ollama nicht verbunden'); + } + + const availableModels = result.models || []; console.log('Available Ollama models:', availableModels); @@ -1168,39 +1173,19 @@ Falls ein Feld nicht erkennbar ist, setze den Wert auf null. _hideError(); try { - // Get Ollama model name from PowerOn name - const ollamaModelName = _getOllamaModelName(modelName.value); - console.log('Ollama model name:', ollamaModelName); - - // Model-specific context lengths - const modelContextLengths = { - 'qwen2.5:7b': 32768, - 'qwen2.5vl:7b': 32768, - 'granite3.2-vision': 16000 - }; - const numCtx = modelContextLengths[ollamaModelName] || 8192; - - // Request-Body für Ollama erstellen + // Request-Body für Backend API erstellen const requestBody = { - model: ollamaModelName, + modelName: modelName.value, // PowerOn model name prompt: promptInput.value, - stream: false, - options: { - num_ctx: numCtx - } + imageBase64: currentImageBase64 || null }; - console.log('Sending request to:', `${ollamaUrl.value}/api/generate`); - console.log('Request body (without prompt):', { ...requestBody, prompt: '[TRUNCATED]' }); + console.log('Sending request to: /api/analyze'); + console.log('Request body (without image):', { ...requestBody, imageBase64: requestBody.imageBase64 ? '[BASE64_IMAGE]' : null }); - // Bild nur hinzufügen wenn vorhanden - if (currentImageBase64) { - requestBody.images = [currentImageBase64]; - } - - // Call Ollama API directly - console.log('Fetching from Ollama...'); - const response = await fetch(`${ollamaUrl.value}/api/generate`, { + // Call Backend API (geht über HTTPS) + console.log('Fetching from Backend...'); + const response = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) @@ -1210,30 +1195,19 @@ Falls ein Feld nicht erkennbar ist, setze den Wert auf null. if (!response.ok) { const errorText = await response.text(); - console.error('Ollama error:', errorText); - throw new Error(`Ollama Fehler: ${response.status} - ${errorText.substring(0, 200)}`); + console.error('Backend error:', errorText); + throw new Error(`Backend Fehler: ${response.status} - ${errorText.substring(0, 200)}`); } const result = await response.json(); - console.log('Ollama response received:', result); - const responseText = result.response || ''; + console.log('Backend response received:', result); - // Try to extract JSON from response - let extractedData = null; - const jsonMatch = responseText.match(/\{[\s\S]*\}/); - - if (jsonMatch) { - try { - extractedData = JSON.parse(jsonMatch[0]); - } catch (e) { - extractedData = null; - } + if (!result.success) { + throw new Error(result.error || 'Analyse fehlgeschlagen'); } - // Wrap plain text response in JSON object - if (extractedData === null) { - extractedData = { response: responseText.trim() }; - } + const responseText = result.rawResponse || ''; + const extractedData = result.data || { response: responseText.trim() }; _displayResults(extractedData, responseText); _setStatus('success', 'Erfolgreich extrahiert');