Compare commits

..

6 commits

489 changed files with 22936 additions and 6208 deletions

5
.gitignore vendored
View file

@ -31,7 +31,4 @@ dist-ssr
.cursorignore .cursorignore
# Keep environment files in config/ (naming: env-<workflow>.env) # Keep environment files in config/ (naming: env-<workflow>.env)
!config/env-*.env !config/env-*.env
tsc-errors.txt
scripts/i18n_missing_report.md

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Configuration reads mandatory env vars set by .env (copied from config/env-*.env by CI). * Configuration reads mandatory env vars set by .env (copied from config/env-*.env by CI).
* *

View file

@ -1,3 +1 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { getApiBaseUrl, getAppName } from './config'; export { getApiBaseUrl, getAppName } from './config';

2
env.d.ts vendored
View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv { interface ImportMetaEnv {

15027
scripts/i18n_missing_report.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* App.tsx * App.tsx
* *
@ -41,11 +39,11 @@ import { GDPRPage } from './pages/GDPR';
import StorePage from './pages/Store'; import StorePage from './pages/Store';
import { IntegrationsOverviewPage } from './pages/IntegrationsOverviewPage'; import { IntegrationsOverviewPage } from './pages/IntegrationsOverviewPage';
import { FeatureViewPage } from './pages/FeatureView'; import { FeatureViewPage } from './pages/FeatureView';
import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminLogsPage, AdminDemoConfigPage } from './pages/admin'; import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminUserRoleTemplatesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminUserAccessOverviewPage, AdminLogsPage, AdminDemoConfigPage } from './pages/admin';
import { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards'; import { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards';
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata'; import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing'; import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing';
import { WorkflowAutomationPage } from './pages/workflowAutomation/WorkflowAutomationHubPage'; import { AutomationsDashboardPage } from './pages/AutomationsDashboardPage';
import { RagInventoryPage } from './pages/RagInventoryPage'; import { RagInventoryPage } from './pages/RagInventoryPage';
import { ComplianceAuditPage } from './pages/ComplianceAuditPage'; import { ComplianceAuditPage } from './pages/ComplianceAuditPage';
function App() { function App() {
@ -126,9 +124,9 @@ function App() {
</Route> </Route>
{/* ============================================== */} {/* ============================================== */}
{/* WORKFLOW AUTOMATION (System-Komponente) */} {/* AUTOMATIONS DASHBOARD */}
{/* ============================================== */} {/* ============================================== */}
<Route path="workflow-automation" element={<WorkflowAutomationPage />} /> <Route path="automations" element={<AutomationsDashboardPage />} />
{/* ============================================== */} {/* ============================================== */}
{/* RAG INVENTORY */} {/* RAG INVENTORY */}
@ -139,7 +137,6 @@ function App() {
<Route path="pek" element={<Navigate to="/" replace />} /> <Route path="pek" element={<Navigate to="/" replace />} />
<Route path="speech" element={<Navigate to="/" replace />} /> <Route path="speech" element={<Navigate to="/" replace />} />
{/* ============================================== */} {/* ============================================== */}
{/* FEATURE-INSTANZ ROUTES */} {/* FEATURE-INSTANZ ROUTES */}
{/* /mandates/:mandateId/:featureCode/:instanceId */} {/* /mandates/:mandateId/:featureCode/:instanceId */}
@ -173,9 +170,13 @@ function App() {
<Route path="templates" element={<FeatureViewPage view="templates" />} /> <Route path="templates" element={<FeatureViewPage view="templates" />} />
<Route path="logs" element={<FeatureViewPage view="logs" />} /> <Route path="logs" element={<FeatureViewPage view="logs" />} />
{/* Workspace Editor */} {/* Workspace + Automation2 Editor */}
<Route path="editor" element={<FeatureViewPage view="editor" />} /> <Route path="editor" element={<FeatureViewPage view="editor" />} />
{/* Automation2: legacy workflows URL → editor */}
<Route path="workflows" element={<Navigate to="../editor" replace />} />
<Route path="workflows-tasks" element={<FeatureViewPage view="workflows-tasks" />} />
{/* Teams Bot Feature Views */} {/* Teams Bot Feature Views */}
<Route path="sessions" element={<FeatureViewPage view="sessions" />} /> <Route path="sessions" element={<FeatureViewPage view="sessions" />} />
<Route path="settings" element={<FeatureViewPage view="settings" />} /> <Route path="settings" element={<FeatureViewPage view="settings" />} />
@ -205,14 +206,14 @@ function App() {
<Route index element={<Navigate to="/admin/access" replace />} /> <Route index element={<Navigate to="/admin/access" replace />} />
<Route path="mandates" element={<AdminMandatesPage />} /> <Route path="mandates" element={<AdminMandatesPage />} />
<Route path="users" element={<AdminUsersPage />} /> <Route path="users" element={<AdminUsersPage />} />
<Route path="user-mandates" element={<AdminUserMandatesPage />} /> <Route path="user-mandates" element={<Navigate to="/admin/mandates" replace />} />
<Route path="access" element={<AccessManagementHub />} /> <Route path="access" element={<AccessManagementHub />} />
<Route path="feature-instances" element={<AdminFeatureAccessPage />} /> <Route path="feature-instances" element={<AdminFeatureAccessPage />} />
<Route path="feature-roles" element={<AdminFeatureRolesPage />} /> <Route path="feature-roles" element={<AdminFeatureRolesPage />} />
<Route path="feature-users" element={<AdminFeatureInstanceUsersPage />} /> <Route path="feature-users" element={<AdminFeatureInstanceUsersPage />} />
<Route path="invitations" element={<AdminInvitationsPage />} /> <Route path="invitations" element={<AdminInvitationsPage />} />
<Route path="mandate-roles" element={<AdminMandateRolesPage />} /> <Route path="user-role-templates" element={<AdminUserRoleTemplatesPage />} />
<Route path="mandate-role-permissions" element={<AdminMandateRolePermissionsPage />} /> <Route path="mandate-roles" element={<Navigate to="/admin/user-role-templates" replace />} />
<Route path="user-access-overview" element={<AdminUserAccessOverviewPage />} /> <Route path="user-access-overview" element={<AdminUserAccessOverviewPage />} />
<Route path="billing"> <Route path="billing">
<Route index element={<Navigate to="/billing/admin" replace />} /> <Route index element={<Navigate to="/billing/admin" replace />} />

View file

@ -1,10 +1,25 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
// api.ts // api.ts
import axios from 'axios'; import axios from 'axios';
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from './utils/csrfUtils'; import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from './utils/csrfUtils';
import { clearUserDataCache, getUserDataCache } from './utils/userCache'; import { clearUserDataCache, getUserDataCache } from './utils/userCache';
// Utility function to resolve hostname to IP address
const resolveHostnameToIP = async (hostname: string): Promise<string | null> => {
try {
// For localhost, return as is
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return hostname;
}
// For production domains, we can't directly resolve IP due to CORS
// But we can show the hostname which is more useful anyway
return hostname;
} catch (error) {
console.warn('Could not resolve hostname to IP:', error);
return hostname;
}
};
/** /**
* Extract mandate/instance context from current URL. * Extract mandate/instance context from current URL.
* URL pattern: /mandates/:mandateId/:featureCode/:instanceId/... * URL pattern: /mandates/:mandateId/:featureCode/:instanceId/...
@ -29,25 +44,52 @@ const getContextFromUrl = (): { mandateId?: string; instanceId?: string } => {
import { getApiBaseUrl } from '../config/config'; import { getApiBaseUrl } from '../config/config';
const _baseUrl = getApiBaseUrl();
if (import.meta.env.DEV) {
console.log(`[api] Backend: ${_baseUrl} | env: ${import.meta.env.MODE}`);
}
const api = axios.create({ const api = axios.create({
baseURL: _baseUrl, baseURL: getApiBaseUrl(),
withCredentials: true, withCredentials: true,
// FastAPI expects repeat-style array query params (``?ids=1&ids=2``).
// Axios v1.x default would render ``?ids[]=1&ids[]=2``, which FastAPI
// silently drops -- e.g. ``trackerIds`` filters on the Redmine stats
// endpoint never reach the route. Setting ``indexes: null`` switches
// the URLSearchParams visitor to repeat format. Applies globally so
// every endpoint with array query params gets it for free.
paramsSerializer: { indexes: null }, paramsSerializer: { indexes: null },
}); });
// Add a request interceptor to add the auth token, context headers // Add a request interceptor to add the auth token, context headers, and log backend IP
api.interceptors.request.use( api.interceptors.request.use(
async (config) => { async (config) => {
// Add auth token if available (otherwise httpOnly cookies are used automatically) // Log backend information
const backendUrl = config.baseURL || getApiBaseUrl();
console.log(`🌐 Communicating with backend: ${backendUrl}`);
// Try to resolve and log the IP address
if (backendUrl) {
try {
const url = new URL(backendUrl);
const hostname = url.hostname;
const resolvedIP = await resolveHostnameToIP(hostname);
console.log(`📍 Backend hostname: ${hostname}`);
console.log(`🔗 Full backend URL: ${backendUrl}`);
console.log(`🌍 Resolved address: ${resolvedIP}`);
// Log environment info
console.log(`🏗️ Environment: ${import.meta.env.MODE}`);
console.log(`⚙️ API Base URL: ${getApiBaseUrl()}`);
} catch (error) {
console.warn('Could not parse backend URL:', error);
}
}
// Check for auth token in localStorage and add to headers
const authToken = localStorage.getItem('authToken'); const authToken = localStorage.getItem('authToken');
if (authToken && config.headers) { if (authToken && config.headers) {
config.headers.Authorization = `Bearer ${authToken}`; config.headers.Authorization = `Bearer ${authToken}`;
console.log('🔑 Using Bearer token for authentication');
} else {
// Fallback: httpOnly cookies
console.log('🍪 Using httpOnly cookies for authentication (automatic)');
} }
// Send app language to backend so i18n labels match the UI // Send app language to backend so i18n labels match the UI

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
import type { AttributeType } from '../utils/attributeTypeMapper'; import type { AttributeType } from '../utils/attributeTypeMapper';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
import api from '../api'; import api from '../api';
import { addCSRFTokenToHeaders } from '../utils/csrfUtils'; import { addCSRFTokenToHeaders } from '../utils/csrfUtils';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,135 +0,0 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/**
* ClickUp API ClickUp-specific functions for the workflow automation flow editor.
*
* Extracted from the legacy workflowApi.ts re-export shim so each integration
* lives in its own module.
*/
import type { ApiRequestFunction } from './workflowAutomationApi';
function _encodedConnectionId(connectionId: string): string {
return encodeURIComponent(connectionId);
}
/** ClickUp GET /task/{taskId} — list.id for resolving list-scoped fields when only task id is known. */
export async function fetchClickupTask(
request: ApiRequestFunction,
connectionId: string,
taskId: string
): Promise<Record<string, unknown>> {
const data = await request({
url: `/api/clickup/${_encodedConnectionId(connectionId)}/tasks/${encodeURIComponent(taskId)}`,
method: 'get',
});
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
}
/** ClickUp list metadata (statuses, etc.) — GET /api/clickup/{connectionId}/lists/{listId}. */
export async function fetchClickupList(
request: ApiRequestFunction,
connectionId: string,
listId: string
): Promise<Record<string, unknown>> {
const data = await request({
url: `/api/clickup/${_encodedConnectionId(connectionId)}/lists/${encodeURIComponent(listId)}`,
method: 'get',
});
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
}
/** ClickUp workspace/team (members for assignees) — GET /api/clickup/{connectionId}/teams/{teamId}. */
export async function fetchClickupTeam(
request: ApiRequestFunction,
connectionId: string,
teamId: string
): Promise<Record<string, unknown>> {
const data = await request({
url: `/api/clickup/${_encodedConnectionId(connectionId)}/teams/${encodeURIComponent(teamId)}`,
method: 'get',
});
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
}
/** ClickUp list custom fields (GET /api/clickup/{connectionId}/lists/{listId}/fields). */
export async function fetchClickupListFields(
request: ApiRequestFunction,
connectionId: string,
listId: string
): Promise<{ fields?: unknown[] } & Record<string, unknown>> {
const data = await request({
url: `/api/clickup/${_encodedConnectionId(connectionId)}/lists/${encodeURIComponent(listId)}/fields`,
method: 'get',
});
return (data && typeof data === 'object' ? data : {}) as { fields?: unknown[] } & Record<string, unknown>;
}
/** ClickUp GET /list/{id}/task page (tasks in a list for relationship dropdowns). */
export interface ClickupListTaskItem {
id?: string;
name?: string;
}
export async function fetchClickupListTasks(
request: ApiRequestFunction,
connectionId: string,
listId: string,
options?: { page?: number; includeClosed?: boolean }
): Promise<
{ tasks?: ClickupListTaskItem[]; last_page?: boolean } & Record<string, unknown>
> {
const data = await request({
url: `/api/clickup/${_encodedConnectionId(connectionId)}/lists/${encodeURIComponent(listId)}/tasks`,
method: 'get',
params: {
page: options?.page ?? 0,
include_closed: options?.includeClosed ?? false,
},
});
return (data && typeof data === 'object' ? data : {}) as {
tasks?: ClickupListTaskItem[];
last_page?: boolean;
} & Record<string, unknown>;
}
/** Paginated tasks in a list — for ClickUp relationship dropdowns and input.form „ClickUp-Aufgabe". */
export async function loadClickupListTasksForDropdown(
request: ApiRequestFunction,
connectionId: string,
listId: string
): Promise<Array<{ id: string; name: string }>> {
const acc: Array<{ id: string; name: string }> = [];
const seen = new Set<string>();
const maxPages = 12;
const pageSizeHint = 100;
for (let page = 0; page < maxPages; page++) {
const data = await fetchClickupListTasks(request, connectionId, listId, {
page,
includeClosed: false,
});
if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) {
const err = (data as { error?: unknown }).error;
const body = (data as { body?: string }).body;
throw new Error(
typeof err === 'string' ? err + (body ? `: ${body.slice(0, 200)}` : '') : 'ClickUp API error'
);
}
const tasks = Array.isArray(data.tasks) ? data.tasks : [];
for (const t of tasks) {
const id = t?.id != null ? String(t.id) : '';
if (!id || seen.has(id)) continue;
seen.add(id);
acc.push({ id, name: String(t.name ?? id) });
}
const rawLast = (data as Record<string, unknown>).last_page;
const last =
rawLast === true ||
rawLast === 'true' ||
tasks.length === 0 ||
tasks.length < pageSizeHint;
if (last) break;
}
acc.sort((a, b) => a.name.localeCompare(b.name, 'de'));
return acc;
}

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import api from '../api'; import api from '../api';
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils'; import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils';
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Features API * Features API
* *
@ -172,11 +170,19 @@ export async function fetchMyFeatures(): Promise<FeaturesMyResponse> {
} }
try { try {
console.log('📡 featuresApi: Fetching /api/features/my');
const response = await api.get<FeaturesMyResponse>('/api/features/my'); const response = await api.get<FeaturesMyResponse>('/api/features/my');
// Get the actual data (response.data contains the FeaturesMyResponse) // Get the actual data (response.data contains the FeaturesMyResponse)
const data = response.data; const data = response.data;
console.log('✅ featuresApi: Loaded features:', {
mandateCount: data?.mandates?.length || 0,
totalInstances: data?.mandates
?.flatMap(m => m.features)
?.flatMap(f => f.instances)
?.length || 0,
});
return data; return data;
} catch (error) { } catch (error) {
console.error('❌ featuresApi: Error fetching features:', error); console.error('❌ featuresApi: Error fetching features:', error);

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Neutralization API * Neutralization API
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import api from '../api'; import api from '../api';
import type { ApiRequestOptions } from '../hooks/useApi'; import type { ApiRequestOptions } from '../hooks/useApi';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Redmine API * Redmine API
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Store API * Store API
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import api from '../api'; import api from '../api';
export interface TableListViewRow { export interface TableListViewRow {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import api from '../api'; import api from '../api';
import type { VoiceOption } from './voiceCatalogApi'; import type { VoiceOption } from './voiceCatalogApi';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Trustee API * Trustee API
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { ApiRequestOptions } from '../hooks/useApi'; import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================ // ============================================================================

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Voice / Language Catalog API. * Voice / Language Catalog API.
* *

View file

@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';
/** Mirrors _encodedConnectionId in workflowApi.ts for browse/services URL paths. */
function encodedConnectionId(connectionId: string): string {
return encodeURIComponent(connectionId);
}
describe('connection path encoding for workflow browse', () => {
it('encodes spaces and colons in connection:clickup:username references', () => {
const ref = 'connection:clickup:Stephan Schellworth';
const segment = encodedConnectionId(ref);
expect(segment).toBe('connection%3Aclickup%3AStephan%20Schellworth');
const url = `/api/workflows/inst/connections/${segment}/browse`;
expect(url).not.toContain(' ');
expect(url).toContain('%20');
});
});

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* AccessLevelSelect * AccessLevelSelect
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* AccessRulesEditor * AccessRulesEditor
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* AccessRulesTable * AccessRulesTable
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* AccessRules Components * AccessRules Components
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* AddConnectionWizard * AddConnectionWizard
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* ChatInput -- Shared chat input component. * ChatInput -- Shared chat input component.
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* ChatMessageList -- Shared chat message display component. * ChatMessageList -- Shared chat message display component.
* *

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { ChatMessageList } from './ChatMessageList'; export { ChatMessageList } from './ChatMessageList';
export type { ChatMessage } from './ChatMessageList'; export type { ChatMessage } from './ChatMessageList';
export { ChatInput } from './ChatInput'; export { ChatInput } from './ChatInput';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { IoIosDownload, IoIosCopy } from 'react-icons/io'; import { IoIosDownload, IoIosCopy } from 'react-icons/io';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { IoIosDownload } from 'react-icons/io'; import { IoIosDownload } from 'react-icons/io';
import { Popup, PopupAction } from '../UiComponents/Popup/Popup'; import { Popup, PopupAction } from '../UiComponents/Popup/Popup';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { ContentPreview } from './ContentPreview'; export { ContentPreview } from './ContentPreview';
export type { ContentPreviewProps } from './ContentPreview'; export type { ContentPreviewProps } from './ContentPreview';
export { UrlContentPreview } from './UrlContentPreview'; export { UrlContentPreview } from './UrlContentPreview';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';
interface ApplicationRendererProps { interface ApplicationRendererProps {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';
interface HtmlRendererProps { interface HtmlRendererProps {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';
interface ImageRendererProps { interface ImageRendererProps {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useState } from 'react'; import { useState } from 'react';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
// @ts-ignore // @ts-ignore
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { IoIosWarning } from 'react-icons/io'; import { IoIosWarning } from 'react-icons/io';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { renderAsync } from 'docx-preview'; import { renderAsync } from 'docx-preview';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { JsonRenderer } from './JsonRenderer'; export { JsonRenderer } from './JsonRenderer';
export { ImageRenderer } from './ImageRenderer'; export { ImageRenderer } from './ImageRenderer';
export { TextRenderer } from './TextRenderer'; export { TextRenderer } from './TextRenderer';

View file

@ -1,16 +1,14 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Workflow Flow Editor - Data flow context for Data Picker and DynamicValueField. * Automation2 Flow Editor - Data flow context for Data Picker and DynamicValueField.
* Extended with portTypeCatalog and systemVariables for the Typed Port System. * Extended with portTypeCatalog and systemVariables for the Typed Port System.
*/ */
import React, { createContext, useContext, useMemo } from 'react'; import React, { createContext, useContext, useMemo } from 'react';
import type { CanvasNode, CanvasConnection } from '../editor/FlowCanvas'; import type { CanvasNode, CanvasConnection } from '../editor/FlowCanvas';
import { getAvailableSources } from '../nodes/shared/dataFlowGraph'; import { getAvailableSources } from '../nodes/shared/dataFlowGraph';
import type { ApiRequestFunction, ConditionOperatorDef, FormFieldType, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowAutomationApi'; import type { ApiRequestFunction, ConditionOperatorDef, FormFieldType, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowApi';
export interface WorkflowDataFlowContextValue { export interface Automation2DataFlowContextValue {
currentNodeId: string; currentNodeId: string;
nodes: CanvasNode[]; nodes: CanvasNode[];
connections: CanvasConnection[]; connections: CanvasConnection[];
@ -32,13 +30,13 @@ export interface WorkflowDataFlowContextValue {
parseGraphDefinedSchema: (parameterKey: string) => PortSchema | null; parseGraphDefinedSchema: (parameterKey: string) => PortSchema | null;
} }
const WorkflowDataFlowContext = createContext<WorkflowDataFlowContextValue | null>(null); const Automation2DataFlowContext = createContext<Automation2DataFlowContextValue | null>(null);
export function useWorkflowDataFlow(): WorkflowDataFlowContextValue | null { export function useAutomation2DataFlow(): Automation2DataFlowContextValue | null {
return useContext(WorkflowDataFlowContext); return useContext(Automation2DataFlowContext);
} }
interface WorkflowDataFlowProviderProps { interface Automation2DataFlowProviderProps {
node: CanvasNode | null; node: CanvasNode | null;
nodes: CanvasNode[]; nodes: CanvasNode[];
connections: CanvasConnection[]; connections: CanvasConnection[];
@ -54,7 +52,7 @@ interface WorkflowDataFlowProviderProps {
children: React.ReactNode; children: React.ReactNode;
} }
export const WorkflowDataFlowProvider: React.FC<WorkflowDataFlowProviderProps> = ({ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderProps> = ({
node, node,
nodes, nodes,
connections, connections,
@ -69,7 +67,7 @@ export const WorkflowDataFlowProvider: React.FC<WorkflowDataFlowProviderProps> =
request, request,
children, children,
}) => { }) => {
const value = useMemo((): WorkflowDataFlowContextValue | null => { const value = useMemo((): Automation2DataFlowContextValue | null => {
if (!node) return null; if (!node) return null;
const formTypeToPort: Record<string, string> = Object.fromEntries( const formTypeToPort: Record<string, string> = Object.fromEntries(
formFieldTypes.map((f) => [f.id, f.portType]) formFieldTypes.map((f) => [f.id, f.portType])
@ -137,8 +135,8 @@ export const WorkflowDataFlowProvider: React.FC<WorkflowDataFlowProviderProps> =
}, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, formFieldTypes, conditionOperatorCatalog, instanceId, request]); }, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, formFieldTypes, conditionOperatorCatalog, instanceId, request]);
return ( return (
<WorkflowDataFlowContext.Provider value={value}> <Automation2DataFlowContext.Provider value={value}>
{children} {children}
</WorkflowDataFlowContext.Provider> </Automation2DataFlowContext.Provider>
); );
}; };

View file

@ -1,5 +1,5 @@
/** /**
* Workflow Flow Editor Styles * Automation2 Flow Editor Styles
* Sidebar with node list + canvas area. * Sidebar with node list + canvas area.
*/ */

View file

@ -1,7 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* WorkflowFlowEditor * Automation2FlowEditor
* *
* n8n-style flow builder with backend-driven node list and categories. * n8n-style flow builder with backend-driven node list and categories.
* Start nodes come from the API (category `start`); invocations are synced on the server from the graph. * Start nodes come from the API (category `start`); invocations are synced on the server from the graph.
@ -25,16 +23,15 @@ import {
createTemplateFromWorkflow, createTemplateFromWorkflow,
copyTemplate, copyTemplate,
importWorkflowFromFile, importWorkflowFromFile,
WORKFLOW_FILE_EXTENSION,
type NodeType, type NodeType,
type NodeTypeCategory, type NodeTypeCategory,
type WorkflowGraph, type Automation2Graph,
type WorkflowDefinition, type Automation2Workflow,
type ExecuteGraphResponse, type ExecuteGraphResponse,
type WorkflowEntryPoint, type WorkflowEntryPoint,
type AutoVersion, type AutoVersion,
type AutoTemplateScope, type AutoTemplateScope,
} from '../../../api/workflowAutomationApi'; } from '../../../api/workflowApi';
import { import {
FlowCanvas, FlowCanvas,
type CanvasNode, type CanvasNode,
@ -52,7 +49,7 @@ import { fromApiGraph, toApiGraph, switchOutputCountFromCases, trimConnectionsFo
import { buildNodeOutputsPreview, setPortTypeCatalog as setRegistryCatalog } from '../nodes/shared/outputPreviewRegistry'; import { buildNodeOutputsPreview, setPortTypeCatalog as setRegistryCatalog } from '../nodes/shared/outputPreviewRegistry';
import { findGraphErrors } from '../nodes/shared/paramValidation'; import { findGraphErrors } from '../nodes/shared/paramValidation';
import { getLabel as getParamLabel } from '../nodes/shared/utils'; import { getLabel as getParamLabel } from '../nodes/shared/utils';
import { WorkflowDataFlowProvider } from '../context/WorkflowDataFlowContext'; import { Automation2DataFlowProvider } from '../context/Automation2DataFlowContext';
import { usePrompt } from '../../../hooks/usePrompt'; import { usePrompt } from '../../../hooks/usePrompt';
import { EditorChatPanel } from './EditorChatPanel'; import { EditorChatPanel } from './EditorChatPanel';
import type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './EditorChatPanel'; import type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './EditorChatPanel';
@ -60,12 +57,12 @@ import { EditorWorkflowChatList } from './EditorWorkflowChatList';
import { RunTracingPanel } from './RunTracingPanel'; import { RunTracingPanel } from './RunTracingPanel';
import { UnifiedDataBar } from '../../../components/UnifiedDataBar'; import { UnifiedDataBar } from '../../../components/UnifiedDataBar';
import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar'; import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar';
import styles from './WorkflowFlowEditor.module.css'; import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
const LOG = '[WorkflowEditor]'; const LOG = '[Automation2]';
const CANVAS_HISTORY_MAX = 50; const CANVAS_HISTORY_MAX = 50;
@ -81,7 +78,7 @@ function cloneCanvasSnapshot(nodes: CanvasNode[], connections: CanvasConnection[
}; };
} }
interface WorkflowFlowEditorProps { interface Automation2FlowEditorProps {
instanceId: string; instanceId: string;
mandateId?: string; mandateId?: string;
language?: string; language?: string;
@ -95,7 +92,7 @@ interface WorkflowFlowEditorProps {
onSourcesChanged?: () => void; onSourcesChanged?: () => void;
} }
export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instanceId, export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ instanceId,
mandateId, mandateId,
language = 'de', language = 'de',
initialWorkflowId, initialWorkflowId,
@ -113,9 +110,9 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
const [categories, setCategories] = useState<NodeTypeCategory[]>([]); const [categories, setCategories] = useState<NodeTypeCategory[]>([]);
const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({}); const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({});
const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({}); const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({});
const [formFieldTypes, setFormFieldTypes] = useState<import('../../../api/workflowAutomationApi').FormFieldType[]>([]); const [formFieldTypes, setFormFieldTypes] = useState<import('../../../api/workflowApi').FormFieldType[]>([]);
const [conditionOperatorCatalog, setConditionOperatorCatalog] = useState< const [conditionOperatorCatalog, setConditionOperatorCatalog] = useState<
Record<string, import('../../../api/workflowAutomationApi').ConditionOperatorDef[]> Record<string, import('../../../api/workflowApi').ConditionOperatorDef[]>
>({}); >({});
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -140,7 +137,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
const [canvasStickyNotes, setCanvasStickyNotes] = useState<CanvasStickyNote[]>([]); const [canvasStickyNotes, setCanvasStickyNotes] = useState<CanvasStickyNote[]>([]);
const [executing, setExecuting] = useState(false); const [executing, setExecuting] = useState(false);
const [executeResult, setExecuteResult] = useState<ExecuteGraphResponse | null>(null); const [executeResult, setExecuteResult] = useState<ExecuteGraphResponse | null>(null);
const [workflows, setWorkflows] = useState<WorkflowDefinition[]>([]); const [workflows, setWorkflows] = useState<Automation2Workflow[]>([]);
const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null); const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
const [selectedNode, setSelectedNode] = useState<CanvasNode | null>(null); const [selectedNode, setSelectedNode] = useState<CanvasNode | null>(null);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@ -156,7 +153,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
instanceId, instanceId,
mandateId: mandateId || '', mandateId: mandateId || '',
featureInstanceId: instanceId, featureInstanceId: instanceId,
surface: 'workflowAutomation', surface: 'graphEditor',
}), [instanceId, mandateId]); }), [instanceId, mandateId]);
const [versions, setVersions] = useState<AutoVersion[]>([]); const [versions, setVersions] = useState<AutoVersion[]>([]);
const [currentVersionId, setCurrentVersionId] = useState<string | null>(null); const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
@ -304,7 +301,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
const applyGraphWithSync = useCallback( const applyGraphWithSync = useCallback(
( (
graph: WorkflowGraph | null | undefined, graph: Automation2Graph | null | undefined,
wfInvocations: WorkflowEntryPoint[] | undefined, wfInvocations: WorkflowEntryPoint[] | undefined,
opts?: { skipHistory?: boolean } opts?: { skipHistory?: boolean }
) => { ) => {
@ -312,7 +309,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
pushCanvasHistoryPastFromCurrent(); pushCanvasHistoryPastFromCurrent();
} }
setInvocations(wfInvocations ?? []); setInvocations(wfInvocations ?? []);
const g: WorkflowGraph = graph ?? { nodes: [], connections: [] }; const g: Automation2Graph = graph ?? { nodes: [], connections: [] };
const { nodes, connections } = fromApiGraph(g, nodeTypes); const { nodes, connections } = fromApiGraph(g, nodeTypes);
setCanvasNodes(nodes); setCanvasNodes(nodes);
setCanvasConnections(connections); setCanvasConnections(connections);
@ -321,7 +318,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
); );
const handleFromApiGraph = useCallback( const handleFromApiGraph = useCallback(
(graph: WorkflowGraph, wfInvocations?: WorkflowEntryPoint[]) => { (graph: Automation2Graph, wfInvocations?: WorkflowEntryPoint[]) => {
applyGraphWithSync(graph, wfInvocations); applyGraphWithSync(graph, wfInvocations);
}, },
[applyGraphWithSync] [applyGraphWithSync]
@ -357,7 +354,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setExecuteResult(null); setExecuteResult(null);
try { try {
const ep = currentWorkflowId ? invocations[0]?.id : undefined; const ep = currentWorkflowId ? invocations[0]?.id : undefined;
const result = await executeGraph(request, graph, currentWorkflowId ?? undefined, { const result = await executeGraph(request, instanceId, graph, currentWorkflowId ?? undefined, {
...(ep ? { entryPointId: ep } : {}), ...(ep ? { entryPointId: ep } : {}),
}); });
setExecuteResult(result); setExecuteResult(result);
@ -406,7 +403,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setSaving(true); setSaving(true);
try { try {
if (currentWorkflowId) { if (currentWorkflowId) {
const updated = await updateWorkflow(request, currentWorkflowId, { const updated = await updateWorkflow(request, instanceId, currentWorkflowId, {
graph, graph,
invocations, invocations,
targetFeatureInstanceId, targetFeatureInstanceId,
@ -423,12 +420,11 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setSaving(false); setSaving(false);
return; return;
} }
const created = await createWorkflow(request, { const created = await createWorkflow(request, instanceId, {
label: label.trim() || t('Neuer Workflow'), label: label.trim() || t('Neuer Workflow'),
graph, graph,
invocations, invocations,
targetFeatureInstanceId, targetFeatureInstanceId,
mandateId,
}); });
setCurrentWorkflowId(created.id); setCurrentWorkflowId(created.id);
setInvocations(created.invocations ?? []); setInvocations(created.invocations ?? []);
@ -440,12 +436,12 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
} finally { } finally {
setSaving(false); setSaving(false);
} }
}, [request, mandateId, canvasNodes, canvasConnections, currentWorkflowId, promptInput, invocations, t, nodeErrors, targetFeatureInstanceId, hasCanvasStartNode]); }, [request, instanceId, canvasNodes, canvasConnections, currentWorkflowId, promptInput, invocations, t, nodeErrors, targetFeatureInstanceId, hasCanvasStartNode]);
const handleLoad = useCallback( const handleLoad = useCallback(
async (workflowId: string) => { async (workflowId: string) => {
try { try {
const wf = await fetchWorkflow(request, workflowId); const wf = await fetchWorkflow(request, instanceId, workflowId);
if (wf.graph) { if (wf.graph) {
handleFromApiGraph(wf.graph, wf.invocations); handleFromApiGraph(wf.graph, wf.invocations);
} else { } else {
@ -467,7 +463,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setExecuteResult(null); setExecuteResult(null);
applyGraphWithSync({ nodes: [], connections: [] }, []); applyGraphWithSync({ nodes: [], connections: [] }, []);
try { try {
const result = await fetchWorkflows(request); const result = await fetchWorkflows(request, instanceId);
setWorkflows(Array.isArray(result) ? result : result.items); setWorkflows(Array.isArray(result) ? result : result.items);
} catch (refreshErr) { } catch (refreshErr) {
console.error(`${LOG} workflows refresh failed`, refreshErr); console.error(`${LOG} workflows refresh failed`, refreshErr);
@ -480,7 +476,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
}); });
} }
}, },
[request, handleFromApiGraph, applyGraphWithSync, t] [request, instanceId, handleFromApiGraph, applyGraphWithSync, t]
); );
const handleWorkflowSelect = useCallback( const handleWorkflowSelect = useCallback(
@ -548,10 +544,11 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
); );
const loadNodeTypes = useCallback(async () => { const loadNodeTypes = useCallback(async () => {
if (!instanceId) return;
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const data = await fetchNodeTypes(request, mandateId || '', language); const data = await fetchNodeTypes(request, instanceId, language);
setNodeTypes(data.nodeTypes); setNodeTypes(data.nodeTypes);
setCategories(data.categories); setCategories(data.categories);
if (data.portTypeCatalog) { if (data.portTypeCatalog) {
@ -568,16 +565,17 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [language, request]); }, [instanceId, language, request]);
const loadWorkflows = useCallback(async () => { const loadWorkflows = useCallback(async () => {
if (!instanceId) return;
try { try {
const result = await fetchWorkflows(request, { mandateId: mandateId || undefined }); const result = await fetchWorkflows(request, instanceId);
setWorkflows(Array.isArray(result) ? result : result.items); setWorkflows(Array.isArray(result) ? result : result.items);
} catch (e) { } catch (e) {
console.error(`${LOG} loadWorkflows failed`, e); console.error(`${LOG} loadWorkflows failed`, e);
} }
}, [request, mandateId]); }, [instanceId, request]);
useEffect(() => { useEffect(() => {
loadNodeTypes(); loadNodeTypes();
@ -667,17 +665,17 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
); );
const loadVersions = useCallback(async () => { const loadVersions = useCallback(async () => {
if (!currentWorkflowId) { if (!instanceId || !currentWorkflowId) {
setVersions([]); setVersions([]);
return; return;
} }
try { try {
const v = await fetchVersions(request, currentWorkflowId); const v = await fetchVersions(request, instanceId, currentWorkflowId);
setVersions(v); setVersions(v);
} catch (e) { } catch (e) {
console.error(`${LOG} loadVersions failed`, e); console.error(`${LOG} loadVersions failed`, e);
} }
}, [currentWorkflowId, request]); }, [instanceId, currentWorkflowId, request]);
useEffect(() => { useEffect(() => {
loadVersions(); loadVersions();
@ -698,9 +696,10 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
const handlePublishVersion = useCallback( const handlePublishVersion = useCallback(
async (versionId: string) => { async (versionId: string) => {
if (!instanceId) return;
setVersionLoading(true); setVersionLoading(true);
try { try {
await publishVersion(request, versionId); await publishVersion(request, instanceId, versionId);
await loadVersions(); await loadVersions();
} catch (e: unknown) { } catch (e: unknown) {
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) }); setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
@ -708,14 +707,15 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setVersionLoading(false); setVersionLoading(false);
} }
}, },
[request, loadVersions] [request, instanceId, loadVersions]
); );
const handleUnpublishVersion = useCallback( const handleUnpublishVersion = useCallback(
async (versionId: string) => { async (versionId: string) => {
if (!instanceId) return;
setVersionLoading(true); setVersionLoading(true);
try { try {
await unpublishVersion(request, versionId); await unpublishVersion(request, instanceId, versionId);
await loadVersions(); await loadVersions();
} catch (e: unknown) { } catch (e: unknown) {
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) }); setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
@ -723,14 +723,15 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setVersionLoading(false); setVersionLoading(false);
} }
}, },
[request, loadVersions] [request, instanceId, loadVersions]
); );
const handleArchiveVersion = useCallback( const handleArchiveVersion = useCallback(
async (versionId: string) => { async (versionId: string) => {
if (!instanceId) return;
setVersionLoading(true); setVersionLoading(true);
try { try {
await archiveVersion(request, versionId); await archiveVersion(request, instanceId, versionId);
await loadVersions(); await loadVersions();
} catch (e: unknown) { } catch (e: unknown) {
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) }); setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
@ -738,14 +739,14 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setVersionLoading(false); setVersionLoading(false);
} }
}, },
[request, loadVersions] [request, instanceId, loadVersions]
); );
const handleCreateDraft = useCallback(async () => { const handleCreateDraft = useCallback(async () => {
if (!currentWorkflowId) return; if (!instanceId || !currentWorkflowId) return;
setVersionLoading(true); setVersionLoading(true);
try { try {
const draft = await createDraftVersion(request, currentWorkflowId); const draft = await createDraftVersion(request, instanceId, currentWorkflowId);
await loadVersions(); await loadVersions();
setCurrentVersionId(draft.id); setCurrentVersionId(draft.id);
} catch (e: unknown) { } catch (e: unknown) {
@ -753,16 +754,16 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
} finally { } finally {
setVersionLoading(false); setVersionLoading(false);
} }
}, [request, currentWorkflowId, loadVersions]); }, [request, instanceId, currentWorkflowId, loadVersions]);
// Template: save current workflow as template // Template: save current workflow as template
const [templateSaving, setTemplateSaving] = useState(false); const [templateSaving, setTemplateSaving] = useState(false);
const handleSaveAsTemplate = useCallback( const handleSaveAsTemplate = useCallback(
async (scope: AutoTemplateScope) => { async (scope: AutoTemplateScope) => {
if (!currentWorkflowId) return; if (!instanceId || !currentWorkflowId) return;
setTemplateSaving(true); setTemplateSaving(true);
try { try {
await createTemplateFromWorkflow(request, currentWorkflowId, scope); await createTemplateFromWorkflow(request, instanceId, currentWorkflowId, scope);
setExecuteResult({ success: true, error: undefined } as unknown as ExecuteGraphResponse); setExecuteResult({ success: true, error: undefined } as unknown as ExecuteGraphResponse);
} catch (e: unknown) { } catch (e: unknown) {
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) }); setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
@ -770,15 +771,16 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setTemplateSaving(false); setTemplateSaving(false);
} }
}, },
[request, currentWorkflowId] [request, instanceId, currentWorkflowId]
); );
// Template: new workflow from template // Template: new workflow from template
const [templatePickerOpen, setTemplatePickerOpen] = useState(false); const [templatePickerOpen, setTemplatePickerOpen] = useState(false);
const handleNewFromTemplate = useCallback( const handleNewFromTemplate = useCallback(
async (templateId: string) => { async (templateId: string) => {
if (!instanceId) return;
try { try {
const wf = await copyTemplate(request, templateId); const wf = await copyTemplate(request, instanceId, templateId);
setWorkflows((prev) => [...prev, wf]); setWorkflows((prev) => [...prev, wf]);
setCurrentWorkflowId(wf.id); setCurrentWorkflowId(wf.id);
if (wf.graph) handleFromApiGraph(wf.graph, wf.invocations); if (wf.graph) handleFromApiGraph(wf.graph, wf.invocations);
@ -787,7 +789,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) }); setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
} }
}, },
[request, handleFromApiGraph] [request, instanceId, handleFromApiGraph]
); );
@ -945,20 +947,12 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
activeTab={udbTab as UdbTab} activeTab={udbTab as UdbTab}
onTabChange={(tab) => setUdbTab(tab as LeftTab)} onTabChange={(tab) => setUdbTab(tab as LeftTab)}
hideTabs={['chats']} hideTabs={['chats']}
onFileSelect={async (fileId, fileName) => { onFileSelect={onFileSelect}
if (fileName?.toLowerCase().endsWith(WORKFLOW_FILE_EXTENSION)) {
try {
const result = await importWorkflowFromFile(request, { fileId });
await loadWorkflows();
if (result?.workflow?.id) handleWorkflowSelect(result.workflow.id);
} catch (e) {
console.error('[workflowAutomation] workflow file import failed', e);
}
return;
}
onFileSelect?.(fileId, fileName);
}}
onSourcesChanged={onSourcesChanged} onSourcesChanged={onSourcesChanged}
onWorkflowImportedFromFile={async (workflowId) => {
await loadWorkflows();
handleWorkflowSelect(workflowId);
}}
/> />
)} )}
</div> </div>
@ -1030,12 +1024,12 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
stickyNotes={canvasStickyNotes} stickyNotes={canvasStickyNotes}
onStickyNotesChange={setCanvasStickyNotes} onStickyNotesChange={setCanvasStickyNotes}
onExternalDrop={async (mime, payload) => { onExternalDrop={async (mime, payload) => {
if (mime !== 'application/json+workflow') return false; if (mime !== 'application/json+workflow' || !instanceId) return false;
const p = payload as { files?: Array<{ id: string }> } | undefined; const p = payload as { files?: Array<{ id: string }> } | undefined;
const fileId = p?.files?.[0]?.id; const fileId = p?.files?.[0]?.id;
if (!fileId) return false; if (!fileId) return false;
try { try {
const result = await importWorkflowFromFile(request, { fileId }); const result = await importWorkflowFromFile(request, instanceId, { fileId });
await loadWorkflows(); await loadWorkflows();
if (result?.workflow?.id) handleWorkflowSelect(result.workflow.id); if (result?.workflow?.id) handleWorkflowSelect(result.workflow.id);
return true; return true;
@ -1048,7 +1042,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
</div> </div>
{configurableSelected && selectedNode && ( {configurableSelected && selectedNode && (
<div className={styles.nodeConfigPanelWrap} data-suppress-flow-node-hotkeys=""> <div className={styles.nodeConfigPanelWrap} data-suppress-flow-node-hotkeys="">
<WorkflowDataFlowProvider <Automation2DataFlowProvider
node={selectedNode} node={selectedNode}
nodes={canvasNodes} nodes={canvasNodes}
connections={canvasConnections} connections={canvasConnections}
@ -1073,7 +1067,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
request={request} request={request}
verboseSchema={verboseSchema} verboseSchema={verboseSchema}
/> />
</WorkflowDataFlowProvider> </Automation2DataFlowProvider>
</div> </div>
)} )}
</div> </div>
@ -1128,4 +1122,4 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
); );
}; };
export default WorkflowFlowEditor; export default Automation2FlowEditor;

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* CanvasHeader - Workflow controls, version selector, and execute result. * CanvasHeader - Workflow controls, version selector, and execute result.
*/ */
@ -28,8 +26,8 @@ import {
HiOutlineChatBubbleLeftEllipsis, HiOutlineChatBubbleLeftEllipsis,
HiOutlineSquares2X2, HiOutlineSquares2X2,
} from 'react-icons/hi2'; } from 'react-icons/hi2';
import type { WorkflowDefinition, ExecuteGraphResponse, AutoVersion, AutoTemplateScope } from '../../../api/workflowAutomationApi'; import type { Automation2Workflow, ExecuteGraphResponse, AutoVersion, AutoTemplateScope } from '../../../api/workflowApi';
import styles from './WorkflowFlowEditor.module.css'; import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import { getUserDataCache } from '../../../utils/userCache'; import { getUserDataCache } from '../../../utils/userCache';
@ -62,7 +60,7 @@ export interface CanvasHeaderCanvasEditProps {
} }
interface CanvasHeaderProps { interface CanvasHeaderProps {
workflows: WorkflowDefinition[]; workflows: Automation2Workflow[];
currentWorkflowId: string | null; currentWorkflowId: string | null;
onWorkflowSelect: (workflowId: string | null) => void; onWorkflowSelect: (workflowId: string | null) => void;
onNew: () => void; onNew: () => void;

View file

@ -1,9 +1,7 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* EditorChatPanel * EditorChatPanel
* *
* AI Chat sidebar for the WorkflowAutomation editor. * AI Chat sidebar for the GraphicalEditor.
* Streams responses via SSE (same pattern as Workspace chat). * Streams responses via SSE (same pattern as Workspace chat).
* File & data-source attachment UX mirrors WorkspaceInput: * File & data-source attachment UX mirrors WorkspaceInput:
* - Files: drag & drop from FilesTab (UDB) onto input area, or click in UDB * - Files: drag & drop from FilesTab (UDB) onto input area, or click in UDB
@ -89,7 +87,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
// Load persisted chat history from the backend whenever the workflow changes. // Load persisted chat history from the backend whenever the workflow changes.
// The chat is stored in `ChatWorkflow.linkedWorkflowId == workflowId` and is // The chat is stored in `ChatWorkflow.linkedWorkflowId == workflowId` and is
// returned by `GET /api/workflow-automation/{workflowId}/chat/messages`. // returned by `GET /api/workflows/{instanceId}/{workflowId}/chat/messages`.
// For an unsaved workflow (workflowId == null) we just clear the panel. // For an unsaved workflow (workflowId == null) we just clear the panel.
useEffect(() => { useEffect(() => {
if (!workflowId) { if (!workflowId) {
@ -101,7 +99,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
setHistoryLoading(true); setHistoryLoading(true);
try { try {
const res = await api.get<PersistedEditorChatResponse>( const res = await api.get<PersistedEditorChatResponse>(
`/api/workflow-automation/${workflowId}/chat/messages`, `/api/workflows/${instanceId}/${workflowId}/chat/messages`,
); );
if (cancelled) return; if (cancelled) return;
const persisted = (res.data?.messages || []).map((m): ChatMessage => ({ const persisted = (res.data?.messages || []).map((m): ChatMessage => ({
@ -168,7 +166,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
const baseURL = api.defaults.baseURL || ''; const baseURL = api.defaults.baseURL || '';
const cleanup = startSseStream({ const cleanup = startSseStream({
url: `${baseURL}/api/workflow-automation/${workflowId}/chat/stream`, url: `${baseURL}/api/workflows/${instanceId}/${workflowId}/chat/stream`,
body, body,
handlers: { handlers: {
onChunk: (event) => { onChunk: (event) => {
@ -229,7 +227,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
: m)); : m));
} }
try { try {
await api.post(`/api/workflow-automation/${workflowId}/chat/stop`); await api.post(`/api/workflows/${instanceId}/${workflowId}/chat/stop`);
} catch { } catch {
} }
abortRef.current?.(); abortRef.current?.();

View file

@ -1,19 +1,17 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* EditorWorkflowChatList * EditorWorkflowChatList
* *
* UDB "Chats" tab content for the WorkflowAutomation editor: each AutoWorkflow * UDB "Chats" tab content for the GraphicalEditor: each AutoWorkflow is treated
* is treated as one editor chat session. Lists workflows already loaded by the * as one editor chat session. Lists workflows already loaded by the parent
* parent editor (no extra fetch), supports search and "+ Neu" to start a fresh * editor (no extra fetch), supports search and "+ Neu" to start a fresh
* workflow chat. Mirrors the spirit of the Workspace ChatsTab but uses * workflow chat. Mirrors the spirit of the Workspace ChatsTab but uses
* WorkflowAutomation data instead of the workspace endpoint. * GraphicalEditor data instead of the workspace endpoint.
*/ */
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import type { WorkflowDefinition } from '../../../api/workflowAutomationApi'; import type { Automation2Workflow } from '../../../api/workflowApi';
interface EditorWorkflowChatListProps { interface EditorWorkflowChatListProps {
workflows: WorkflowDefinition[]; workflows: Automation2Workflow[];
currentWorkflowId: string | null; currentWorkflowId: string | null;
onSelect: (workflowId: string | null) => void; onSelect: (workflowId: string | null) => void;
onNew: () => void; onNew: () => void;

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* FlowCanvas - Workflow graph canvas with nodes and connection lines. * FlowCanvas - Workflow graph canvas with nodes and connection lines.
* Nodes have 4 connection handles (one per side), drag nodes to add, connect with arrows. * Nodes have 4 connection handles (one per side), drag nodes to add, connect with arrows.
@ -15,8 +13,8 @@ import React, {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import type { GraphDefinedSchemaRef, NodeType } from '../../../api/workflowAutomationApi'; import type { GraphDefinedSchemaRef, NodeType } from '../../../api/workflowApi';
import styles from './WorkflowFlowEditor.module.css'; import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import { AiBadge } from '../nodes/shared/AiBadge'; import { AiBadge } from '../nodes/shared/AiBadge';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* NodeConfigPanel - Generic parameter renderer for all node types. * NodeConfigPanel - Generic parameter renderer for all node types.
* Renders each parameter using FRONTEND_TYPE_RENDERERS based on frontendType. * Renders each parameter using FRONTEND_TYPE_RENDERERS based on frontendType.
@ -7,15 +5,15 @@
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import type { CanvasNode } from './FlowCanvas'; import type { CanvasNode } from './FlowCanvas';
import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } from '../../../api/workflowAutomationApi'; import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } from '../../../api/workflowApi';
import type { ApiRequestFunction } from '../../../api/workflowAutomationApi'; import type { ApiRequestFunction } from '../../../api/workflowApi';
import { getLabel } from '../nodes/shared/utils'; import { getLabel } from '../nodes/shared/utils';
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers'; import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
import { ContextBuilderRenderer } from '../nodes/frontendTypeRenderers/ContextBuilderRenderer'; import { ContextBuilderRenderer } from '../nodes/frontendTypeRenderers/ContextBuilderRenderer';
import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker'; import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker';
import { findRequiredErrors } from '../nodes/shared/paramValidation'; import { findRequiredErrors } from '../nodes/shared/paramValidation';
import { useWorkflowDataFlow } from '../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../context/Automation2DataFlowContext';
import styles from './WorkflowFlowEditor.module.css'; import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import { AccordionList } from '../../UiComponents/AccordionList'; import { AccordionList } from '../../UiComponents/AccordionList';
@ -212,7 +210,7 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
[onParametersChange] [onParametersChange]
); );
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const portTypeCatalog: Record<string, PortSchema> = (dataFlow?.portTypeCatalog as Record<string, PortSchema> | undefined) ?? {}; const portTypeCatalog: Record<string, PortSchema> = (dataFlow?.portTypeCatalog as Record<string, PortSchema> | undefined) ?? {};
// Phase-4 Schicht-4 — Pflicht-Params zuerst sortieren, damit der User // Phase-4 Schicht-4 — Pflicht-Params zuerst sortieren, damit der User

View file

@ -1,16 +1,14 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* NodeListItem - Draggable node type item for the sidebar. * NodeListItem - Draggable node type item for the sidebar.
* Used in both regular categories and I/O sub-groups. * Used in both regular categories and I/O sub-groups.
*/ */
import React from 'react'; import React from 'react';
import type { NodeType } from '../../../api/workflowAutomationApi'; import type { NodeType } from '../../../api/workflowApi';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
import { getCategoryIcon } from '../nodes/shared/utils'; import { getCategoryIcon } from '../nodes/shared/utils';
import type { GetLabelFn } from '../nodes/shared/utils'; import type { GetLabelFn } from '../nodes/shared/utils';
import styles from './WorkflowFlowEditor.module.css'; import styles from './Automation2FlowEditor.module.css';
import { AiBadge } from '../nodes/shared/AiBadge'; import { AiBadge } from '../nodes/shared/AiBadge';
interface NodeListItemProps { interface NodeListItemProps {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* NodeSidebar - Sidebar with searchable, collapsible node list. * NodeSidebar - Sidebar with searchable, collapsible node list.
* Groups node types by category (start, input, flow, data, ai, email, sharepoint). * Groups node types by category (start, input, flow, data, ai, email, sharepoint).
@ -7,11 +5,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { FaChevronDown, FaChevronRight } from 'react-icons/fa'; import { FaChevronDown, FaChevronRight } from 'react-icons/fa';
import type { NodeType, NodeTypeCategory } from '../../../api/workflowAutomationApi'; import type { NodeType, NodeTypeCategory } from '../../../api/workflowApi';
import { CATEGORY_ORDER, HIDDEN_NODE_IDS } from '../nodes/shared/constants'; import { CATEGORY_ORDER, HIDDEN_NODE_IDS } from '../nodes/shared/constants';
import { getLabel } from '../nodes/shared/utils'; import { getLabel } from '../nodes/shared/utils';
import { NodeListItem } from './NodeListItem'; import { NodeListItem } from './NodeListItem';
import styles from './WorkflowFlowEditor.module.css'; import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* RunTracingPanel * RunTracingPanel
* *
@ -9,7 +7,7 @@
*/ */
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useApiRequest } from '../../../hooks/useApi'; import { useApiRequest } from '../../../hooks/useApi';
import type { AutoStepLog } from '../../../api/workflowAutomationApi'; import type { AutoStepLog } from '../../../api/workflowApi';
import api from '../../../api'; import api from '../../../api';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
@ -100,7 +98,7 @@ export const RunTracingPanel: React.FC<RunTracingPanelProps> = ({
setLoading(true); setLoading(true);
try { try {
const data = await request({ const data = await request({
url: `/api/workflow-automation/runs/${runId}/steps`, url: `/api/workflows/${instanceId}/runs/${runId}/steps`,
method: 'get', method: 'get',
}); });
setSteps(data?.steps || []); setSteps(data?.steps || []);
@ -117,7 +115,7 @@ export const RunTracingPanel: React.FC<RunTracingPanelProps> = ({
loadSteps(); loadSteps();
const baseUrl = api.defaults.baseURL || ''; const baseUrl = api.defaults.baseURL || '';
const url = `${baseUrl}/api/workflow-automation/runs/${runId}/stream`; const url = `${baseUrl}/api/workflows/${instanceId}/runs/${runId}/stream`;
const es = new EventSource(url, { withCredentials: true }); const es = new EventSource(url, { withCredentials: true });
eventSourceRef.current = es; eventSourceRef.current = es;

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* TemplatePicker - modal to browse and select a workflow template for creating a new workflow. * TemplatePicker - modal to browse and select a workflow template for creating a new workflow.
*/ */
@ -11,8 +9,8 @@ import {
type AutoWorkflowTemplate, type AutoWorkflowTemplate,
type AutoTemplateScope, type AutoTemplateScope,
type ApiRequestFunction, type ApiRequestFunction,
} from '../../../api/workflowAutomationApi'; } from '../../../api/workflowApi';
import styles from './WorkflowFlowEditor.module.css'; import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext'; import { useLanguage } from '../../../providers/language/LanguageContext';
interface TemplatePickerProps { interface TemplatePickerProps {
@ -52,7 +50,7 @@ export const TemplatePicker: React.FC<TemplatePickerProps> = ({
setLoading(true); setLoading(true);
try { try {
const scope = activeScope === 'all' ? undefined : activeScope; const scope = activeScope === 'all' ? undefined : activeScope;
const result = await fetchTemplates(request, scope); const result = await fetchTemplates(request, instanceId, scope);
setTemplates(Array.isArray(result) ? result : result.items); setTemplates(Array.isArray(result) ? result : result.items);
} catch { } catch {
setTemplates([]); setTemplates([]);

View file

@ -1,6 +1,4 @@
// Copyright (c) 2026 PowerOn AG export { Automation2FlowEditor, Automation2FlowEditor as FlowEditor } from './editor/Automation2FlowEditor';
// All rights reserved.
export { WorkflowFlowEditor, WorkflowFlowEditor as FlowEditor } from './editor/WorkflowFlowEditor';
export type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './editor/EditorChatPanel'; export type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './editor/EditorChatPanel';
export { FlowCanvas, STICKY_NOTE_PALETTE, STICKY_NOTE_DEFAULT_COLOR_ID, STICKY_NOTE_DEFAULT_HEIGHT, getStickyNotePaletteEntry } from './editor/FlowCanvas'; export { FlowCanvas, STICKY_NOTE_PALETTE, STICKY_NOTE_DEFAULT_COLOR_ID, STICKY_NOTE_DEFAULT_HEIGHT, getStickyNotePaletteEntry } from './editor/FlowCanvas';
export type { CanvasNode, CanvasConnection, CanvasStickyNote, FlowCanvasHandle, FlowCanvasViewportEditState } from './editor/FlowCanvas'; export type { CanvasNode, CanvasConnection, CanvasStickyNote, FlowCanvasHandle, FlowCanvasViewportEditState } from './editor/FlowCanvas';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* One text field per option the text the end user sees in the dropdown. * One text field per option the text the end user sees in the dropdown.
* Stored as { value, label } with the same string so payload and UI stay in sync. * Stored as { value, label } with the same string so payload and UI stay in sync.

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Form node config - draggable fields, types, required toggle * Form node config - draggable fields, types, required toggle
*/ */
@ -8,8 +6,8 @@ import React from 'react';
import { FaGripVertical, FaTimes } from 'react-icons/fa'; import { FaGripVertical, FaTimes } from 'react-icons/fa';
import type { FormField, NodeConfigRendererProps } from '../shared/types'; import type { FormField, NodeConfigRendererProps } from '../shared/types';
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper'; import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { FormFieldOptionsEditor } from './FormFieldOptionsEditor'; import { FormFieldOptionsEditor } from './FormFieldOptionsEditor';
import { import {
deriveFormFieldPayloadKey, deriveFormFieldPayloadKey,
@ -21,7 +19,7 @@ import { useLanguage } from '../../../../providers/language/LanguageContext';
export const FormNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => { export const FormNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const ctx = useWorkflowDataFlow(); const ctx = useAutomation2DataFlow();
const fieldTypeOptions = ctx?.formFieldTypes?.length const fieldTypeOptions = ctx?.formFieldTypes?.length
? ctx.formFieldTypes ? ctx.formFieldTypes
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' })); : FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Helpers for optional select/multiselect rows on workflow form field definitions. * Helpers for optional select/multiselect rows on workflow form field definitions.
*/ */

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { FormNodeConfig } from './FormNodeConfig'; export { FormNodeConfig } from './FormNodeConfig';
export { FormFieldOptionsEditor } from './FormFieldOptionsEditor'; export { FormFieldOptionsEditor } from './FormFieldOptionsEditor';
export type { FormFieldOptionRow } from './formFieldOptionsUtils'; export type { FormFieldOptionRow } from './formFieldOptionsUtils';

View file

@ -1,15 +1,13 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Backend-driven case list for flow.switch (depends on value dataRef). * Backend-driven case list for flow.switch (depends on value dataRef).
*/ */
import React from 'react'; import React from 'react';
import type { FieldRendererProps } from './index'; import type { FieldRendererProps } from './index';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { isRef, type DataRef } from '../shared/dataRef'; import { isRef, type DataRef } from '../shared/dataRef';
import { toApiGraph } from '../shared/graphUtils'; import { toApiGraph } from '../shared/graphUtils';
import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowAutomationApi'; import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowApi';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
export interface SwitchCase { export interface SwitchCase {
@ -118,7 +116,7 @@ export const CaseListEditor: React.FC<FieldRendererProps> = ({
allParams, allParams,
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const dependsOn = const dependsOn =
param.frontendOptions && typeof param.frontendOptions === 'object' param.frontendOptions && typeof param.frontendOptions === 'object'
? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'value') ? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'value')
@ -159,7 +157,7 @@ export const CaseListEditor: React.FC<FieldRendererProps> = ({
if (dataFlow?.instanceId && dataFlow.request) { if (dataFlow?.instanceId && dataFlow.request) {
setLoading(true); setLoading(true);
fetchConditionMeta(dataFlow.request, { fetchConditionMeta(dataFlow.request, dataFlow.instanceId, {
graph: toApiGraph(dataFlow.nodes, dataFlow.connections), graph: toApiGraph(dataFlow.nodes, dataFlow.connections),
nodeId: dataFlow.currentNodeId, nodeId: dataFlow.currentNodeId,
ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path }, ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path },

View file

@ -1,13 +1,14 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* clickupList hierarchical ClickUp list picker via connector browse API. * clickupList hierarchical ClickUp list picker via connector browse API.
*/ */
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
import { fetchBrowse, type BrowseEntry } from '../../../../api/workflowAutomationApi'; import {
import { fetchClickupList } from '../../../../api/clickupApi'; fetchBrowse,
fetchClickupList,
type BrowseEntry,
} from '../../../../api/workflowApi';
import type { FieldRendererProps } from './index'; import type { FieldRendererProps } from './index';
import { import {
clickupBrowseParentPath, clickupBrowseParentPath,
@ -73,7 +74,7 @@ export const ClickUpListPicker: React.FC<FieldRendererProps> = ({
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const res = await fetchBrowse(request, connectionReference, 'clickup', path); const res = await fetchBrowse(request, instanceId, connectionReference, 'clickup', path);
setItems(res.items); setItems(res.items);
setBrowsePath(res.path || path); setBrowsePath(res.path || path);
} catch (err: unknown) { } catch (err: unknown) {

View file

@ -1,15 +1,13 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Backend-driven condition editor for flow.ifElse (depends on Item dataRef). * Backend-driven condition editor for flow.ifElse (depends on Item dataRef).
*/ */
import React from 'react'; import React from 'react';
import type { FieldRendererProps } from './index'; import type { FieldRendererProps } from './index';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { isRef, type DataRef } from '../shared/dataRef'; import { isRef, type DataRef } from '../shared/dataRef';
import { toApiGraph } from '../shared/graphUtils'; import { toApiGraph } from '../shared/graphUtils';
import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowAutomationApi'; import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowApi';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
export interface StructuredCondition { export interface StructuredCondition {
@ -43,7 +41,7 @@ export const ConditionEditor: React.FC<FieldRendererProps> = ({
allParams, allParams,
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const dependsOn = const dependsOn =
param.frontendOptions && typeof param.frontendOptions === 'object' param.frontendOptions && typeof param.frontendOptions === 'object'
? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'Item') ? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'Item')
@ -85,7 +83,7 @@ export const ConditionEditor: React.FC<FieldRendererProps> = ({
if (dataFlow?.instanceId && dataFlow.request) { if (dataFlow?.instanceId && dataFlow.request) {
setLoading(true); setLoading(true);
fetchConditionMeta(dataFlow.request, { fetchConditionMeta(dataFlow.request, dataFlow.instanceId, {
graph: toApiGraph(dataFlow.nodes, dataFlow.connections), graph: toApiGraph(dataFlow.nodes, dataFlow.connections),
nodeId: dataFlow.currentNodeId, nodeId: dataFlow.currentNodeId,
ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path }, ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path },

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* One place to configure context.setContext rows: target key, then either * One place to configure context.setContext rows: target key, then either
* upstream picker, a fixed literal, or a human task. * upstream picker, a fixed literal, or a human task.
@ -7,7 +5,7 @@
import React from 'react'; import React from 'react';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { DataPicker } from '../shared/DataPicker'; import { DataPicker } from '../shared/DataPicker';
import { isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef'; import { isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef';
import type { FieldRendererProps } from './index'; import type { FieldRendererProps } from './index';
@ -176,7 +174,7 @@ const REMOVE_BTN: React.CSSProperties = {
export const ContextAssignmentsEditor: React.FC<FieldRendererProps> = ({ param, value, onChange, allParams }) => { export const ContextAssignmentsEditor: React.FC<FieldRendererProps> = ({ param, value, onChange, allParams }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const rows = normalizeRows(value, allParams); const rows = normalizeRows(value, allParams);
const [pickerRow, setPickerRow] = React.useState<number | null>(null); const [pickerRow, setPickerRow] = React.useState<number | null>(null);

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* ContextBuilderRenderer multi-select context binding for AI nodes. * ContextBuilderRenderer multi-select context binding for AI nodes.
* *
@ -13,7 +11,7 @@
import React from 'react'; import React from 'react';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { DataPicker } from '../shared/DataPicker'; import { DataPicker } from '../shared/DataPicker';
import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef'; import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef';
import type { FieldRendererProps } from './index'; import type { FieldRendererProps } from './index';
@ -54,7 +52,7 @@ const REMOVE_BTN: React.CSSProperties = {
export const ContextBuilderRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => { export const ContextBuilderRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const [pickerOpen, setPickerOpen] = React.useState(false); const [pickerOpen, setPickerOpen] = React.useState(false);
const dragIndex = React.useRef<number | null>(null); const dragIndex = React.useRef<number | null>(null);

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* DataRefRenderer Pick-not-Push attribute binding using the existing * DataRefRenderer Pick-not-Push attribute binding using the existing
* hierarchical DataPicker. * hierarchical DataPicker.
@ -12,14 +10,14 @@
import React from 'react'; import React from 'react';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { DataPicker } from '../shared/DataPicker'; import { DataPicker } from '../shared/DataPicker';
import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef'; import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef';
import type { FieldRendererProps } from './index'; import type { FieldRendererProps } from './index';
export const DataRefRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => { export const DataRefRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const [pickerOpen, setPickerOpen] = React.useState(false); const [pickerOpen, setPickerOpen] = React.useState(false);
const currentRef = isRef(value) ? (value as DataRef) : null; const currentRef = isRef(value) ? (value as DataRef) : null;

View file

@ -1,11 +1,9 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* FeatureInstancePicker renderer for frontendType="featureInstance". * FeatureInstancePicker renderer for frontendType="featureInstance".
* *
* Modeled on ConnectionPicker. Loads mandate-scoped FeatureInstances filtered * Modeled on ConnectionPicker. Loads mandate-scoped FeatureInstances filtered
* by `frontendOptions.featureCode` (e.g. "trustee", "redmine") via * by `frontendOptions.featureCode` (e.g. "trustee", "redmine") via
* GET /api/workflow-automation/options/feature.instance?featureCode=<code> * GET /api/workflows/{instanceId}/options/feature.instance?featureCode=<code>
* *
* Behavior matches the rest of the editor: * Behavior matches the rest of the editor:
* - 0 results -> hint to create a feature instance for this mandate * - 0 results -> hint to create a feature instance for this mandate
@ -44,7 +42,7 @@ export const FeatureInstancePicker: React.FC<FieldRendererProps> = ({
setLoading(true); setLoading(true);
setLoadError(null); setLoadError(null);
request({ request({
url: `/api/workflow-automation/options/feature.instance?featureCode=${encodeURIComponent(featureCode)}`, url: `/api/workflows/${instanceId}/options/feature.instance?featureCode=${encodeURIComponent(featureCode)}`,
method: 'get', method: 'get',
}) })
.then((res: unknown) => { .then((res: unknown) => {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* TemplateTextarea Freitext mit eingebetteten {{nodeId.path}} Tokens. * TemplateTextarea Freitext mit eingebetteten {{nodeId.path}} Tokens.
* Tokens werden zur Laufzeit von resolveParameterReferences aufgeloest (Gateway). * Tokens werden zur Laufzeit von resolveParameterReferences aufgeloest (Gateway).
@ -7,11 +5,11 @@
import React, { useCallback, useMemo, useRef, useState } from 'react'; import React, { useCallback, useMemo, useRef, useState } from 'react';
import type { FieldRendererProps } from './index'; import type { FieldRendererProps } from './index';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { DataPicker } from '../shared/DataPicker'; import { DataPicker } from '../shared/DataPicker';
import { formatRefLabel, isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef'; import { formatRefLabel, isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
const _TEMPLATE_TOKEN_RE = /\{\{\s*([^}]+?)\s*\}\}/g; const _TEMPLATE_TOKEN_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
@ -62,7 +60,7 @@ function _parseTokensInTemplate(
export const TemplateTextareaRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => { export const TemplateTextareaRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const [pickerOpen, setPickerOpen] = useState(false); const [pickerOpen, setPickerOpen] = useState(false);

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* userFileFolder FormGeneratorTree embedded: combobox-style trigger + expandable tree. * userFileFolder FormGeneratorTree embedded: combobox-style trigger + expandable tree.
*/ */

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { import {
clickupBrowseParentPath, clickupBrowseParentPath,

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** Parse virtual ClickUp list paths: /team/{teamId}/list/{listId} */ /** Parse virtual ClickUp list paths: /team/{teamId}/list/{listId} */
const LIST_PATH_RE = /^\/team\/([^/]+)\/list\/([^/]+)$/; const LIST_PATH_RE = /^\/team\/([^/]+)\/list\/([^/]+)$/;

View file

@ -1,15 +1,13 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Generic FrontendType renderer registry. * Generic FrontendType renderer registry.
* Maps frontendType strings to React components. * Maps frontendType strings to React components.
*/ */
import type { ComponentType } from 'react'; import type { ComponentType } from 'react';
import type { NodeTypeParameter } from '../../../../api/workflowAutomationApi'; import type { NodeTypeParameter } from '../../../../api/workflowApi';
import type { ApiRequestFunction } from '../../../../api/workflowAutomationApi'; import type { ApiRequestFunction } from '../../../../api/workflowApi';
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper'; import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { FormFieldOptionsEditor } from '../form/FormFieldOptionsEditor'; import { FormFieldOptionsEditor } from '../form/FormFieldOptionsEditor';
import { import {
deriveFormFieldPayloadKey, deriveFormFieldPayloadKey,
@ -48,7 +46,7 @@ import {
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
import { toApiGraph } from '../shared/graphUtils'; import { toApiGraph } from '../shared/graphUtils';
import { postUpstreamPaths } from '../../../../api/workflowAutomationApi'; import { postUpstreamPaths } from '../../../../api/workflowApi';
import type { CanvasNode } from '../../editor/FlowCanvas'; import type { CanvasNode } from '../../editor/FlowCanvas';
import { DataRefRenderer } from './DataRefRenderer'; import { DataRefRenderer } from './DataRefRenderer';
import { ContextBuilderRenderer } from './ContextBuilderRenderer'; import { ContextBuilderRenderer } from './ContextBuilderRenderer';
@ -302,7 +300,7 @@ const HiddenInput: React.FC<FieldRendererProps> = () => null;
const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange, instanceId, request }) => { const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange, instanceId, request }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const [connections, setConnections] = React.useState<Array<{ id: string; label: string }>>([]); const [connections, setConnections] = React.useState<Array<{ id: string; label: string }>>([]);
const [loadError, setLoadError] = React.useState<string | null>(null); const [loadError, setLoadError] = React.useState<string | null>(null);
const [upstreamBindOptions, setUpstreamBindOptions] = React.useState<Array<{ key: string; label: string; ref: unknown }>>([]); const [upstreamBindOptions, setUpstreamBindOptions] = React.useState<Array<{ key: string; label: string; ref: unknown }>>([]);
@ -312,7 +310,7 @@ const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange
if (!instanceId || !request) return; if (!instanceId || !request) return;
const qs = authority ? `?authority=${encodeURIComponent(authority)}` : ''; const qs = authority ? `?authority=${encodeURIComponent(authority)}` : '';
setLoadError(null); setLoadError(null);
request({ url: `/api/workflow-automation/options/user.connection${qs}`, method: 'get' }) request({ url: `/api/workflows/${instanceId}/options/user.connection${qs}`, method: 'get' })
.then((res: unknown) => { .then((res: unknown) => {
const data = res as { options?: Array<{ value: string; label: string }> }; const data = res as { options?: Array<{ value: string; label: string }> };
setConnections((data?.options || []).map((o) => ({ id: o.value, label: o.label }))); setConnections((data?.options || []).map((o) => ({ id: o.value, label: o.label })));
@ -330,7 +328,7 @@ const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange
return; return;
} }
const graph = toApiGraph(dataFlow.nodes as CanvasNode[], dataFlow.connections); const graph = toApiGraph(dataFlow.nodes as CanvasNode[], dataFlow.connections);
postUpstreamPaths(request, graph, dataFlow.currentNodeId) postUpstreamPaths(request, instanceId, graph, dataFlow.currentNodeId)
.then(({ paths }) => { .then(({ paths }) => {
const opts = paths const opts = paths
.filter( .filter(
@ -645,7 +643,7 @@ const SharepointPathPicker: React.FC<FieldRendererProps> = ({ param, value, onCh
const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }) => { const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const ctx = useWorkflowDataFlow(); const ctx = useAutomation2DataFlow();
const fieldTypeOptions = ctx?.formFieldTypes?.length const fieldTypeOptions = ctx?.formFieldTypes?.length
? ctx.formFieldTypes ? ctx.formFieldTypes
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' })); : FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));

View file

@ -1,4 +1,2 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { ConditionEditor as IfElseNodeConfig } from '../frontendTypeRenderers/ConditionEditor'; export { ConditionEditor as IfElseNodeConfig } from '../frontendTypeRenderers/ConditionEditor';
export type { StructuredCondition } from '../frontendTypeRenderers/ConditionEditor'; export type { StructuredCondition } from '../frontendTypeRenderers/ConditionEditor';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Loop node config - Datenquelle für Iteration mit benutzerfreundlichen Labels. * Loop node config - Datenquelle für Iteration mit benutzerfreundlichen Labels.
* Z.B. für jedes Formularfeld, jede Datei aus Upload, jede E-Mail aus Suche. * Z.B. für jedes Formularfeld, jede Datei aus Upload, jede E-Mail aus Suche.
@ -9,7 +7,7 @@ import React from 'react';
import type { NodeConfigRendererProps } from '../shared/types'; import type { NodeConfigRendererProps } from '../shared/types';
import { LoopItemsSelect } from '../shared/LoopItemsSelect'; import { LoopItemsSelect } from '../shared/LoopItemsSelect';
import { createValue, isRef, isValue } from '../shared/dataRef'; import { createValue, isRef, isValue } from '../shared/dataRef';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
export const LoopNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => { export const LoopNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
const value = params.items; const value = params.items;

View file

@ -1,3 +1 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { LoopNodeConfig } from './LoopNodeConfig'; export { LoopNodeConfig } from './LoopNodeConfig';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Shared mapping: file type options (accept strings) MIME types. * Shared mapping: file type options (accept strings) MIME types.
* Used by Upload node config (allowed types) and IfElse node (mimeType comparison). * Used by Upload node config (allowed types) and IfElse node (mimeType comparison).

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Small label for workflow nodes that consume AI credits (LLM calls). * Small label for workflow nodes that consume AI credits (LLM calls).
*/ */

View file

@ -1,7 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Workflow Flow Editor - Schema-based Data Picker. * Automation2 Flow Editor - Schema-based Data Picker.
* Builds pickable paths from portTypeCatalog + node outputPorts, or from * Builds pickable paths from portTypeCatalog + node outputPorts, or from
* outputPorts[n].dataPickOptions when the backend defines an explicit list (authoritative). * outputPorts[n].dataPickOptions when the backend defines an explicit list (authoritative).
* Resolves Transit chains to show the real upstream schema. * Resolves Transit chains to show the real upstream schema.
@ -11,10 +9,10 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { createRef, createSystemVar, type DataRef, type SystemVarRef, isCompatible } from './dataRef'; import { createRef, createSystemVar, type DataRef, type SystemVarRef, isCompatible } from './dataRef';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import type { DataPickOption, GraphDataSources, GraphDefinedSchemaRef, NodeType, PortField, PortSchema } from '../../../../api/workflowAutomationApi'; import type { DataPickOption, GraphDataSources, GraphDefinedSchemaRef, NodeType, PortField, PortSchema } from '../../../../api/workflowApi';
import { fetchGraphDataSources } from '../../../../api/workflowAutomationApi'; import { fetchGraphDataSources } from '../../../../api/workflowApi';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
@ -270,7 +268,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
// Default: when the consumer declares an expected type, show only compatible // Default: when the consumer declares an expected type, show only compatible
// candidates ("strict" mode). User can override per-session via the toggle. // candidates ("strict" mode). User can override per-session via the toggle.
const [strictFilter, setStrictFilter] = useState<boolean>(Boolean(expectedParamType)); const [strictFilter, setStrictFilter] = useState<boolean>(Boolean(expectedParamType));
const ctx = useWorkflowDataFlow(); const ctx = useAutomation2DataFlow();
// NOTE: All hooks must be called unconditionally on every render to satisfy // NOTE: All hooks must be called unconditionally on every render to satisfy
// the Rules of Hooks. The `if (!open) return null;` early-return therefore // the Rules of Hooks. The `if (!open) return null;` early-return therefore
@ -305,7 +303,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
if (scopeFetchKey.current === key) return; // already fetched for this state if (scopeFetchKey.current === key) return; // already fetched for this state
scopeFetchKey.current = key; scopeFetchKey.current = key;
const nodeShapes = (ctx.nodes ?? []).map((n) => ({ id: n.id, type: n.type })); const nodeShapes = (ctx.nodes ?? []).map((n) => ({ id: n.id, type: n.type }));
fetchGraphDataSources(ctx.request, ctx.currentNodeId, nodeShapes, connections) fetchGraphDataSources(ctx.request, ctx.instanceId, ctx.currentNodeId, nodeShapes, connections)
.then(setScopeData) .then(setScopeData)
.catch(() => setScopeData(null)); .catch(() => setScopeData(null));
}, [open, ctx?.instanceId, ctx?.request, ctx?.currentNodeId, connections, nodesRaw]); }, [open, ctx?.instanceId, ctx?.request, ctx?.currentNodeId, connections, nodesRaw]);
@ -363,10 +361,10 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="workflowDataPickerTitle" aria-labelledby="automation2DataPickerTitle"
> >
<div className={styles.dataPickerHeader}> <div className={styles.dataPickerHeader}>
<h4 className={styles.dataPickerTitle} id="workflowDataPickerTitle"> <h4 className={styles.dataPickerTitle} id="automation2DataPickerTitle">
{t('Datenquelle wählen')} {t('Datenquelle wählen')}
{expectedParamType && ( {expectedParamType && (
<span <span

View file

@ -1,7 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Workflow Flow Editor - Field that supports node reference only (no static value). * Automation2 Flow Editor - Field that supports node reference only (no static value).
*/ */
import React, { useState } from 'react'; import React, { useState } from 'react';
@ -14,8 +12,8 @@ import {
} from './dataRef'; } from './dataRef';
import { RefSourceSelect } from './RefSourceSelect'; import { RefSourceSelect } from './RefSourceSelect';
import { DataPicker } from './DataPicker'; import { DataPicker } from './DataPicker';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
@ -41,7 +39,7 @@ export const DynamicValueField: React.FC<DynamicValueFieldProps> = ({ paramKey,
variant = 'picker', variant = 'picker',
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const [pickerOpen, setPickerOpen] = useState(false); const [pickerOpen, setPickerOpen] = useState(false);
const ref: DataRef | null = isRef(value) ? value : null; const ref: DataRef | null = isRef(value) ? value : null;

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Text/number field: Quelle wählen Statisch (Eingabe) oder Kontext-Ref. * Text/number field: Quelle wählen Statisch (Eingabe) oder Kontext-Ref.
* Textfeld nur bei Statisch, nicht bei Kontext-Referenz. * Textfeld nur bei Statisch, nicht bei Kontext-Referenz.
@ -11,9 +9,9 @@ import {
shouldShowStaticControl, shouldShowStaticControl,
type PathPickMode, type PathPickMode,
} from './RefSourceSelect'; } from './RefSourceSelect';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { isRef, isValue, createValue } from './dataRef'; import { isRef, isValue, createValue } from './dataRef';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
@ -50,7 +48,7 @@ export const HybridStaticRefField: React.FC<HybridStaticRefFieldProps> = ({ labe
pathPickMode = 'default', pathPickMode = 'default',
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
const hasSources = const hasSources =
dataFlow && dataFlow &&
dataFlow.getAvailableSourceIds().some((id) => { dataFlow.getAvailableSourceIds().some((id) => {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Loop node - Datenquelle für Iteration mit benutzerfreundlichen Labels. * Loop node - Datenquelle für Iteration mit benutzerfreundlichen Labels.
* Zeigt nur iterierbare Quellen: Arrays und Objekte (Formularfelder {name, value}). * Zeigt nur iterierbare Quellen: Arrays und Objekte (Formularfelder {name, value}).
@ -8,8 +6,8 @@
import React from 'react'; import React from 'react';
import { createRef, isRef, type DataRef } from './dataRef'; import { createRef, isRef, type DataRef } from './dataRef';
import { refToOptionValue, optionValueToRef } from './RefSourceSelect'; import { refToOptionValue, optionValueToRef } from './RefSourceSelect';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
@ -163,7 +161,7 @@ export const LoopItemsSelect: React.FC<LoopItemsSelectProps> = ({ value,
placeholder, placeholder,
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
if (!dataFlow) return null; if (!dataFlow) return null;
const sourceIds = dataFlow.getAvailableSourceIds(); const sourceIds = dataFlow.getAvailableSourceIds();

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Inline dropdown to select a data source (node + path) - no popup. * Inline dropdown to select a data source (node + path) - no popup.
* Form nodes (trigger.form / input.form): only payload.<fieldName> paths (no duplicate tree). * Form nodes (trigger.form / input.form): only payload.<fieldName> paths (no duplicate tree).
@ -7,7 +5,7 @@
import React from 'react'; import React from 'react';
import { createRef, isRef, isValue, createValue, type DataRef } from './dataRef'; import { createRef, isRef, isValue, createValue, type DataRef } from './dataRef';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
/** How to build path options for StatischKontextSelect / RefSourceSelect. */ /** How to build path options for StatischKontextSelect / RefSourceSelect. */
@ -240,7 +238,7 @@ export const StatischKontextSelect: React.FC<StatischKontextSelectProps> = ({
pathPickMode = 'default', pathPickMode = 'default',
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
if (!dataFlow) return null; if (!dataFlow) return null;
const sourceIds = dataFlow.getAvailableSourceIds(); const sourceIds = dataFlow.getAvailableSourceIds();
@ -318,7 +316,7 @@ export const RefSourceSelect: React.FC<RefSourceSelectProps> = ({
pathPickMode = 'default', pathPickMode = 'default',
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const dataFlow = useWorkflowDataFlow(); const dataFlow = useAutomation2DataFlow();
if (!dataFlow) return null; if (!dataFlow) return null;
const sourceIds = dataFlow.getAvailableSourceIds(); const sourceIds = dataFlow.getAvailableSourceIds();

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* RequiredAttributePicker Phase-4 Schicht-4 binding affordance for * RequiredAttributePicker Phase-4 Schicht-4 binding affordance for
* required parameters of a Schicht-3 Adapter (Editor-Node). * required parameters of a Schicht-3 Adapter (Editor-Node).
@ -17,11 +15,11 @@
*/ */
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { DataPicker } from './DataPicker'; import { DataPicker } from './DataPicker';
import { createRef, formatRefLabel, isRef, type DataRef, type SystemVarRef } from './dataRef'; import { createRef, formatRefLabel, isRef, type DataRef, type SystemVarRef } from './dataRef';
import { findSourceCandidates, strictlyCompatible, type SourceCandidate } from './paramValidation'; import { findSourceCandidates, strictlyCompatible, type SourceCandidate } from './paramValidation';
import styles from '../../editor/WorkflowFlowEditor.module.css'; import styles from '../../editor/Automation2FlowEditor.module.css';
import { useLanguage } from '../../../../providers/language/LanguageContext'; import { useLanguage } from '../../../../providers/language/LanguageContext';
@ -46,7 +44,7 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
description, description,
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const ctx = useWorkflowDataFlow(); const ctx = useAutomation2DataFlow();
const [pickerOpen, setPickerOpen] = useState(false); const [pickerOpen, setPickerOpen] = useState(false);
const consumerNodeId = ctx?.currentNodeId ?? ''; const consumerNodeId = ctx?.currentNodeId ?? '';

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Category icons for node types * Category icons for node types
*/ */

View file

@ -1,5 +1,3 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Shared condition operators for If/Else and Switch nodes. * Shared condition operators for If/Else and Switch nodes.
* Type-dependent: number gets <, >, etc.; string gets contains, equals, etc. * Type-dependent: number gets <, >, etc.; string gets contains, equals, etc.

View file

@ -1,7 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Workflow Flow Editor - Constants * Automation2 Flow Editor - Constants
* Category ordering for node sidebar. * Category ordering for node sidebar.
*/ */

View file

@ -1,7 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/** /**
* Workflow Flow Editor - Graph helpers for data flow (ancestors, topo order). * Automation2 Flow Editor - Graph helpers for data flow (ancestors, topo order).
*/ */
import type { CanvasNode, CanvasConnection } from '../../editor/FlowCanvas'; import type { CanvasNode, CanvasConnection } from '../../editor/FlowCanvas';

Some files were not shown because too many files have changed in this diff Show more