Fix mixed content: route all requests through backend API
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3527e5b135
commit
11c9b64e14
2 changed files with 44 additions and 53 deletions
21
app.py
21
app.py
|
|
@ -475,7 +475,7 @@ async def _listModels(authenticated: bool = Depends(_verifyApiKey)):
|
||||||
return models
|
return models
|
||||||
|
|
||||||
@app.get("/api/ollama/status", response_model=OllamaStatusResponse, tags=["System"])
|
@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."""
|
"""Check Ollama connection status and list available models."""
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
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"])
|
@app.post("/api/analyze", response_model=AnalyzeResponse, tags=["AI"])
|
||||||
async def _analyzeDocument(
|
async def _analyzeDocument(
|
||||||
request: AnalyzeRequest,
|
request: AnalyzeRequest,
|
||||||
apiKey: str = Depends(_checkRateLimit)
|
xApiKey: Optional[str] = Header(None, alias="X-API-Key")
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Analyze a document with AI Vision API.
|
Analyze a document with AI Vision API.
|
||||||
|
|
||||||
Supports both vision models (with images) and text models (without images).
|
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:
|
try:
|
||||||
# Get internal model name
|
# Get internal model name
|
||||||
internalModelName = _getInternalModelName(request.modelName)
|
internalModelName = _getInternalModelName(request.modelName)
|
||||||
|
|
|
||||||
|
|
@ -681,7 +681,7 @@
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<label>Server:</label>
|
<label>Server:</label>
|
||||||
<input type="text" id="ollama-url" value="http://83.228.200.109:11434" placeholder="http://83.228.200.109:11434">
|
<input type="text" id="ollama-url" value="" placeholder="Backend API (automatisch)" disabled style="opacity:0.6;">
|
||||||
<button class="btn btn-secondary btn-small" id="check-ollama">Prüfen</button>
|
<button class="btn btn-secondary btn-small" id="check-ollama">Prüfen</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
|
|
@ -838,18 +838,23 @@ Falls ein Feld nicht erkennbar ist, setze den Wert auf null.</textarea>
|
||||||
ollamaStatusDiv.textContent = 'Prüfe Ollama-Verbindung...';
|
ollamaStatusDiv.textContent = 'Prüfe Ollama-Verbindung...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Direkt Ollama API abfragen (ohne Auth)
|
// Backend API abfragen (geht über HTTPS)
|
||||||
const response = await fetch(`${ollamaUrl.value}/api/tags`, {
|
const response = await fetch('/api/ollama/status', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { 'Accept': 'application/json' }
|
headers: { 'Accept': 'application/json' }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
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 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);
|
console.log('Available Ollama models:', availableModels);
|
||||||
|
|
||||||
|
|
@ -1168,39 +1173,19 @@ Falls ein Feld nicht erkennbar ist, setze den Wert auf null.</textarea>
|
||||||
_hideError();
|
_hideError();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get Ollama model name from PowerOn name
|
// Request-Body für Backend API erstellen
|
||||||
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
|
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
model: ollamaModelName,
|
modelName: modelName.value, // PowerOn model name
|
||||||
prompt: promptInput.value,
|
prompt: promptInput.value,
|
||||||
stream: false,
|
imageBase64: currentImageBase64 || null
|
||||||
options: {
|
|
||||||
num_ctx: numCtx
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Sending request to:', `${ollamaUrl.value}/api/generate`);
|
console.log('Sending request to: /api/analyze');
|
||||||
console.log('Request body (without prompt):', { ...requestBody, prompt: '[TRUNCATED]' });
|
console.log('Request body (without image):', { ...requestBody, imageBase64: requestBody.imageBase64 ? '[BASE64_IMAGE]' : null });
|
||||||
|
|
||||||
// Bild nur hinzufügen wenn vorhanden
|
// Call Backend API (geht über HTTPS)
|
||||||
if (currentImageBase64) {
|
console.log('Fetching from Backend...');
|
||||||
requestBody.images = [currentImageBase64];
|
const response = await fetch('/api/analyze', {
|
||||||
}
|
|
||||||
|
|
||||||
// Call Ollama API directly
|
|
||||||
console.log('Fetching from Ollama...');
|
|
||||||
const response = await fetch(`${ollamaUrl.value}/api/generate`, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(requestBody)
|
body: JSON.stringify(requestBody)
|
||||||
|
|
@ -1210,30 +1195,19 @@ Falls ein Feld nicht erkennbar ist, setze den Wert auf null.</textarea>
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
console.error('Ollama error:', errorText);
|
console.error('Backend error:', errorText);
|
||||||
throw new Error(`Ollama Fehler: ${response.status} - ${errorText.substring(0, 200)}`);
|
throw new Error(`Backend Fehler: ${response.status} - ${errorText.substring(0, 200)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
console.log('Ollama response received:', result);
|
console.log('Backend response received:', result);
|
||||||
const responseText = result.response || '';
|
|
||||||
|
|
||||||
// Try to extract JSON from response
|
if (!result.success) {
|
||||||
let extractedData = null;
|
throw new Error(result.error || 'Analyse fehlgeschlagen');
|
||||||
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
|
||||||
|
|
||||||
if (jsonMatch) {
|
|
||||||
try {
|
|
||||||
extractedData = JSON.parse(jsonMatch[0]);
|
|
||||||
} catch (e) {
|
|
||||||
extractedData = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap plain text response in JSON object
|
const responseText = result.rawResponse || '';
|
||||||
if (extractedData === null) {
|
const extractedData = result.data || { response: responseText.trim() };
|
||||||
extractedData = { response: responseText.trim() };
|
|
||||||
}
|
|
||||||
|
|
||||||
_displayResults(extractedData, responseText);
|
_displayResults(extractedData, responseText);
|
||||||
_setStatus('success', 'Erfolgreich extrahiert');
|
_setStatus('success', 'Erfolgreich extrahiert');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue