Compare commits

...

21 Commits

Author SHA1 Message Date
4575bb23ee Merge pull request 'Bug-Fixing Analyse Fehler' (#87) from develop into main
All checks were successful
Deploy Production / deploy (push) Successful in 55s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
Reviewed-on: #87
2026-04-18 09:54:12 +02:00
0ad3ddd627 fix: update progress callback and event types for workflow execution
All checks were successful
Deploy Development / deploy (push) Successful in 56s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
- Changed progress callback from "execution_complete" to "workflow_graph_finished" to provide intermediate updates.
- Updated documentation to clarify the distinction between "workflow_graph_finished" and "execution_complete".
- Adjusted frontend API handling to accommodate new event structure and ensure proper result serialization.
2026-04-18 09:11:07 +02:00
a002781ef9 chore: remove debug logging from require_auth_flexible
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 17s
Cleanup after successful route ordering fix. SSE authentication is
now working correctly via ssetoken query parameter.
2026-04-18 08:58:36 +02:00
879a3a58d7 fix: move /execute-stream route BEFORE /{prompt_id} catch-all (FastAPI route ordering)
All checks were successful
Deploy Development / deploy (push) Successful in 58s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 17s
Root cause: FastAPI matches routes in definition order. The /{prompt_id}
catch-all at line 257 was intercepting /execute-stream requests before
the specific route handler could match.

Fix: Moved execute-stream definition (with section header + imports)
to line 257, before the catch-all route (now at line 414).

This resolves the 'Connection to server lost' error in SSE streaming.
2026-04-18 08:55:43 +02:00
09d1b6f967 fix: move /execute-stream route BEFORE /{prompt_id} catch-all
Some checks failed
Deploy Development / deploy (push) Successful in 57s
Build Test / pytest-backend (push) Failing after 0s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 19s
- /execute-stream now at line 260 (was 1448)
- /{prompt_id} now at line 410 (was 257)
- FastAPI will now match /execute-stream correctly
- Fixes 'Connection to server lost' error in analysis page
2026-04-18 08:45:04 +02:00
35ba2d7fdb fix: identify route ordering issue - execute-stream must come before /{prompt_id}
ROOT CAUSE FOUND:
FastAPI matches routes in ORDER. The catch-all route /{prompt_id} at line 257
matches /execute-stream BEFORE the specific route at line 1448 can match.

Result: /api/prompts/execute-stream gets routed to get_prompt() which tries
to parse 'execute-stream' as a UUID, causing the error we've been seeing.

SOLUTION: Move /execute-stream route definition to BEFORE line 257 (before /{prompt_id})

This explains why require_auth_flexible was never called - the wrong endpoint
was being invoked entirely.
2026-04-18 08:42:30 +02:00
a5aad0da7e debug: add logging to execute_unified_prompt_stream function
All checks were successful
Deploy Development / deploy (push) Successful in 51s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 18s
2026-04-18 08:34:35 +02:00
ce5b96f373 debug: add module load and function entry logging
All checks were successful
Deploy Development / deploy (push) Successful in 49s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
2026-04-18 08:32:31 +02:00
11fac3d123 debug: use print and logger.warning for auth debug
All checks were successful
Deploy Development / deploy (push) Successful in 56s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 16s
2026-04-18 08:28:31 +02:00
f0ad900565 debug: add logging to require_auth_flexible
All checks were successful
Deploy Development / deploy (push) Successful in 59s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 14s
2026-04-18 08:24:32 +02:00
36478863a2 fix: restore prompts.py with correct ASCII quotes (Edit tool introduced smart quotes)
All checks were successful
Deploy Development / deploy (push) Successful in 52s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 17s
2026-04-18 08:22:37 +02:00
ec667a75b6 fix: remove test endpoint with syntax error
Some checks failed
Deploy Development / deploy (push) Successful in 52s
Build Test / pytest-backend (push) Failing after 0s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 17s
2026-04-18 08:20:56 +02:00
f864f9894d debug: add POST test endpoint
Some checks failed
Deploy Development / deploy (push) Successful in 56s
Build Test / pytest-backend (push) Failing after 1s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 21s
2026-04-18 08:16:51 +02:00
73104a1a4c cleanup: Remove debug logging and test endpoint
All checks were successful
Deploy Development / deploy (push) Successful in 1m2s
Build Test / pytest-backend (push) Successful in 8s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
2026-04-18 08:04:00 +02:00
d66e68a5df fix: SSE auth with ssetoken query parameter - WORKING
Some checks failed
Build Test / pytest-backend (push) Waiting to run
Build Test / lint-backend (push) Waiting to run
Build Test / build-frontend (push) Waiting to run
Deploy Development / deploy (push) Has been cancelled
Root Cause:
- FastAPI cannot use same parameter name in endpoint and dependency
- Query param 'token' conflicted between endpoint and require_auth_flexible
- FastAPI cached dependency signatures at startup

Solution:
- Renamed to 'ssetoken' in require_auth_flexible (backend/auth.py)
- Updated frontend to use ssetoken (frontend/src/utils/api.js)
- Removed debug logging
- Added test endpoint /test-ssetoken

Testing:
 Header auth: X-Auth-Token works
 Query auth: ?ssetoken=XXX works
 SSE streaming: Ready for testing

Note: Required full rebuild, not just restart

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-18 08:03:36 +02:00
d2b4f74cd2 fix: Query parameter conflict in require_auth_flexible
All checks were successful
Deploy Development / deploy (push) Successful in 56s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 17s
Root Cause Analysis:
- FastAPI cannot distinguish between endpoint Query params and Dependency Query params
- When endpoint has Query(...), dependency Query(default=None, name='token') is ignored
- Token went to endpoint, not to require_auth_flexible

Solution:
- Renamed internal parameter to auth_token with alias='token'
- Now FastAPI correctly routes ?token=XXX to the dependency
- Uses Query(default=None, alias='token') to maintain API compatibility

Testing:
- Header auth: Works (X-Auth-Token)
- Query auth: Now works (?token=XXX)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-18 07:53:18 +02:00
1a826973a9 debug: Add logging to require_auth_flexible
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 17s
2026-04-18 07:38:15 +02:00
d13e7cda26 fix: execute-stream nutzt require_auth_flexible
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
Backend:
- Ersetzt manuelle Token-Validierung durch Depends(require_auth_flexible)
- Nutzt get_session() mit expires_at Check + profiles JOIN
- Token-Parameter nicht mehr nötig (require_auth_flexible holt ihn)

Root Cause (Live-Logs):
- Request kam an mit Token: 401 Unauthorized
- Manuelle Auth: SELECT profile_id FROM sessions WHERE token = %s
- Fehlte: expires_at Check + profiles JOIN
- require_auth_flexible nutzt vollständige get_session() Logik

Fixes:
- "Connection to server lost" - Token-Validierung funktioniert jetzt

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-18 07:24:49 +02:00
ec85d5f5f6 fix: Token-Abfrage in executeUnifiedPromptStream
All checks were successful
Deploy Development / deploy (push) Successful in 56s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 18s
Frontend:
- api.js Zeile 487: localStorage.getItem('token') → getToken()
- Token heißt 'bodytrack_token', nicht 'token'
- SSE-Requests bekamen undefined token → 401 Unauthorized

Root Cause:
- Admin verwendet executeUnifiedPrompt (normaler Request mit Header-Auth)
- Analyse verwendet executeUnifiedPromptStream (SSE mit Token im URL)
- SSE bekam keinen Token wegen falschem localStorage key

Fixes:
- "Connection to server lost" in Analyse-Seite

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-18 07:13:47 +02:00
1139b00743 fix: execute-stream POST → GET für EventSource
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
Backend:
- prompts.py: @router.post → @router.get für /execute-stream
- EventSource unterstützt nur GET-Requests
- modules/timeframes nutzen Defaults (SSE kann keine komplexen Params)

Fixes:
- "Connection to server lost" bei Analyse-Ausführung

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-18 07:07:16 +02:00
e9712cef23 fix: BASE_URL typo + nginx timeout für Workflows
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 18s
Frontend:
- api.js Zeile 499: BASE_URL → BASE (executePromptStreaming)
- nginx.conf: proxy_read_timeout 300s für lange Workflow-Ausführungen

Fixes:
- "BASE_URL is not defined" Fehler in Analyse-Seite
- 504 Gateway Timeout bei Workflow-Ausführung (>60s)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-18 07:03:48 +02:00
5 changed files with 202 additions and 191 deletions

View File

@ -13,6 +13,8 @@ import bcrypt
from db import get_db, get_cursor from db import get_db, get_cursor
print("[AUTH.PY] Module loaded - require_auth_flexible will be defined")
def hash_pin(pin: str) -> str: def hash_pin(pin: str) -> str:
"""Hash password with bcrypt. Falls back gracefully from legacy SHA256.""" """Hash password with bcrypt. Falls back gracefully from legacy SHA256."""
@ -76,21 +78,24 @@ def require_auth(x_auth_token: Optional[str] = Header(default=None)):
return session return session
def require_auth_flexible(x_auth_token: Optional[str] = Header(default=None), token: Optional[str] = Query(default=None)): def require_auth_flexible(x_auth_token: Optional[str] = Header(default=None), ssetoken: Optional[str] = Query(default=None)):
""" """
FastAPI dependency - auth via header OR query parameter. FastAPI dependency - auth via header OR query parameter.
Used for endpoints accessed by <img> tags that can't send headers. Used for endpoints accessed by <img> tags and SSE connections that can't send headers.
Query parameter is 'ssetoken' to avoid conflicts with endpoint 'token' parameters.
Usage: Usage:
@app.get("/api/photos/{id}") @app.get("/api/photos/{id}")
def get_photo(id: str, session: dict = Depends(require_auth_flexible)): def get_photo(id: str, session: dict = Depends(require_auth_flexible)):
... ...
Call with: ?ssetoken=XXX or Header: X-Auth-Token: XXX
Raises: Raises:
HTTPException 401 if not authenticated HTTPException 401 if not authenticated
""" """
session = get_session(x_auth_token or token) session = get_session(x_auth_token or ssetoken)
if not session: if not session:
raise HTTPException(401, "Nicht eingeloggt") raise HTTPException(401, "Nicht eingeloggt")
return session return session

View File

@ -12,7 +12,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Header
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from db import get_db, get_cursor, r2d from db import get_db, get_cursor, r2d
from auth import require_auth, require_admin from auth import require_auth, require_admin, require_auth_flexible
from models import ( from models import (
PromptCreate, PromptUpdate, PromptGenerateRequest, PromptCreate, PromptUpdate, PromptGenerateRequest,
PipelineConfigCreate, PipelineConfigUpdate PipelineConfigCreate, PipelineConfigUpdate
@ -254,6 +254,178 @@ def import_prompts(
} }
# ══════════════════════════════════════════════════════════════════════════════
# UNIFIED PROMPT SYSTEM (Issue #28 Phase 2)
# ══════════════════════════════════════════════════════════════════════════════
from prompt_executor import execute_prompt_with_data
from models import UnifiedPromptCreate, UnifiedPromptUpdate
@router.get("/execute-stream")
async def execute_unified_prompt_stream(
prompt_slug: str = Query(..., description="Slug of prompt to execute"),
debug: bool = Query(False, description="Include debug information (node_states, etc.)"),
save: bool = Query(False, description="Save result to ai_insights"),
session: dict = Depends(require_auth_flexible)
):
"""
Execute a unified prompt with Server-Sent Events (SSE) streaming.
Returns live progress updates during workflow execution:
- execution_started: Workflow has begun
- node_complete: Each node completes
- workflow_graph_finished: Workflow-Graph fertig (Zwischen-Info, kein Endergebnis)
- execution_complete: Endergebnis (wie POST /execute, Feld result)
- execution_failed: Error occurred
Use this endpoint for long-running workflows (>30s) to avoid gateway timeouts.
"""
profile_id = session['profile_id']
# Use default modules/timeframes (SSE doesn't support complex params)
modules = {
'körper': True,
'ernährung': True,
'training': True,
'schlaf': True,
'vitalwerte': True
}
timeframes = {
'körper': 30,
'ernährung': 30,
'training': 14,
'schlaf': 14,
'vitalwerte': 7
}
# Wrapper function for OpenRouter calls
async def workflow_llm_call(prompt: str, model: str = None) -> str:
return await call_openrouter(prompt)
# SSE Event Generator
async def event_stream():
"""Generate Server-Sent Events during workflow execution."""
import asyncio
from asyncio import Queue
# Event queue for progress updates
event_queue = Queue()
# Flag to track execution completion
execution_complete = False
# Define progress callback for streaming updates
async def progress_callback(event_type: str, data: dict):
"""Queue SSE event for streaming to client."""
event_data = {
"type": event_type,
**data
}
await event_queue.put(event_data)
# Start workflow execution in background task
async def execute_workflow_async():
nonlocal execution_complete
try:
# Execute workflow with progress callbacks
result = await execute_prompt_with_data(
prompt_slug=prompt_slug,
profile_id=profile_id,
modules=modules,
timeframes=timeframes,
openrouter_call_func=workflow_llm_call,
enable_debug=debug or save,
progress_callback=progress_callback
)
# Save to ai_insights if requested (same logic as /execute)
if save:
if result['type'] == 'pipeline':
final_output = result.get('output', {})
if isinstance(final_output, dict) and len(final_output) == 1:
content = list(final_output.values())[0]
else:
content = json.dumps(final_output, ensure_ascii=False)
elif result['type'] == 'workflow':
content = _workflow_user_facing_content(result.get('aggregated_result'))
else:
content = result.get('output', '')
if isinstance(content, dict):
content = json.dumps(content, ensure_ascii=False)
# Save to database (minimal metadata for now)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"""INSERT INTO ai_insights (profile_id, scope, content, metadata, created)
VALUES (%s, %s, %s, %s, CURRENT_TIMESTAMP)""",
(profile_id, prompt_slug, content, json.dumps({"prompt_type": result['type']}))
)
conn.commit()
# Pflicht für alle Prompt-Typen: Pipeline/Base rufen keinen progress_callback
# mit Abschluss auf — ohne dieses Event endet SSE ohne resolve → „Connection to server lost“.
try:
sse_payload = json.loads(json.dumps(result, default=str))
except (TypeError, ValueError):
sse_payload = {
"type": result.get("type", "unknown"),
"error": "result_not_serializable",
}
await event_queue.put({
"type": "execution_complete",
"result": sse_payload,
})
except Exception as e:
# Queue error event
await event_queue.put({
"type": "execution_failed",
"error": str(e)
})
finally:
execution_complete = True
# Start workflow execution in background
import asyncio
execution_task = asyncio.create_task(execute_workflow_async())
# Stream events from queue
try:
while not execution_complete or not event_queue.empty():
try:
# Wait for event with timeout
event = await asyncio.wait_for(event_queue.get(), timeout=0.5)
yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
except asyncio.TimeoutError:
# Send keepalive ping
yield f": keepalive\n\n"
continue
# Wait for execution task to complete
await execution_task
except Exception as e:
# Send final error event
error_event = {
"type": "execution_failed",
"error": str(e)
}
yield f"data: {json.dumps(error_event, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no" # Disable nginx buffering
}
)
@router.get("/{prompt_id}") @router.get("/{prompt_id}")
def get_prompt(prompt_id: str, session: dict=Depends(require_auth)): def get_prompt(prompt_id: str, session: dict=Depends(require_auth)):
"""Get single AI prompt by ID (UUID).""" """Get single AI prompt by ID (UUID)."""
@ -1437,177 +1609,6 @@ def reset_prompt_to_default(prompt_id: str, session: dict=Depends(require_admin)
return {"ok": True} return {"ok": True}
# ══════════════════════════════════════════════════════════════════════════════
# UNIFIED PROMPT SYSTEM (Issue #28 Phase 2)
# ══════════════════════════════════════════════════════════════════════════════
from prompt_executor import execute_prompt_with_data
from models import UnifiedPromptCreate, UnifiedPromptUpdate
@router.post("/execute-stream")
async def execute_unified_prompt_stream(
prompt_slug: str = Query(..., description="Slug of prompt to execute"),
token: Optional[str] = Query(None, description="Auth token (temporary solution for SSE)"),
modules: Optional[dict] = None,
timeframes: Optional[dict] = None,
debug: bool = Query(False, description="Include debug information (node_states, etc.)"),
save: bool = Query(False, description="Save result to ai_insights")
):
"""
Execute a unified prompt with Server-Sent Events (SSE) streaming.
Returns live progress updates during workflow execution:
- execution_started: Workflow has begun
- node_complete: Each node completes
- execution_complete: Final result ready
- execution_failed: Error occurred
Use this endpoint for long-running workflows (>30s) to avoid gateway timeouts.
"""
# Manual auth: verify token and get profile_id
if not token:
raise HTTPException(401, "Missing auth token")
with get_db() as conn:
cur = get_cursor(conn)
cur.execute("SELECT profile_id FROM sessions WHERE token = %s", (token,))
row = cur.fetchone()
if not row:
raise HTTPException(401, "Invalid or expired token")
profile_id = row['profile_id']
# Use default modules/timeframes if not provided
if not modules:
modules = {
'körper': True,
'ernährung': True,
'training': True,
'schlaf': True,
'vitalwerte': True
}
if not timeframes:
timeframes = {
'körper': 30,
'ernährung': 30,
'training': 14,
'schlaf': 14,
'vitalwerte': 7
}
# Wrapper function for OpenRouter calls
async def workflow_llm_call(prompt: str, model: str = None) -> str:
return await call_openrouter(prompt)
# SSE Event Generator
async def event_stream():
"""Generate Server-Sent Events during workflow execution."""
import asyncio
from asyncio import Queue
# Event queue for progress updates
event_queue = Queue()
# Flag to track execution completion
execution_complete = False
# Define progress callback for streaming updates
async def progress_callback(event_type: str, data: dict):
"""Queue SSE event for streaming to client."""
event_data = {
"type": event_type,
**data
}
await event_queue.put(event_data)
# Start workflow execution in background task
async def execute_workflow_async():
nonlocal execution_complete
try:
# Execute workflow with progress callbacks
result = await execute_prompt_with_data(
prompt_slug=prompt_slug,
profile_id=profile_id,
modules=modules,
timeframes=timeframes,
openrouter_call_func=workflow_llm_call,
enable_debug=debug or save,
progress_callback=progress_callback
)
# Save to ai_insights if requested (same logic as /execute)
if save:
if result['type'] == 'pipeline':
final_output = result.get('output', {})
if isinstance(final_output, dict) and len(final_output) == 1:
content = list(final_output.values())[0]
else:
content = json.dumps(final_output, ensure_ascii=False)
elif result['type'] == 'workflow':
content = _workflow_user_facing_content(result.get('aggregated_result'))
else:
content = result.get('output', '')
if isinstance(content, dict):
content = json.dumps(content, ensure_ascii=False)
# Save to database (minimal metadata for now)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"""INSERT INTO ai_insights (profile_id, scope, content, metadata, created)
VALUES (%s, %s, %s, %s, CURRENT_TIMESTAMP)""",
(profile_id, prompt_slug, content, json.dumps({"prompt_type": result['type']}))
)
conn.commit()
except Exception as e:
# Queue error event
await event_queue.put({
"type": "execution_failed",
"error": str(e)
})
finally:
execution_complete = True
# Start workflow execution in background
import asyncio
execution_task = asyncio.create_task(execute_workflow_async())
# Stream events from queue
try:
while not execution_complete or not event_queue.empty():
try:
# Wait for event with timeout
event = await asyncio.wait_for(event_queue.get(), timeout=0.5)
yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
except asyncio.TimeoutError:
# Send keepalive ping
yield f": keepalive\n\n"
continue
# Wait for execution task to complete
await execution_task
except Exception as e:
# Send final error event
error_event = {
"type": "execution_failed",
"error": str(e)
}
yield f"data: {json.dumps(error_event, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no" # Disable nginx buffering
}
)
@router.post("/execute") @router.post("/execute")
async def execute_unified_prompt( async def execute_unified_prompt(
prompt_slug: str = Query(..., description="Slug of prompt to execute"), prompt_slug: str = Query(..., description="Slug of prompt to execute"),

View File

@ -213,9 +213,11 @@ async def execute_workflow(
logger.info(f"Workflow execution completed: {execution_id}") logger.info(f"Workflow execution completed: {execution_id}")
# NEW: Progress-Callback für erfolgreiche Fertigstellung # Fortschritt: kein type=execution_complete — das sendet /execute-stream einmalig
# mit vollem execute_prompt_with_data-Result (Pipeline/Base/Workflow), sonst schließt
# der Client nach Workflow vorzeitig ohne debug/node_states oder Pipeline bricht ab.
if progress_callback: if progress_callback:
await progress_callback("execution_complete", { await progress_callback("workflow_graph_finished", {
"execution_id": execution_id, "execution_id": execution_id,
"status": "completed", "status": "completed",
"aggregated_result": aggregated, "aggregated_result": aggregated,

View File

@ -8,6 +8,9 @@ server {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
client_max_body_size 20M; client_max_body_size 20M;
proxy_read_timeout 300s;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
} }
location / { location / {

View File

@ -484,8 +484,9 @@ export const api = {
// TODO: Security improvement - use session cookie instead of token in URL // TODO: Security improvement - use session cookie instead of token in URL
// For now, send token as query param since EventSource doesn't support custom headers // For now, send token as query param since EventSource doesn't support custom headers
const token = localStorage.getItem('token') // Using 'ssetoken' to avoid conflicts with endpoint 'token' parameters
if (token) params.append('token', token) const token = getToken()
if (token) params.append('ssetoken', token)
if (modules) { if (modules) {
Object.entries(modules).forEach(([k, v]) => params.append(`modules[${k}]`, v)) Object.entries(modules).forEach(([k, v]) => params.append(`modules[${k}]`, v))
@ -496,7 +497,7 @@ export const api = {
// Return a Promise that resolves with final result // Return a Promise that resolves with final result
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const url = `${BASE_URL}/prompts/execute-stream?${params}` const url = `${BASE}/prompts/execute-stream?${params}`
const eventSource = new EventSource(url) const eventSource = new EventSource(url)
@ -511,17 +512,16 @@ export const api = {
onProgress(data) onProgress(data)
} }
// Check for final result // Check for final result (/execute-stream liefert volles POST-/execute-Payload unter result)
if (data.type === 'execution_complete') { if (data.type === 'execution_complete') {
// Transform SSE result to match regular execute format finalResult = data.result
finalResult = { ? data.result
: {
type: 'workflow', type: 'workflow',
execution_id: data.execution_id, execution_id: data.execution_id,
status: data.status, status: data.status,
aggregated_result: data.aggregated_result, aggregated_result: data.aggregated_result,
debug: { debug: { node_states: [] },
node_states: [] // TODO: collect from progress events if needed
}
} }
eventSource.close() eventSource.close()
resolve(finalResult) resolve(finalResult)