From c7e94aea7959c023cb3cb9d9c56524a4f87a918b Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Thu, 30 Apr 2026 23:54:51 +0200 Subject: [PATCH] fixed nodes handovers --- src/api/workflowApi.ts | 16 +- .../nodes/frontendTypeRenderers/index.tsx | 3 +- src/pages/AutomationsDashboardPage.tsx | 190 ++++++++++++++---- 3 files changed, 168 insertions(+), 41 deletions(-) diff --git a/src/api/workflowApi.ts b/src/api/workflowApi.ts index e55d693..083cf3e 100644 --- a/src/api/workflowApi.ts +++ b/src/api/workflowApi.ts @@ -1026,12 +1026,16 @@ export interface WorkspaceRunDetail { nodeId: string; nodeType: string; status: string; + inputSnapshot?: Record; output?: Record; + inputFiles?: Array<{ id: string; fileName?: string }>; + outputFiles?: Array<{ id: string; fileName?: string }>; error?: string; startedAt?: number; completedAt?: number; durationMs?: number; tokensUsed?: number; + retryCount?: number; }>; files: Array<{ id: string; @@ -1039,10 +1043,14 @@ export interface WorkspaceRunDetail { contentType?: string; sizeBytes?: number; }>; + unassignedFiles?: Array<{ + id: string; + fileName?: string; + }>; } export async function fetchWorkspaceRuns( - request: ApiRequestOptions['request'], + request: ApiRequestFunction, params: { scope?: 'mine' | 'mandate'; status?: string; @@ -1061,14 +1069,14 @@ export async function fetchWorkspaceRuns( if (params.offset) query.set('offset', String(params.offset)); const qs = query.toString(); const url = `/api/automations/runs${qs ? `?${qs}` : ''}`; - const resp = await request({ url, method: 'GET' }); + const resp = await request({ url, method: 'get' }); return resp as { runs: WorkspaceRun[]; total: number }; } export async function fetchWorkspaceRunDetail( - request: ApiRequestOptions['request'], + request: ApiRequestFunction, runId: string, ): Promise { - const resp = await request({ url: `/api/automations/runs/${runId}/detail`, method: 'GET' }); + const resp = await request({ url: `/api/automations/runs/${runId}/detail`, method: 'get' }); return resp as WorkspaceRunDetail; } diff --git a/src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx b/src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx index 2654804..6da5d8d 100644 --- a/src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx +++ b/src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx @@ -34,6 +34,7 @@ import type { CanvasNode } from '../../editor/FlowCanvas'; import { DataRefRenderer } from './DataRefRenderer'; import { FeatureInstancePicker } from './FeatureInstancePicker'; import { TemplateTextareaRenderer } from './TemplateTextareaRenderer'; +import { getApiBaseUrl } from '../../../../../config/config'; const TextInput: React.FC = ({ param, value, onChange }) => (
@@ -747,7 +748,7 @@ const ModelMultiSelect: React.FC = ({ param, value, onChange React.useEffect(() => { let cancelled = false; setLoading(true); - fetch('/api/system/ai-models', { credentials: 'include' }) + fetch(`${getApiBaseUrl()}/api/system/ai-models`, { credentials: 'include' }) .then((r) => r.json()) .then((data) => { if (cancelled) return; diff --git a/src/pages/AutomationsDashboardPage.tsx b/src/pages/AutomationsDashboardPage.tsx index 5279219..4644873 100644 --- a/src/pages/AutomationsDashboardPage.tsx +++ b/src/pages/AutomationsDashboardPage.tsx @@ -1083,6 +1083,100 @@ const _WorkflowsTab: React.FC<_WorkflowsTabProps> = ({ onWorkflowClick }) => { // Workspace Tab (run detail only — no table) // =========================================================================== +const _FILE_REF_KEYS = new Set(['fileId', 'documentId', 'fileIds', 'documents']); + +function _isPlainObject(v: unknown): v is Record { + return typeof v === 'object' && v !== null && !Array.isArray(v); +} + +function _stripFileRefKeys(value: unknown): unknown { + if (_isPlainObject(value)) { + const out: Record = {}; + for (const [k, v] of Object.entries(value)) { + if (_FILE_REF_KEYS.has(k)) continue; + const stripped = _stripFileRefKeys(v); + if (stripped !== undefined) out[k] = stripped; + } + return Object.keys(out).length > 0 ? out : undefined; + } + if (Array.isArray(value)) { + const out = value.map((v) => _stripFileRefKeys(v)).filter((v) => v !== undefined); + return out.length > 0 ? out : undefined; + } + return value; +} + +function _formatScalar(v: unknown): string { + if (v === null || v === undefined) return '—'; + if (typeof v === 'string') return v; + if (typeof v === 'number' || typeof v === 'boolean') return String(v); + return JSON.stringify(v); +} + +const _DataBlock: React.FC<{ data: unknown; emptyHint?: string }> = ({ data, emptyHint }) => { + if (data === undefined || data === null) { + return emptyHint ?

{emptyHint}

: null; + } + + if (_isPlainObject(data)) { + const entries = Object.entries(data); + if (entries.length === 0) { + return emptyHint ?

{emptyHint}

: null; + } + return ( +
+ {entries.map(([k, v]) => { + const isComplex = _isPlainObject(v) || Array.isArray(v); + if (isComplex) { + return ( +
+ + {k} + +
+                  {JSON.stringify(v, null, 2)}
+                
+
+ ); + } + return ( +
+ {k} + {_formatScalar(v)} +
+ ); + })} +
+ ); + } + + return ( +
+      {JSON.stringify(data, null, 2)}
+    
+ ); +}; + +const _FileLinkList: React.FC<{ files: Array<{ id: string; fileName?: string }> }> = ({ files }) => { + if (!files.length) return null; + const baseUrl = api.defaults.baseURL || ''; + return ( +
+ {files.map((f) => ( + + + {f.fileName || f.id} + + ))} +
+ ); +}; + interface _WorkspaceTabProps { runId: string | null; onBack: () => void; @@ -1113,20 +1207,20 @@ const _WorkspaceTab: React.FC<_WorkspaceTabProps> = ({ runId, onBack }) => { if (!runId) { return ( -
+

{t('Wähle einen Run im Dashboard aus, um die Details anzuzeigen.')}

); } if (detailLoading || !runDetail) { - return

{t('Laden…')}

; + return

{t('Laden…')}

; } - const { run, steps, files, workflow } = runDetail; + const { run, steps, workflow, unassignedFiles } = runDetail; return ( -
+
@@ -1148,41 +1242,65 @@ const _WorkspaceTab: React.FC<_WorkspaceTabProps> = ({ runId, onBack }) => {

{t('Keine Schritte protokolliert.')}

) : (
- {steps.map((step) => ( -
- - - {step.status} - - {step.nodeType} ({step.nodeId}) - {step.durationMs != null && {step.durationMs}ms} - - {step.output && Object.keys(step.output).length > 0 && ( -
-                  {JSON.stringify(step.output, null, 2)}
-                
- )} - {step.error &&

{step.error}

} -
- ))} + {steps.map((step) => { + const inputData = _stripFileRefKeys(step.inputSnapshot ?? {}); + const outputData = _stripFileRefKeys(step.output ?? {}); + const inputFiles = step.inputFiles ?? []; + const outputFiles = step.outputFiles ?? []; + const hasInput = inputData !== undefined || inputFiles.length > 0; + const hasOutput = outputData !== undefined || outputFiles.length > 0; + return ( +
+ + + {step.status} + + {step.nodeType} ({step.nodeId}) + {step.durationMs != null && {step.durationMs}ms} + {(step.tokensUsed ?? 0) > 0 && {step.tokensUsed} tokens} + +
+ {hasInput && ( +
+
+ {t('Input')} +
+ <_DataBlock data={inputData} /> + <_FileLinkList files={inputFiles} /> +
+ )} + {hasOutput && ( +
+
+ {t('Output')} +
+ <_DataBlock data={outputData} /> + <_FileLinkList files={outputFiles} /> +
+ )} + {step.error && ( +
+
+ {t('Fehler')} +
+

{step.error}

+
+ )} +
+ {step.startedAt && {t('Start')}: {_formatTs(step.startedAt)}} + {step.completedAt && {t('Ende')}: {_formatTs(step.completedAt)}} + {(step.retryCount ?? 0) > 0 && {t('Wiederholungen')}: {step.retryCount}} +
+
+
+ ); + })}
)} - {files.length > 0 && ( + {unassignedFiles && unassignedFiles.length > 0 && ( <> -

{t('Dokumente')}

-
- {files.map((f) => ( - - - {f.fileName || f.id} - - ))} -
+

{t('Sonstige Dokumente')}

+ <_FileLinkList files={unassignedFiles} /> )}