diff --git a/backend/routers/prompts.py b/backend/routers/prompts.py index cc90c5c..5e7d76f 100644 --- a/backend/routers/prompts.py +++ b/backend/routers/prompts.py @@ -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', '') diff --git a/frontend/src/pages/Analysis.jsx b/frontend/src/pages/Analysis.jsx index cea6271..c8f8317 100644 --- a/frontend/src/pages/Analysis.jsx +++ b/frontend/src/pages/Analysis.jsx @@ -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) diff --git a/frontend/src/utils/workflowDisplay.js b/frontend/src/utils/workflowDisplay.js new file mode 100644 index 0000000..ff971ca --- /dev/null +++ b/frontend/src/utils/workflowDisplay.js @@ -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) +}