Merge branch 'int'
All checks were successful
Deploy Nyla Frontend to Production / deploy (push) Successful in 51s

This commit is contained in:
ValueOn AG 2026-06-09 23:55:04 +02:00
commit 5f47dd395c
468 changed files with 4361 additions and 19424 deletions

5
.gitignore vendored
View file

@ -31,4 +31,7 @@ dist-ssr
.cursorignore
# 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,3 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/**
* Configuration reads mandatory env vars set by .env (copied from config/env-*.env by CI).
*

View file

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

2
env.d.ts vendored
View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/**
* App.tsx
*
@ -43,7 +45,7 @@ import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandat
import { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards';
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing';
import { AutomationsDashboardPage } from './pages/AutomationsDashboardPage';
import { WorkflowAutomationPage } from './pages/workflowAutomation/WorkflowAutomationHubPage';
import { RagInventoryPage } from './pages/RagInventoryPage';
import { ComplianceAuditPage } from './pages/ComplianceAuditPage';
function App() {
@ -124,9 +126,9 @@ function App() {
</Route>
{/* ============================================== */}
{/* AUTOMATIONS DASHBOARD */}
{/* WORKFLOW AUTOMATION (System-Komponente) */}
{/* ============================================== */}
<Route path="automations" element={<AutomationsDashboardPage />} />
<Route path="workflow-automation" element={<WorkflowAutomationPage />} />
{/* ============================================== */}
{/* RAG INVENTORY */}
@ -137,6 +139,7 @@ function App() {
<Route path="pek" element={<Navigate to="/" replace />} />
<Route path="speech" element={<Navigate to="/" replace />} />
{/* ============================================== */}
{/* FEATURE-INSTANZ ROUTES */}
{/* /mandates/:mandateId/:featureCode/:instanceId */}
@ -170,13 +173,9 @@ function App() {
<Route path="templates" element={<FeatureViewPage view="templates" />} />
<Route path="logs" element={<FeatureViewPage view="logs" />} />
{/* Workspace + Automation2 Editor */}
{/* Workspace 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 */}
<Route path="sessions" element={<FeatureViewPage view="sessions" />} />
<Route path="settings" element={<FeatureViewPage view="settings" />} />

View file

@ -1,25 +1,10 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
// api.ts
import axios from 'axios';
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from './utils/csrfUtils';
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.
* URL pattern: /mandates/:mandateId/:featureCode/:instanceId/...
@ -44,52 +29,25 @@ const getContextFromUrl = (): { mandateId?: string; instanceId?: string } => {
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({
baseURL: getApiBaseUrl(),
baseURL: _baseUrl,
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 },
});
// Add a request interceptor to add the auth token, context headers, and log backend IP
// Add a request interceptor to add the auth token, context headers
api.interceptors.request.use(
async (config) => {
// 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
// Add auth token if available (otherwise httpOnly cookies are used automatically)
const authToken = localStorage.getItem('authToken');
if (authToken && config.headers) {
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

View file

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

View file

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

View file

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

135
src/api/clickupApi.ts Normal file
View file

@ -0,0 +1,135 @@
// 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,3 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
import api from '../api';
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils';
import { ApiRequestOptions } from '../hooks/useApi';

View file

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

View file

@ -1,3 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/**
* Features API
*
@ -170,19 +172,11 @@ export async function fetchMyFeatures(): Promise<FeaturesMyResponse> {
}
try {
console.log('📡 featuresApi: Fetching /api/features/my');
const response = await api.get<FeaturesMyResponse>('/api/features/my');
// Get the actual data (response.data contains the FeaturesMyResponse)
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;
} catch (error) {
console.error('❌ featuresApi: Error fetching features:', error);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +0,0 @@
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,3 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/**
* AccessLevelSelect
*

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,6 @@
export { Automation2FlowEditor, Automation2FlowEditor as FlowEditor } from './editor/Automation2FlowEditor';
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
export { WorkflowFlowEditor, WorkflowFlowEditor as FlowEditor } from './editor/WorkflowFlowEditor';
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 type { CanvasNode, CanvasConnection, CanvasStickyNote, FlowCanvasHandle, FlowCanvasViewportEditState } from './editor/FlowCanvas';

View file

@ -1,3 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/**
* 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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
// Copyright (c) 2026 PowerOn AG
// All rights reserved.
/**
* 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.
@ -7,7 +9,7 @@ import React from 'react';
import type { NodeConfigRendererProps } from '../shared/types';
import { LoopItemsSelect } from '../shared/LoopItemsSelect';
import { createValue, isRef, isValue } from '../shared/dataRef';
import styles from '../../editor/Automation2FlowEditor.module.css';
import styles from '../../editor/WorkflowFlowEditor.module.css';
export const LoopNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
const value = params.items;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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