feat: Refactor workflow result handling in prompts and analysis components
All checks were successful
Deploy Development / deploy (push) Successful in 53s
Build Test / pytest-backend (push) Successful in 8s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s

- Introduced a new utility function to streamline the extraction of user-facing content from aggregated workflow results.
- Updated backend prompt handling to utilize the new function for improved clarity and maintainability.
- Adjusted frontend analysis component to leverage the utility for consistent content display across different workflow result formats.

These changes enhance the overall user experience by ensuring more reliable and readable output from workflow executions.
This commit is contained in:
Lars 2026-04-11 12:04:35 +02:00
parent 300d96a9d8
commit d803f39de3
3 changed files with 76 additions and 20 deletions

View File

@ -31,6 +31,42 @@ OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "anthropic/claude-sonnet-4")
router = APIRouter(prefix="/api/prompts", tags=["prompts"])
# Metadaten-Schlüssel in workflow aggregate_results (nicht als „einziger“ Nutzer-Output)
_WORKFLOW_AGG_META_KEYS = frozenset({
"combined_analysis",
"all_signals",
"total_nodes",
"executed_nodes",
"failed_nodes",
"skipped_nodes",
})
def _workflow_user_facing_content(agg: object) -> str:
"""
Nutzer-sichtbarer Text wie im Admin WorkflowResultViewer (Final Output):
primär aggregated_result['analysis_core'], nicht das gesamte JSON.
"""
if agg is None:
return ""
if isinstance(agg, str):
return agg
if not isinstance(agg, dict):
return json.dumps(agg, ensure_ascii=False)
core = agg.get("analysis_core")
if isinstance(core, str) and core.strip():
return core
combined = agg.get("combined_analysis")
if isinstance(combined, str) and combined.strip():
return combined
non_meta = [k for k in agg.keys() if k not in _WORKFLOW_AGG_META_KEYS]
if len(non_meta) == 1:
v = agg[non_meta[0]]
if isinstance(v, str):
return v
return json.dumps(v, ensure_ascii=False)
return json.dumps(agg, ensure_ascii=False)
@router.get("")
def list_prompts(session: dict=Depends(require_auth)):
@ -1254,17 +1290,7 @@ async def execute_unified_prompt(
else:
content = json.dumps(final_output, ensure_ascii=False)
elif result['type'] == 'workflow':
# Graph-Workflows: kein "output", sondern aggregated_result
agg = result.get('aggregated_result')
if isinstance(agg, dict) and len(agg) == 1:
v = list(agg.values())[0]
content = v if isinstance(v, str) else json.dumps(v, ensure_ascii=False)
elif agg is None:
content = ''
elif isinstance(agg, str):
content = agg
else:
content = json.dumps(agg, ensure_ascii=False)
content = _workflow_user_facing_content(result.get('aggregated_result'))
else:
# For base prompts, use output directly
content = result.get('output', '')

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect, useMemo } from 'react'
import { Brain, Trash2, ChevronDown, ChevronUp } from 'lucide-react'
import { Link } from 'react-router-dom'
import { api } from '../utils/api'
import { getWorkflowDisplayContent } from '../utils/workflowDisplay'
import { useAuth } from '../context/AuthContext'
import Markdown from '../utils/Markdown'
import UsageBadge from '../components/UsageBadge'
@ -400,15 +401,7 @@ export default function Analysis() {
content = JSON.stringify(finalOutput, null, 2)
}
} else if (result.type === 'workflow') {
const agg = result.aggregated_result
if (agg != null && typeof agg === 'object' && !Array.isArray(agg) && Object.keys(agg).length === 1) {
const v = Object.values(agg)[0]
content = typeof v === 'string' ? v : JSON.stringify(v, null, 2)
} else if (typeof agg === 'string') {
content = agg
} else {
content = JSON.stringify(agg ?? {}, null, 2)
}
content = getWorkflowDisplayContent(result.aggregated_result)
} else {
// For base prompts, use output directly
content = typeof result.output === 'string' ? result.output : JSON.stringify(result.output, null, 2)

View File

@ -0,0 +1,37 @@
/**
* Nutzer-sichtbare Textausgabe eines Workflow-Laufs gleiche Logik wie
* WorkflowResultViewer (Final Output): primär aggregated_result.analysis_core.
*/
const AGG_META_KEYS = new Set([
'combined_analysis',
'all_signals',
'total_nodes',
'executed_nodes',
'failed_nodes',
'skipped_nodes',
])
export function getWorkflowDisplayContent(aggregatedResult) {
if (aggregatedResult == null) return ''
if (typeof aggregatedResult !== 'object' || Array.isArray(aggregatedResult)) {
return typeof aggregatedResult === 'string'
? aggregatedResult
: JSON.stringify(aggregatedResult, null, 2)
}
const core = aggregatedResult.analysis_core
if (typeof core === 'string' && core.trim() !== '') return core
const combined = aggregatedResult.combined_analysis
if (typeof combined === 'string' && combined.trim() !== '') return combined
const keys = Object.keys(aggregatedResult).filter((k) => !AGG_META_KEYS.has(k))
if (keys.length === 1) {
const v = aggregatedResult[keys[0]]
if (typeof v === 'string') return v
if (v != null && typeof v === 'object') return JSON.stringify(v, null, 2)
return String(v)
}
return JSON.stringify(aggregatedResult, null, 2)
}