updated feature automation and feature chat playground routes
This commit is contained in:
parent
0dbcad771b
commit
df1344c228
5 changed files with 147 additions and 106 deletions
|
|
@ -102,12 +102,15 @@ function App() {
|
||||||
<Route path="gdpr" element={<GDPRPage />} />
|
<Route path="gdpr" element={<GDPRPage />} />
|
||||||
|
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
{/* WORKFLOWS ROUTES (global) */}
|
{/* WORKFLOWS ROUTES (deprecated - redirect to /) */}
|
||||||
|
{/* Workflows are accessed via feature routes: */}
|
||||||
|
{/* /mandates/:mandateId/chatplayground/:id/workflows */}
|
||||||
|
{/* /mandates/:mandateId/automation/:id/definitions */}
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
<Route path="workflows">
|
<Route path="workflows">
|
||||||
<Route path="playground" element={<PlaygroundPage />} />
|
<Route path="playground" element={<PlaygroundPage />} />
|
||||||
<Route path="list" element={<WorkflowsPage />} />
|
<Route path="list" element={<Navigate to="/" replace />} />
|
||||||
<Route path="automations" element={<AutomationDefinitionsView />} />
|
<Route path="automations" element={<Navigate to="/" replace />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
|
|
|
||||||
|
|
@ -69,17 +69,24 @@ export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<an
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all workflows for the current user
|
* Fetch workflows - feature-scoped or global (legacy)
|
||||||
* Endpoint: GET /api/workflows/
|
* When apiBaseUrl is provided: GET {apiBaseUrl}/workflows
|
||||||
|
* Otherwise (legacy): GET /api/workflows/
|
||||||
*/
|
*/
|
||||||
export async function fetchWorkflows(request: ApiRequestFunction): Promise<Workflow[]> {
|
export async function fetchWorkflows(
|
||||||
console.log('📤 fetchWorkflows: Making API request to /api/workflows/');
|
request: ApiRequestFunction,
|
||||||
|
params?: { pagination?: string },
|
||||||
|
apiBaseUrl?: string
|
||||||
|
): Promise<Workflow[] | { items: Workflow[]; pagination?: any }> {
|
||||||
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows/';
|
||||||
|
console.log('📤 fetchWorkflows: Making API request to', url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await request({
|
const requestConfig: any = { url, method: 'get' as const };
|
||||||
url: '/api/workflows/',
|
if (params?.pagination) {
|
||||||
method: 'get'
|
requestConfig.params = { pagination: params.pagination };
|
||||||
});
|
}
|
||||||
|
const data = await request(requestConfig);
|
||||||
|
|
||||||
console.log('📥 fetchWorkflows: API response:', data);
|
console.log('📥 fetchWorkflows: API response:', data);
|
||||||
|
|
||||||
|
|
@ -129,30 +136,30 @@ export async function fetchWorkflows(request: ApiRequestFunction): Promise<Workf
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a single workflow by ID
|
* Fetch a single workflow by ID
|
||||||
* Endpoint: GET /api/workflows/{workflowId}
|
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}
|
||||||
|
* Otherwise: GET /api/workflows/{workflowId}
|
||||||
*/
|
*/
|
||||||
export async function fetchWorkflow(
|
export async function fetchWorkflow(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string
|
workflowId: string,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<Workflow & { messages?: WorkflowMessage[]; logs?: WorkflowLog[] }> {
|
): Promise<Workflow & { messages?: WorkflowMessage[]; logs?: WorkflowLog[] }> {
|
||||||
return await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}` : `/api/workflows/${workflowId}`;
|
||||||
url: `/api/workflows/${workflowId}`,
|
return await request({ url, method: 'get' });
|
||||||
method: 'get'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch workflow status (lightweight status check)
|
* Fetch workflow status (lightweight status check)
|
||||||
* Endpoint: GET /api/workflows/{workflowId}/status
|
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/status
|
||||||
|
* Otherwise: GET /api/workflows/{workflowId}/status
|
||||||
*/
|
*/
|
||||||
export async function fetchWorkflowStatus(
|
export async function fetchWorkflowStatus(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string
|
workflowId: string,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<Workflow | { status: string } | null> {
|
): Promise<Workflow | { status: string } | null> {
|
||||||
const data = await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/status` : `/api/workflows/${workflowId}/status`;
|
||||||
url: `/api/workflows/${workflowId}/status`,
|
const data = await request({ url, method: 'get' });
|
||||||
method: 'get'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
if (data.status) {
|
if (data.status) {
|
||||||
|
|
@ -166,20 +173,18 @@ export async function fetchWorkflowStatus(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch workflow messages
|
* Fetch workflow messages
|
||||||
* Endpoint: GET /api/workflows/{workflowId}/messages
|
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/messages
|
||||||
* Query params: messageId (optional) - fetch only newer messages
|
* Otherwise: GET /api/workflows/{workflowId}/messages
|
||||||
*/
|
*/
|
||||||
export async function fetchWorkflowMessages(
|
export async function fetchWorkflowMessages(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
messageId?: string
|
messageId?: string,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<WorkflowMessage[]> {
|
): Promise<WorkflowMessage[]> {
|
||||||
const params = messageId ? { messageId } : undefined;
|
const params = messageId ? { messageId } : undefined;
|
||||||
const data = await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/messages` : `/api/workflows/${workflowId}/messages`;
|
||||||
url: `/api/workflows/${workflowId}/messages`,
|
const data = await request({ url, method: 'get', params });
|
||||||
method: 'get',
|
|
||||||
params
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -199,20 +204,18 @@ export async function fetchWorkflowMessages(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch workflow logs
|
* Fetch workflow logs
|
||||||
* Endpoint: GET /api/workflows/{workflowId}/logs
|
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/logs
|
||||||
* Query params: logId (optional) - fetch only newer logs
|
* Otherwise: GET /api/workflows/{workflowId}/logs
|
||||||
*/
|
*/
|
||||||
export async function fetchWorkflowLogs(
|
export async function fetchWorkflowLogs(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
logId?: string
|
logId?: string,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<WorkflowLog[]> {
|
): Promise<WorkflowLog[]> {
|
||||||
const params = logId ? { logId } : undefined;
|
const params = logId ? { logId } : undefined;
|
||||||
const data = await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/logs` : `/api/workflows/${workflowId}/logs`;
|
||||||
url: `/api/workflows/${workflowId}/logs`,
|
const data = await request({ url, method: 'get', params });
|
||||||
method: 'get',
|
|
||||||
params
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -232,7 +235,7 @@ export async function fetchWorkflowLogs(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch unified chat data (messages, logs, stats, documents)
|
* Fetch unified chat data (messages, logs, stats, documents)
|
||||||
* Endpoint: GET /api/chatplayground/{instanceId}/{workflowId}/chatData
|
* Endpoint: GET /api/chatplayground/{instanceId}/workflows/{workflowId}/chatData
|
||||||
* Query params: afterTimestamp (optional) - fetch only data created after this time
|
* Query params: afterTimestamp (optional) - fetch only data created after this time
|
||||||
*/
|
*/
|
||||||
export async function fetchChatData(
|
export async function fetchChatData(
|
||||||
|
|
@ -243,7 +246,7 @@ export async function fetchChatData(
|
||||||
): Promise<ChatDataResponse> {
|
): Promise<ChatDataResponse> {
|
||||||
const params = afterTimestamp ? { afterTimestamp: afterTimestamp.toString() } : undefined;
|
const params = afterTimestamp ? { afterTimestamp: afterTimestamp.toString() } : undefined;
|
||||||
const requestConfig = {
|
const requestConfig = {
|
||||||
url: `/api/chatplayground/${instanceId}/${workflowId}/chatData`,
|
url: `/api/chatplayground/${instanceId}/workflows/${workflowId}/chatData`,
|
||||||
method: 'get' as const,
|
method: 'get' as const,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
|
|
@ -372,7 +375,7 @@ export async function startWorkflowApi(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop a running workflow
|
* Stop a running workflow
|
||||||
* Endpoint: POST /api/chatplayground/{instanceId}/{workflowId}/stop
|
* Endpoint: POST /api/chatplayground/{instanceId}/workflows/{workflowId}/stop
|
||||||
*/
|
*/
|
||||||
export async function stopWorkflowApi(
|
export async function stopWorkflowApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
|
|
@ -380,52 +383,53 @@ export async function stopWorkflowApi(
|
||||||
workflowId: string
|
workflowId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await request({
|
await request({
|
||||||
url: `/api/chatplayground/${instanceId}/${workflowId}/stop`,
|
url: `/api/chatplayground/${instanceId}/workflows/${workflowId}/stop`,
|
||||||
method: 'post'
|
method: 'post'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update workflow properties
|
* Update workflow properties
|
||||||
* Endpoint: PUT /api/workflows/{workflowId}
|
* When apiBaseUrl provided: PUT {apiBaseUrl}/workflows/{workflowId}
|
||||||
|
* Otherwise: PUT /api/workflows/{workflowId}
|
||||||
*/
|
*/
|
||||||
export async function updateWorkflowApi(
|
export async function updateWorkflowApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
updateData: Partial<{ name: string; description?: string; tags?: string[] }>
|
updateData: Partial<{ name: string; description?: string; tags?: string[] }>,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<Workflow> {
|
): Promise<Workflow> {
|
||||||
return await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}` : `/api/workflows/${workflowId}`;
|
||||||
url: `/api/workflows/${workflowId}`,
|
return await request({ url, method: 'put', data: updateData });
|
||||||
method: 'put',
|
|
||||||
data: updateData
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a workflow and all associated data
|
* Delete a workflow and all associated data
|
||||||
* Endpoint: DELETE /api/workflows/{workflowId}
|
* When apiBaseUrl provided: DELETE {apiBaseUrl}/workflows/{workflowId}
|
||||||
|
* Otherwise: DELETE /api/workflows/{workflowId}
|
||||||
*/
|
*/
|
||||||
export async function deleteWorkflowApi(
|
export async function deleteWorkflowApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string
|
workflowId: string,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}` : `/api/workflows/${workflowId}`;
|
||||||
url: `/api/workflows/${workflowId}`,
|
await request({ url, method: 'delete' });
|
||||||
method: 'delete'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete multiple workflows
|
* Delete multiple workflows
|
||||||
|
* When apiBaseUrl provided, uses feature-scoped delete endpoint
|
||||||
*/
|
*/
|
||||||
export async function deleteWorkflowsApi(
|
export async function deleteWorkflowsApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowIds: string[]
|
workflowIds: string[],
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Delete workflows one by one since there's no bulk delete endpoint
|
const base = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows';
|
||||||
const deletePromises = workflowIds.map(workflowId =>
|
const deletePromises = workflowIds.map(workflowId =>
|
||||||
request({
|
request({
|
||||||
url: `/api/workflows/${workflowId}`,
|
url: `${base}/${workflowId}`,
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(`Failed to delete workflow ${workflowId}:`, error);
|
console.error(`Failed to delete workflow ${workflowId}:`, error);
|
||||||
|
|
@ -438,33 +442,33 @@ export async function deleteWorkflowsApi(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a message from a workflow
|
* Delete a message from a workflow
|
||||||
* Endpoint: DELETE /api/workflows/{workflowId}/messages/{messageId}
|
* When apiBaseUrl provided: DELETE {apiBaseUrl}/workflows/{workflowId}/messages/{messageId}
|
||||||
|
* Otherwise: DELETE /api/workflows/{workflowId}/messages/{messageId}
|
||||||
*/
|
*/
|
||||||
export async function deleteMessageApi(
|
export async function deleteMessageApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
messageId: string
|
messageId: string,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/messages/${messageId}` : `/api/workflows/${workflowId}/messages/${messageId}`;
|
||||||
url: `/api/workflows/${workflowId}/messages/${messageId}`,
|
await request({ url, method: 'delete' });
|
||||||
method: 'delete'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a file reference from a message
|
* Delete a file reference from a message
|
||||||
* Endpoint: DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}
|
* When apiBaseUrl provided: DELETE {apiBaseUrl}/workflows/{workflowId}/messages/{messageId}/files/{fileId}
|
||||||
|
* Otherwise: DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}
|
||||||
*/
|
*/
|
||||||
export async function deleteFileFromMessageApi(
|
export async function deleteFileFromMessageApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
messageId: string,
|
messageId: string,
|
||||||
fileId: string
|
fileId: string,
|
||||||
|
apiBaseUrl?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await request({
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/messages/${messageId}/files/${fileId}` : `/api/workflows/${workflowId}/messages/${messageId}/files/${fileId}`;
|
||||||
url: `/api/workflows/${workflowId}/messages/${messageId}/files/${fileId}`,
|
await request({ url, method: 'delete' });
|
||||||
method: 'delete'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,17 @@ export interface PaginationParams {
|
||||||
search?: string;
|
search?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workflows list hook
|
/** Get apiBaseUrl from instanceId and featureCode for feature-scoped workflow APIs */
|
||||||
export function useUserWorkflows() {
|
export function getWorkflowApiBaseUrl(instanceId: string | undefined, featureCode: string | undefined): string | undefined {
|
||||||
|
if (!instanceId || !featureCode) return undefined;
|
||||||
|
if (featureCode === 'chatplayground') return `/api/chatplayground/${instanceId}`;
|
||||||
|
if (featureCode === 'automation') return `/api/automations/${instanceId}`;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workflows list hook - pass instanceId and featureCode when in feature context for feature-scoped API
|
||||||
|
export function useUserWorkflows(options?: { instanceId?: string; featureCode?: string }) {
|
||||||
|
const apiBaseUrl = getWorkflowApiBaseUrl(options?.instanceId, options?.featureCode);
|
||||||
const [workflows, setWorkflows] = useState<UserWorkflow[]>([]);
|
const [workflows, setWorkflows] = useState<UserWorkflow[]>([]);
|
||||||
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
|
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
|
||||||
const [permissions, setPermissions] = useState<UserPermissions | null>(null);
|
const [permissions, setPermissions] = useState<UserPermissions | null>(null);
|
||||||
|
|
@ -113,16 +122,11 @@ export function useUserWorkflows() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: any;
|
let data: any;
|
||||||
|
const url = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows/';
|
||||||
if (Object.keys(requestParams).length > 0) {
|
if (Object.keys(requestParams).length > 0) {
|
||||||
// Use request for paginated queries
|
data = await request({ url, method: 'get', params: requestParams });
|
||||||
data = await request({
|
|
||||||
url: '/api/workflows/',
|
|
||||||
method: 'get',
|
|
||||||
params: requestParams
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Use API function for simple queries
|
data = await fetchWorkflowsApi(request, undefined, apiBaseUrl);
|
||||||
data = await fetchWorkflowsApi(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle paginated response
|
// Handle paginated response
|
||||||
|
|
@ -164,7 +168,7 @@ export function useUserWorkflows() {
|
||||||
setWorkflows([]);
|
setWorkflows([]);
|
||||||
setPagination(null);
|
setPagination(null);
|
||||||
}
|
}
|
||||||
}, [request]);
|
}, [request, apiBaseUrl]);
|
||||||
|
|
||||||
// Optimistically remove a workflow from the local state
|
// Optimistically remove a workflow from the local state
|
||||||
const removeOptimistically = (workflowId: string) => {
|
const removeOptimistically = (workflowId: string) => {
|
||||||
|
|
@ -185,13 +189,13 @@ export function useUserWorkflows() {
|
||||||
// Fetch a single workflow by ID
|
// Fetch a single workflow by ID
|
||||||
const fetchWorkflowById = useCallback(async (workflowId: string): Promise<UserWorkflow | null> => {
|
const fetchWorkflowById = useCallback(async (workflowId: string): Promise<UserWorkflow | null> => {
|
||||||
try {
|
try {
|
||||||
const workflow = await fetchWorkflowByIdApi(request, workflowId);
|
const workflow = await fetchWorkflowByIdApi(request, workflowId, apiBaseUrl);
|
||||||
return workflow as UserWorkflow | null;
|
return workflow as UserWorkflow | null;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error fetching workflow by ID:', error);
|
console.error('Error fetching workflow by ID:', error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [request]);
|
}, [request, apiBaseUrl]);
|
||||||
|
|
||||||
// Generate edit fields from attributes dynamically
|
// Generate edit fields from attributes dynamically
|
||||||
const generateEditFieldsFromAttributes = useCallback((): Array<{
|
const generateEditFieldsFromAttributes = useCallback((): Array<{
|
||||||
|
|
@ -375,8 +379,9 @@ export function useUserWorkflows() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workflow operations hook
|
// Workflow operations hook - pass instanceId and featureCode when in feature context for feature-scoped API
|
||||||
export function useWorkflowOperations() {
|
export function useWorkflowOperations(options?: { instanceId?: string; featureCode?: string }) {
|
||||||
|
const apiBaseUrl = getWorkflowApiBaseUrl(options?.instanceId, options?.featureCode);
|
||||||
const [startingWorkflow, setStartingWorkflow] = useState(false);
|
const [startingWorkflow, setStartingWorkflow] = useState(false);
|
||||||
const [stoppingWorkflows, setStoppingWorkflows] = useState<Set<string>>(new Set());
|
const [stoppingWorkflows, setStoppingWorkflows] = useState<Set<string>>(new Set());
|
||||||
const [deletingWorkflows, setDeletingWorkflows] = useState<Set<string>>(new Set());
|
const [deletingWorkflows, setDeletingWorkflows] = useState<Set<string>>(new Set());
|
||||||
|
|
@ -476,7 +481,7 @@ export function useWorkflowOperations() {
|
||||||
workflowId,
|
workflowId,
|
||||||
setDeletingWorkflows,
|
setDeletingWorkflows,
|
||||||
setDeleteError,
|
setDeleteError,
|
||||||
() => deleteWorkflowApi(request, workflowId),
|
() => deleteWorkflowApi(request, workflowId, apiBaseUrl),
|
||||||
{
|
{
|
||||||
default: 'Failed to delete workflow',
|
default: 'Failed to delete workflow',
|
||||||
notFound: 'Workflow not found or has already been deleted.',
|
notFound: 'Workflow not found or has already been deleted.',
|
||||||
|
|
@ -512,7 +517,7 @@ export function useWorkflowOperations() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete workflows one by one since there's no bulk delete endpoint
|
// Delete workflows one by one since there's no bulk delete endpoint
|
||||||
await deleteWorkflowsApi(request, workflowIds);
|
await deleteWorkflowsApi(request, workflowIds, apiBaseUrl);
|
||||||
|
|
||||||
// Add a small delay to ensure backend has time to process
|
// Add a small delay to ensure backend has time to process
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
await new Promise(resolve => setTimeout(resolve, 300));
|
||||||
|
|
@ -547,7 +552,7 @@ export function useWorkflowOperations() {
|
||||||
operationKey,
|
operationKey,
|
||||||
setDeletingMessages,
|
setDeletingMessages,
|
||||||
setDeleteMessageError,
|
setDeleteMessageError,
|
||||||
() => deleteMessageApi(request, workflowId, messageId),
|
() => deleteMessageApi(request, workflowId, messageId, apiBaseUrl),
|
||||||
{
|
{
|
||||||
default: 'Failed to delete message',
|
default: 'Failed to delete message',
|
||||||
notFound: 'Message not found or has already been deleted.',
|
notFound: 'Message not found or has already been deleted.',
|
||||||
|
|
@ -566,7 +571,7 @@ export function useWorkflowOperations() {
|
||||||
operationKey,
|
operationKey,
|
||||||
setDeletingFiles,
|
setDeletingFiles,
|
||||||
setDeleteFileError,
|
setDeleteFileError,
|
||||||
() => deleteFileFromMessageApi(request, workflowId, messageId, fileId),
|
() => deleteFileFromMessageApi(request, workflowId, messageId, fileId, apiBaseUrl),
|
||||||
{
|
{
|
||||||
default: 'Failed to delete file',
|
default: 'Failed to delete file',
|
||||||
notFound: 'File not found or has already been deleted.',
|
notFound: 'File not found or has already been deleted.',
|
||||||
|
|
@ -580,7 +585,7 @@ export function useWorkflowOperations() {
|
||||||
setEditingWorkflows(prev => new Set(prev).add(workflowId));
|
setEditingWorkflows(prev => new Set(prev).add(workflowId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedWorkflow = await updateWorkflowApi(request, workflowId, updateData);
|
const updatedWorkflow = await updateWorkflowApi(request, workflowId, updateData, apiBaseUrl);
|
||||||
|
|
||||||
return { success: true, workflowData: updatedWorkflow };
|
return { success: true, workflowData: updatedWorkflow };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { FaSync, FaRobot, FaRocket, FaPlus, FaFileAlt, FaStop, FaList, FaTimes,
|
||||||
import { useToast } from '../../../contexts/ToastContext';
|
import { useToast } from '../../../contexts/ToastContext';
|
||||||
import { useApiRequest } from '../../../hooks/useApi';
|
import { useApiRequest } from '../../../hooks/useApi';
|
||||||
import { useFeatureStore } from '../../../stores/featureStore';
|
import { useFeatureStore } from '../../../stores/featureStore';
|
||||||
|
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
|
||||||
import styles from '../../admin/Admin.module.css';
|
import styles from '../../admin/Admin.module.css';
|
||||||
|
|
||||||
interface WorkflowLog {
|
interface WorkflowLog {
|
||||||
|
|
@ -24,11 +25,19 @@ interface WorkflowLog {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AutomationDefinitionsView: React.FC = () => {
|
export const AutomationDefinitionsView: React.FC = () => {
|
||||||
|
const { instanceId: routeInstanceId, featureCode: routeFeatureCode } = useCurrentInstance();
|
||||||
const { getAllInstances } = useFeatureStore();
|
const { getAllInstances } = useFeatureStore();
|
||||||
const instances = getAllInstances();
|
const instances = getAllInstances();
|
||||||
const chatbotInstance = instances.find(i => i.featureCode === 'chatbot') || instances[0];
|
const chatbotInstance = instances.find(i => i.featureCode === 'chatbot') || instances[0];
|
||||||
const mandateId = chatbotInstance?.mandateId;
|
const automationInstance = instances.find(i => i.featureCode === 'automation');
|
||||||
const featureInstanceId = chatbotInstance?.id;
|
// When under automation feature route, use route context; otherwise use featureStore
|
||||||
|
const mandateId = routeFeatureCode === 'automation' && routeInstanceId
|
||||||
|
? (automationInstance?.mandateId ?? chatbotInstance?.mandateId)
|
||||||
|
: chatbotInstance?.mandateId;
|
||||||
|
const featureInstanceId = routeFeatureCode === 'automation' && routeInstanceId
|
||||||
|
? routeInstanceId
|
||||||
|
: (chatbotInstance?.id ?? automationInstance?.id);
|
||||||
|
const automationWorkflowInstanceId = routeFeatureCode === 'automation' ? routeInstanceId : undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: automations,
|
data: automations,
|
||||||
|
|
@ -66,6 +75,7 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
automationId: string | null;
|
automationId: string | null;
|
||||||
automationLabel: string;
|
automationLabel: string;
|
||||||
|
featureInstanceId: string | null;
|
||||||
workflowId: string | null;
|
workflowId: string | null;
|
||||||
status: 'starting' | 'running' | 'completed' | 'stopped' | 'error';
|
status: 'starting' | 'running' | 'completed' | 'stopped' | 'error';
|
||||||
logs: WorkflowLog[];
|
logs: WorkflowLog[];
|
||||||
|
|
@ -73,6 +83,7 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
visible: false,
|
visible: false,
|
||||||
automationId: null,
|
automationId: null,
|
||||||
automationLabel: '',
|
automationLabel: '',
|
||||||
|
featureInstanceId: null,
|
||||||
workflowId: null,
|
workflowId: null,
|
||||||
status: 'starting',
|
status: 'starting',
|
||||||
logs: [],
|
logs: [],
|
||||||
|
|
@ -259,14 +270,16 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
setShowEditor(true);
|
setShowEditor(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pollWorkflowLogs = useCallback(async (workflowId: string) => {
|
const pollWorkflowLogs = useCallback(async (workflowId: string, instanceId: string) => {
|
||||||
try {
|
try {
|
||||||
const contextHeaders: Record<string, string> = {};
|
const contextHeaders: Record<string, string> = {};
|
||||||
if (mandateId) contextHeaders['X-Mandate-Id'] = mandateId;
|
if (mandateId) contextHeaders['X-Mandate-Id'] = mandateId;
|
||||||
|
const logsUrl = `/api/automations/${instanceId}/workflows/${workflowId}/logs`;
|
||||||
|
const workflowUrl = `/api/automations/${instanceId}/workflows/${workflowId}`;
|
||||||
const response = await request({
|
const response = await request({
|
||||||
url: `/api/workflows/${workflowId}/logs`,
|
url: logsUrl,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: lastLogIdRef.current ? { afterId: lastLogIdRef.current } : {},
|
params: lastLogIdRef.current ? { logId: lastLogIdRef.current } : {},
|
||||||
additionalConfig: { headers: contextHeaders },
|
additionalConfig: { headers: contextHeaders },
|
||||||
});
|
});
|
||||||
const logs: WorkflowLog[] = response?.items || response || [];
|
const logs: WorkflowLog[] = response?.items || response || [];
|
||||||
|
|
@ -279,7 +292,7 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
lastLogIdRef.current = logs[logs.length - 1].id;
|
lastLogIdRef.current = logs[logs.length - 1].id;
|
||||||
}
|
}
|
||||||
const statusResponse = await request({
|
const statusResponse = await request({
|
||||||
url: `/api/workflows/${workflowId}`,
|
url: workflowUrl,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
additionalConfig: { headers: contextHeaders },
|
additionalConfig: { headers: contextHeaders },
|
||||||
});
|
});
|
||||||
|
|
@ -309,6 +322,7 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
visible: true,
|
visible: true,
|
||||||
automationId: automation.id,
|
automationId: automation.id,
|
||||||
automationLabel: automation.label,
|
automationLabel: automation.label,
|
||||||
|
featureInstanceId: automation.featureInstanceId ?? automationWorkflowInstanceId ?? null,
|
||||||
workflowId: null,
|
workflowId: null,
|
||||||
status: 'starting',
|
status: 'starting',
|
||||||
logs: [{ id: 'init', timestamp: Date.now() / 1000, message: 'Automatisierung wird gestartet...' }],
|
logs: [{ id: 'init', timestamp: Date.now() / 1000, message: 'Automatisierung wird gestartet...' }],
|
||||||
|
|
@ -316,14 +330,17 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const result = await handleAutomationExecute(automation.id);
|
const result = await handleAutomationExecute(automation.id);
|
||||||
const workflowId = result?.id;
|
const workflowId = result?.id;
|
||||||
if (workflowId) {
|
const instanceId = automation.featureInstanceId ?? automationWorkflowInstanceId;
|
||||||
|
if (workflowId && instanceId) {
|
||||||
setExecutionModal(prev => ({
|
setExecutionModal(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
workflowId,
|
workflowId,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
logs: [...prev.logs, { id: 'started', timestamp: Date.now() / 1000, message: `Workflow ${workflowId} gestartet`, status: 'running' }],
|
logs: [...prev.logs, { id: 'started', timestamp: Date.now() / 1000, message: `Workflow ${workflowId} gestartet`, status: 'running' }],
|
||||||
}));
|
}));
|
||||||
pollIntervalRef.current = setInterval(() => pollWorkflowLogs(workflowId), 2000);
|
pollIntervalRef.current = setInterval(() => pollWorkflowLogs(workflowId, instanceId), 2000);
|
||||||
|
} else if (workflowId && !instanceId) {
|
||||||
|
setExecutionModal(prev => ({ ...prev, status: 'error', logs: [...prev.logs, { id: 'error', timestamp: Date.now() / 1000, message: 'Keine Feature-Instanz für Polling', status: 'error' }] }));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setExecutionModal(prev => ({
|
setExecutionModal(prev => ({
|
||||||
|
|
@ -337,11 +354,17 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
|
|
||||||
const handleStopWorkflow = async () => {
|
const handleStopWorkflow = async () => {
|
||||||
if (!executionModal.workflowId) return;
|
if (!executionModal.workflowId) return;
|
||||||
|
const instanceId = executionModal.featureInstanceId ?? automationWorkflowInstanceId;
|
||||||
|
if (!instanceId) {
|
||||||
|
showError('Keine Feature-Instanz für Stopp verfügbar');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const stopHeaders: Record<string, string> = {};
|
const stopHeaders: Record<string, string> = {};
|
||||||
if (mandateId) stopHeaders['X-Mandate-Id'] = mandateId;
|
if (mandateId) stopHeaders['X-Mandate-Id'] = mandateId;
|
||||||
|
const stopUrl = `/api/automations/${instanceId}/workflows/${executionModal.workflowId}/stop`;
|
||||||
await request({
|
await request({
|
||||||
url: `/api/workflows/${executionModal.workflowId}/stop`,
|
url: stopUrl,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
additionalConfig: { headers: stopHeaders },
|
additionalConfig: { headers: stopHeaders },
|
||||||
});
|
});
|
||||||
|
|
@ -363,6 +386,7 @@ export const AutomationDefinitionsView: React.FC = () => {
|
||||||
visible: false,
|
visible: false,
|
||||||
automationId: null,
|
automationId: null,
|
||||||
automationLabel: '',
|
automationLabel: '',
|
||||||
|
featureInstanceId: null,
|
||||||
workflowId: null,
|
workflowId: null,
|
||||||
status: 'starting',
|
status: 'starting',
|
||||||
logs: [],
|
logs: [],
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { useUserWorkflows, useWorkflowOperations } from '../../hooks/useWorkflows';
|
import { useUserWorkflows, useWorkflowOperations, getWorkflowApiBaseUrl } from '../../hooks/useWorkflows';
|
||||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||||||
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
|
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
|
||||||
import { FaSync, FaList, FaPlay } from 'react-icons/fa';
|
import { FaSync, FaList, FaPlay } from 'react-icons/fa';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useCurrentInstance } from '../../hooks/useCurrentInstance';
|
||||||
import styles from '../admin/Admin.module.css';
|
import styles from '../admin/Admin.module.css';
|
||||||
|
|
||||||
interface Workflow {
|
interface Workflow {
|
||||||
|
|
@ -23,8 +24,12 @@ interface Workflow {
|
||||||
|
|
||||||
export const WorkflowsPage: React.FC = () => {
|
export const WorkflowsPage: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { instanceId, featureCode } = useCurrentInstance();
|
||||||
|
const workflowOptions = instanceId && featureCode ? { instanceId, featureCode } : undefined;
|
||||||
|
const apiBaseUrl = getWorkflowApiBaseUrl(instanceId, featureCode);
|
||||||
|
const apiEndpoint = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows/';
|
||||||
|
|
||||||
// Data hook
|
// Data hook - pass instance context when in feature route
|
||||||
const {
|
const {
|
||||||
data: workflows,
|
data: workflows,
|
||||||
attributes,
|
attributes,
|
||||||
|
|
@ -35,16 +40,16 @@ export const WorkflowsPage: React.FC = () => {
|
||||||
refetch,
|
refetch,
|
||||||
fetchWorkflowById,
|
fetchWorkflowById,
|
||||||
updateOptimistically,
|
updateOptimistically,
|
||||||
} = useUserWorkflows();
|
} = useUserWorkflows(workflowOptions);
|
||||||
|
|
||||||
// Operations hook
|
// Operations hook - pass instance context when in feature route
|
||||||
const {
|
const {
|
||||||
handleWorkflowDelete,
|
handleWorkflowDelete,
|
||||||
handleWorkflowDeleteMultiple,
|
handleWorkflowDeleteMultiple,
|
||||||
handleWorkflowUpdate,
|
handleWorkflowUpdate,
|
||||||
handleInlineUpdate,
|
handleInlineUpdate,
|
||||||
deletingWorkflows,
|
deletingWorkflows,
|
||||||
} = useWorkflowOperations();
|
} = useWorkflowOperations(workflowOptions);
|
||||||
|
|
||||||
const [editingWorkflow, setEditingWorkflow] = useState<Workflow | null>(null);
|
const [editingWorkflow, setEditingWorkflow] = useState<Workflow | null>(null);
|
||||||
|
|
||||||
|
|
@ -173,7 +178,7 @@ export const WorkflowsPage: React.FC = () => {
|
||||||
<FormGeneratorTable
|
<FormGeneratorTable
|
||||||
data={workflows}
|
data={workflows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
apiEndpoint="/api/workflows/"
|
apiEndpoint={apiEndpoint}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={true}
|
pagination={true}
|
||||||
pageSize={25}
|
pageSize={25}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue