diff --git a/deployment/poweron_sec.kdbx b/deployment/poweron_sec.kdbx index 368c8ae..5bd02d6 100644 Binary files a/deployment/poweron_sec.kdbx and b/deployment/poweron_sec.kdbx differ diff --git a/test-local-vision/app.py b/test-local-vision/app.py new file mode 100644 index 0000000..b805d73 --- /dev/null +++ b/test-local-vision/app.py @@ -0,0 +1,284 @@ +""" +Belegscanner - KI-Dokumentenanalyse +Python Flask Web App mit CORS-Unterstützung und Poweron Design +""" + +from flask import Flask, render_template, request, jsonify +from flask_cors import CORS +import requests +import base64 +import json +import re +import io + +# PDF Support +try: + import fitz # PyMuPDF + PDF_SUPPORT = True +except ImportError: + PDF_SUPPORT = False + print("WARNUNG: PyMuPDF nicht installiert. PDF-Support deaktiviert.") + print("Installieren mit: pip install pymupdf") + +app = Flask(__name__) +CORS(app) # CORS für alle Routen aktivieren + + +# ============================================================================ +# PDF Helper Functions +# ============================================================================ + +def _extractImagesFromPdf(pdfBytes, maxPages=5): + """ + Extrahiert Bilder aus einem PDF. + Gibt eine Liste von Base64-kodierten Bildern zurück. + """ + if not PDF_SUPPORT: + raise Exception("PDF-Support nicht verfügbar. Bitte PyMuPDF installieren.") + + images = [] + + # PDF öffnen + doc = fitz.open(stream=pdfBytes, filetype="pdf") + + # Anzahl der Seiten begrenzen + numPages = min(len(doc), maxPages) + + for pageNum in range(numPages): + page = doc[pageNum] + + # Seite als Bild rendern (höhere Auflösung für bessere OCR) + mat = fitz.Matrix(2.0, 2.0) # 2x Zoom für bessere Qualität + pix = page.get_pixmap(matrix=mat) + + # In PNG konvertieren + imgBytes = pix.tobytes("png") + imgBase64 = base64.b64encode(imgBytes).decode('utf-8') + + images.append({ + 'page': pageNum + 1, + 'base64': imgBase64, + 'width': pix.width, + 'height': pix.height + }) + + doc.close() + + return images + + +def _renderPdfPageAsImage(pdfBytes, pageNum=0, zoom=2.0): + """ + Rendert eine einzelne PDF-Seite als Bild. + """ + if not PDF_SUPPORT: + raise Exception("PDF-Support nicht verfügbar.") + + doc = fitz.open(stream=pdfBytes, filetype="pdf") + + if pageNum >= len(doc): + pageNum = len(doc) - 1 + + page = doc[pageNum] + mat = fitz.Matrix(zoom, zoom) + pix = page.get_pixmap(matrix=mat) + + imgBytes = pix.tobytes("png") + imgBase64 = base64.b64encode(imgBytes).decode('utf-8') + + result = { + 'base64': imgBase64, + 'width': pix.width, + 'height': pix.height, + 'page': pageNum + 1, + 'totalPages': len(doc) + } + + doc.close() + + return result + +# ============================================================================ +# Routes +# ============================================================================ + +@app.route('/') +def _index(): + """Hauptseite mit dem Belegscanner UI""" + return render_template('index.html') + + +@app.route('/api/analyze', methods=['POST']) +def _analyzeDocument(): + """ + Analysiert ein Dokument mit Ollama Vision API + Erwartet: { imageBase64, prompt, ollamaUrl, modelName } + """ + try: + data = request.get_json() + + imageBase64 = data.get('imageBase64') + prompt = data.get('prompt') + ollamaUrl = data.get('ollamaUrl', 'http://localhost:11434') + modelName = data.get('modelName', 'qwen2.5vl:72b') + + if not imageBase64: + return jsonify({'error': 'Kein Bild übermittelt'}), 400 + + if not prompt: + return jsonify({'error': 'Kein Prompt übermittelt'}), 400 + + # Ollama API aufrufen (Timeout: 60 Minuten für grosse Modelle) + response = requests.post( + f'{ollamaUrl}/api/generate', + json={ + 'model': modelName, + 'prompt': prompt, + 'images': [imageBase64], + 'stream': False + }, + timeout=3600 # 60 Minuten + ) + + if response.status_code == 404: + return jsonify({ + 'error': f'Modell "{modelName}" nicht gefunden. Bitte installieren Sie es mit: ollama pull {modelName}' + }), 404 + + if response.status_code != 200: + return jsonify({ + 'error': f'Ollama API Fehler: {response.status_code} - {response.text[:200]}' + }), response.status_code + + responseData = response.json() + responseText = responseData.get('response', '') + + # JSON aus der Antwort extrahieren + jsonMatch = re.search(r'\{[\s\S]*\}', responseText) + if not jsonMatch: + return jsonify({ + 'error': 'Keine JSON-Daten in der Antwort gefunden', + 'rawResponse': responseText + }), 400 + + extractedData = json.loads(jsonMatch.group()) + + return jsonify({ + 'success': True, + 'data': extractedData, + 'rawResponse': responseText + }) + + except requests.exceptions.Timeout: + return jsonify({'error': 'Zeitüberschreitung bei der Ollama API'}), 504 + except requests.exceptions.ConnectionError: + return jsonify({'error': 'Verbindung zu Ollama fehlgeschlagen. Ist Ollama gestartet?'}), 503 + except json.JSONDecodeError as e: + return jsonify({'error': f'JSON Parse-Fehler: {str(e)}'}), 400 + except Exception as e: + return jsonify({'error': f'Unerwarteter Fehler: {str(e)}'}), 500 + + +@app.route('/api/health', methods=['GET']) +def _healthCheck(): + """Health Check Endpoint""" + return jsonify({'status': 'ok', 'service': 'belegscanner', 'pdfSupport': PDF_SUPPORT}) + + +@app.route('/api/pdf/extract', methods=['POST']) +def _extractPdfImages(): + """ + Extrahiert Bilder aus einem PDF. + Erwartet: { pdfBase64, page (optional, default: alle) } + """ + if not PDF_SUPPORT: + return jsonify({ + 'error': 'PDF-Support nicht verfügbar. Bitte PyMuPDF installieren: pip install pymupdf' + }), 501 + + try: + data = request.get_json() + pdfBase64 = data.get('pdfBase64') + pageNum = data.get('page') # Optional: spezifische Seite + + if not pdfBase64: + return jsonify({'error': 'Kein PDF übermittelt'}), 400 + + # Base64 dekodieren + pdfBytes = base64.b64decode(pdfBase64) + + if pageNum is not None: + # Einzelne Seite extrahieren + result = _renderPdfPageAsImage(pdfBytes, pageNum - 1) # 0-basiert + return jsonify({ + 'success': True, + 'image': result + }) + else: + # Alle Seiten extrahieren (max 5) + images = _extractImagesFromPdf(pdfBytes, maxPages=5) + return jsonify({ + 'success': True, + 'images': images, + 'totalExtracted': len(images) + }) + + except Exception as e: + return jsonify({'error': f'PDF-Verarbeitungsfehler: {str(e)}'}), 500 + + +@app.route('/api/ollama/status', methods=['GET']) +def _ollamaStatus(): + """Prüft ob Ollama erreichbar ist und listet verfügbare Modelle""" + ollamaUrl = request.args.get('url', 'http://localhost:11434') + + try: + # Prüfe ob Ollama läuft + response = requests.get(f'{ollamaUrl}/api/tags', timeout=5) + + if response.status_code != 200: + return jsonify({ + 'connected': False, + 'error': f'Ollama antwortet mit Status {response.status_code}' + }) + + data = response.json() + models = [m.get('name', '') for m in data.get('models', [])] + + # Filtere Vision-Modelle (enthalten oft 'vision', 'vl', 'llava' im Namen) + visionModels = [m for m in models if any(x in m.lower() for x in ['vision', 'vl', 'llava', 'bakllava'])] + + return jsonify({ + 'connected': True, + 'models': models, + 'visionModels': visionModels, + 'totalModels': len(models) + }) + + except requests.exceptions.ConnectionError: + return jsonify({ + 'connected': False, + 'error': 'Keine Verbindung zu Ollama. Ist Ollama gestartet?' + }) + except Exception as e: + return jsonify({ + 'connected': False, + 'error': str(e) + }) + + +# ============================================================================ +# Main +# ============================================================================ + +if __name__ == '__main__': + print("\n" + "="*60) + print(" Belegscanner - KI-Dokumentenanalyse") + print(" Powered by Poweron") + print("="*60) + print("\n Server läuft auf: http://localhost:5000") + print(" CORS ist aktiviert für alle Origins") + print("\n Drücke Ctrl+C zum Beenden") + print("="*60 + "\n") + + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/test-local-vision/requirements.txt b/test-local-vision/requirements.txt new file mode 100644 index 0000000..f718672 --- /dev/null +++ b/test-local-vision/requirements.txt @@ -0,0 +1,5 @@ +flask>=3.0.0 +flask-cors>=4.0.0 +requests>=2.31.0 +werkzeug>=3.0.0 +pymupdf>=1.24.0 diff --git a/test-local-vision/start-python.bat b/test-local-vision/start-python.bat new file mode 100644 index 0000000..7a5b4ad --- /dev/null +++ b/test-local-vision/start-python.bat @@ -0,0 +1,41 @@ +@echo off +chcp 65001 >nul +echo ============================================================ +echo Belegscanner - KI-Dokumentenanalyse +echo Powered by Poweron +echo ============================================================ +echo. + +REM Ollama starten mit CORS-Freigabe +echo [1/3] Starte Ollama mit CORS-Freigabe... +set OLLAMA_ORIGINS=* + +REM Versuche Ollama zu finden +where ollama >nul 2>&1 +if %errorlevel%==0 ( + start /min ollama serve + echo Ollama gestartet +) else ( + if exist "%LOCALAPPDATA%\Programs\Ollama\ollama.exe" ( + start /min "" "%LOCALAPPDATA%\Programs\Ollama\ollama.exe" serve + echo Ollama gestartet + ) else ( + echo Ollama nicht gefunden - bitte manuell starten! + echo Setze OLLAMA_ORIGINS=* vor dem Start + ) +) + +timeout /t 2 /nobreak > nul + +REM Dependencies installieren +echo [2/3] Installiere Python Dependencies... +pip install -r requirements.txt --quiet + +echo [3/3] Starte Python Flask Server... +echo. +echo Server URL: http://localhost:5000 +echo Druecke Ctrl+C zum Beenden +echo. + +REM Flask starten +python app.py diff --git a/test-local-vision/start-python.ps1 b/test-local-vision/start-python.ps1 new file mode 100644 index 0000000..7feab99 --- /dev/null +++ b/test-local-vision/start-python.ps1 @@ -0,0 +1,54 @@ +# Belegscanner - Python Web App Starter +# Poweron Design + +Write-Host "============================================================" -ForegroundColor Cyan +Write-Host " Belegscanner - KI-Dokumentenanalyse" -ForegroundColor White +Write-Host " Powered by Poweron" -ForegroundColor Magenta +Write-Host "============================================================" -ForegroundColor Cyan +Write-Host "" + +# Ollama mit CORS starten (optional) +Write-Host "[1/3] Starte Ollama mit CORS-Freigabe..." -ForegroundColor Yellow +$env:OLLAMA_ORIGINS = "*" + +# Versuche Ollama zu finden und zu starten +$ollamaPath = Get-Command ollama -ErrorAction SilentlyContinue +if ($ollamaPath) { + Start-Process -FilePath $ollamaPath.Source -ArgumentList "serve" -WindowStyle Minimized + Write-Host " Ollama gestartet" -ForegroundColor Green +} else { + # Fallback: Standard-Installationspfade pruefen + $defaultPaths = @( + "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe", + "$env:ProgramFiles\Ollama\ollama.exe", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Ollama\ollama.exe" + ) + $found = $false + foreach ($path in $defaultPaths) { + if (Test-Path $path) { + Start-Process -FilePath $path -ArgumentList "serve" -WindowStyle Minimized + Write-Host " Ollama gestartet von: $path" -ForegroundColor Green + $found = $true + break + } + } + if (-not $found) { + Write-Host " Ollama nicht gefunden - bitte manuell starten!" -ForegroundColor Red + Write-Host " Setze OLLAMA_ORIGINS=* vor dem Start" -ForegroundColor Gray + } +} + +Start-Sleep -Seconds 2 + +# Dependencies installieren +Write-Host "[2/3] Installiere Python Dependencies..." -ForegroundColor Yellow +pip install -r requirements.txt --quiet + +Write-Host "[3/3] Starte Flask Server..." -ForegroundColor Yellow +Write-Host "" +Write-Host "Server URL: http://localhost:5000" -ForegroundColor Green +Write-Host "Druecke Ctrl+C zum Beenden" -ForegroundColor Gray +Write-Host "" + +# Flask Server starten +python app.py diff --git a/test-local-vision/static/favicon.png b/test-local-vision/static/favicon.png new file mode 100644 index 0000000..9d36f52 Binary files /dev/null and b/test-local-vision/static/favicon.png differ diff --git a/test-local-vision/static/poweron-logo.png b/test-local-vision/static/poweron-logo.png new file mode 100644 index 0000000..2a7aea3 Binary files /dev/null and b/test-local-vision/static/poweron-logo.png differ diff --git a/test-local-vision/t1.png b/test-local-vision/t1.png new file mode 100644 index 0000000..fc0618a Binary files /dev/null and b/test-local-vision/t1.png differ diff --git a/test-local-vision/templates/index.html b/test-local-vision/templates/index.html new file mode 100644 index 0000000..255a1e4 --- /dev/null +++ b/test-local-vision/templates/index.html @@ -0,0 +1,1258 @@ + + + + + + Belegscanner – PowerOn + + + + +
+
+ +

KI-gestützte Dokumentenanalyse

+
+
+ +
+ +
+ +
+
+ Dokument +
+
+
+
+
📤
+

Bild oder PDF

+

Drag & Drop

+
+ + +
+ +
+
+ + +
+
+ Einstellungen +
+
+
+ + + +
+
+ + +
+ + +
+
+ Prompt + +
+ +
+ +
+ +
+
+
+ + +
+
+ Extrahierte Daten + + + Warte auf Eingabe + +
+
+
+
+
🔍
+

Laden Sie ein Dokument hoch und klicken Sie auf "Analysieren"

+
+ + +
+
+
+
+ + +
+ + + +