frontend_nyla/src/api/workflowApi.ts
2026-02-08 01:44:48 +01:00

526 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================
// TYPES & INTERFACES
// ============================================================================
// Workflow interfaces
export interface Workflow {
id: string;
mandateId: string;
status: string;
name?: string;
workflowMode?: string;
[key: string]: any; // Allow additional properties
}
export interface WorkflowMessage {
[key: string]: any;
}
export interface WorkflowStats {
[key: string]: any;
}
export interface WorkflowDocument {
[key: string]: any;
}
export interface WorkflowLog {
[key: string]: any;
}
// Request/Response interfaces based on API documentation
export interface FileAttachment {
id: string;
name: string;
}
export interface UserInputRequest {
input: string;
files?: FileAttachment[]; // optional file attachments - array of {id, name} objects
metadata?: Record<string, any>; // optional metadata
}
export interface StartWorkflowRequest {
prompt: string;
listFileId?: string[]; // Array of file ID strings (files must be uploaded first via /api/files/upload)
userLanguage?: string; // Optional, defaults to "en"
metadata?: Record<string, any>;
allowedProviders?: string[]; // Optional: Restrict AI calls to these providers (empty = all RBAC-permitted)
}
export interface StartWorkflowResponse extends Workflow {
// Workflow object returned from start endpoint
}
export interface ChatDataResponse {
messages: WorkflowMessage[];
logs: WorkflowLog[];
stats: WorkflowStats[];
documents: WorkflowDocument[];
}
// Type for the request function passed to API functions
export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;
// ============================================================================
// API REQUEST FUNCTIONS
// ============================================================================
/**
* Fetch all workflows for the current user
* Endpoint: GET /api/workflows/
*/
export async function fetchWorkflows(request: ApiRequestFunction): Promise<Workflow[]> {
console.log('📤 fetchWorkflows: Making API request to /api/workflows/');
try {
const data = await request({
url: '/api/workflows/',
method: 'get'
});
console.log('📥 fetchWorkflows: API response:', data);
// Handle different response formats
let workflows: Workflow[] = [];
if (Array.isArray(data)) {
// Direct array response
workflows = data;
} else if (data && typeof data === 'object') {
// Check for common wrapper properties
if (Array.isArray(data.workflows)) {
workflows = data.workflows;
} else if (Array.isArray(data.data)) {
workflows = data.data;
} else if (Array.isArray(data.items)) {
workflows = data.items;
} else if (Array.isArray(data.results)) {
workflows = data.results;
} else {
// Try to find any array property
const keys = Object.keys(data);
for (const key of keys) {
if (Array.isArray(data[key])) {
workflows = data[key];
console.log(` fetchWorkflows: Found workflows array in property '${key}'`);
break;
}
}
}
}
// Validate that we have workflow objects with id property
const validWorkflows = workflows.filter((w: any) => w && typeof w === 'object' && w.id);
if (validWorkflows.length !== workflows.length) {
console.warn(`⚠️ fetchWorkflows: Filtered out ${workflows.length - validWorkflows.length} invalid workflows`);
}
console.log(`✅ fetchWorkflows: Returning ${validWorkflows.length} valid workflows`);
return validWorkflows;
} catch (error) {
console.error('❌ fetchWorkflows: Error fetching workflows:', error);
throw error;
}
}
/**
* Fetch a single workflow by ID
* Endpoint: GET /api/workflows/{workflowId}
*/
export async function fetchWorkflow(
request: ApiRequestFunction,
workflowId: string
): Promise<Workflow & { messages?: WorkflowMessage[]; logs?: WorkflowLog[] }> {
return await request({
url: `/api/workflows/${workflowId}`,
method: 'get'
});
}
/**
* Fetch workflow status (lightweight status check)
* Endpoint: GET /api/workflows/{workflowId}/status
*/
export async function fetchWorkflowStatus(
request: ApiRequestFunction,
workflowId: string
): Promise<Workflow | { status: string } | null> {
const data = await request({
url: `/api/workflows/${workflowId}/status`,
method: 'get'
});
if (data && typeof data === 'object') {
if (data.status) {
return { status: data.status };
}
return data;
}
return null;
}
/**
* Fetch workflow messages
* Endpoint: GET /api/workflows/{workflowId}/messages
* Query params: messageId (optional) - fetch only newer messages
*/
export async function fetchWorkflowMessages(
request: ApiRequestFunction,
workflowId: string,
messageId?: string
): Promise<WorkflowMessage[]> {
const params = messageId ? { messageId } : undefined;
const data = await request({
url: `/api/workflows/${workflowId}/messages`,
method: 'get',
params
});
if (Array.isArray(data)) {
return data;
}
if (data && typeof data === 'object') {
if (Array.isArray(data.messages)) {
return data.messages;
}
if (Array.isArray(data.data)) {
return data.data;
}
}
return [];
}
/**
* Fetch workflow logs
* Endpoint: GET /api/workflows/{workflowId}/logs
* Query params: logId (optional) - fetch only newer logs
*/
export async function fetchWorkflowLogs(
request: ApiRequestFunction,
workflowId: string,
logId?: string
): Promise<WorkflowLog[]> {
const params = logId ? { logId } : undefined;
const data = await request({
url: `/api/workflows/${workflowId}/logs`,
method: 'get',
params
});
if (Array.isArray(data)) {
return data;
}
if (data && typeof data === 'object') {
if (Array.isArray(data.logs)) {
return data.logs;
}
if (Array.isArray(data.data)) {
return data.data;
}
}
return [];
}
/**
* Fetch unified chat data (messages, logs, stats, documents)
* Endpoint: GET /api/chatplayground/{instanceId}/{workflowId}/chatData
* Query params: afterTimestamp (optional) - fetch only data created after this time
*/
export async function fetchChatData(
request: ApiRequestFunction,
instanceId: string,
workflowId: string,
afterTimestamp?: number
): Promise<ChatDataResponse> {
const params = afterTimestamp ? { afterTimestamp: afterTimestamp.toString() } : undefined;
const requestConfig = {
url: `/api/chatplayground/${instanceId}/${workflowId}/chatData`,
method: 'get' as const,
params
};
console.log('📤 fetchChatData request:', requestConfig);
const data = await request(requestConfig);
console.log('📥 fetchChatData response:', data);
// Handle unified items format: { items: [{ type: 'message'|'log'|'stat', item: {...}, createdAt: ... }] }
if (data.items && Array.isArray(data.items)) {
const messages: WorkflowMessage[] = [];
const logs: WorkflowLog[] = [];
const stats: WorkflowStats[] = [];
const documents: WorkflowDocument[] = [];
data.items.forEach((item: any) => {
if (item.type === 'message') {
// Handle both formats: item.item or direct item data
const messageData = item.item || item;
if (messageData && (messageData.id || messageData.message)) {
messages.push(messageData);
} else {
console.warn('⚠️ Invalid message item:', item);
}
} else if (item.type === 'log') {
const logData = item.item || item;
if (logData) {
logs.push(logData);
}
} else if (item.type === 'stat') {
const statData = item.item || item;
if (statData) {
stats.push(statData);
}
}
// Documents might be in items or separate
if (item.type === 'document') {
const docData = item.item || item;
if (docData) {
documents.push(docData);
}
}
});
console.log('📦 Extracted from items:', {
messages: messages.length,
logs: logs.length,
stats: stats.length,
documents: documents.length
});
return {
messages,
logs,
stats,
documents: documents.length > 0 ? documents : (Array.isArray(data.documents) ? data.documents : [])
};
}
// Fallback to direct format: { messages: [], logs: [], stats: [] }
return {
messages: Array.isArray(data.messages) ? data.messages : [],
logs: Array.isArray(data.logs) ? data.logs : [],
stats: Array.isArray(data.stats) ? data.stats : [],
documents: Array.isArray(data.documents) ? data.documents : []
};
}
/**
* Start a new workflow or continue an existing one
* Endpoint: POST /api/chatplayground/{instanceId}/start
* Query params: workflowId (optional), workflowMode (default: "Dynamic")
*/
export async function startWorkflowApi(
request: ApiRequestFunction,
instanceId: string,
workflowData: StartWorkflowRequest,
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
): Promise<StartWorkflowResponse> {
const params: Record<string, string> = {};
// workflowMode is REQUIRED according to API spec
if (options?.workflowMode) {
params.workflowMode = options.workflowMode;
} else {
// Default to 'Dynamic' if not provided (though it should always be provided)
params.workflowMode = 'Dynamic';
}
if (options?.workflowId) {
params.workflowId = options.workflowId;
}
// Request body uses 'prompt' field (not 'input') according to API spec
const requestBody: any = {
prompt: workflowData.prompt,
...(workflowData.listFileId && workflowData.listFileId.length > 0 && { listFileId: workflowData.listFileId }),
...(workflowData.userLanguage && { userLanguage: workflowData.userLanguage }),
...(workflowData.metadata && { metadata: workflowData.metadata }),
...(workflowData.allowedProviders && workflowData.allowedProviders.length > 0 && { allowedProviders: workflowData.allowedProviders })
};
const requestConfig = {
url: `/api/chatplayground/${instanceId}/start`,
method: 'post' as const,
data: requestBody,
params: params // Always include workflowMode
};
// Log full request details
console.log('📤 Full startWorkflow request details:');
console.log(' URL:', requestConfig.url);
console.log(' Method:', requestConfig.method);
console.log(' Query Parameters:', params);
console.log(' Request Body:', JSON.stringify(requestBody, null, 2));
console.log(' Full Request Config:', JSON.stringify(requestConfig, null, 2));
const response = await request(requestConfig);
console.log('📥 startWorkflow response:', response);
return response;
}
/**
* Stop a running workflow
* Endpoint: POST /api/chatplayground/{instanceId}/{workflowId}/stop
*/
export async function stopWorkflowApi(
request: ApiRequestFunction,
instanceId: string,
workflowId: string
): Promise<void> {
await request({
url: `/api/chatplayground/${instanceId}/${workflowId}/stop`,
method: 'post'
});
}
/**
* Update workflow properties
* Endpoint: PUT /api/workflows/{workflowId}
*/
export async function updateWorkflowApi(
request: ApiRequestFunction,
workflowId: string,
updateData: Partial<{ name: string; description?: string; tags?: string[] }>
): Promise<Workflow> {
return await request({
url: `/api/workflows/${workflowId}`,
method: 'put',
data: updateData
});
}
/**
* Delete a workflow and all associated data
* Endpoint: DELETE /api/workflows/{workflowId}
*/
export async function deleteWorkflowApi(
request: ApiRequestFunction,
workflowId: string
): Promise<void> {
await request({
url: `/api/workflows/${workflowId}`,
method: 'delete'
});
}
/**
* Delete multiple workflows
*/
export async function deleteWorkflowsApi(
request: ApiRequestFunction,
workflowIds: string[]
): Promise<void> {
// Delete workflows one by one since there's no bulk delete endpoint
const deletePromises = workflowIds.map(workflowId =>
request({
url: `/api/workflows/${workflowId}`,
method: 'delete'
}).catch(error => {
console.error(`Failed to delete workflow ${workflowId}:`, error);
throw error;
})
);
await Promise.all(deletePromises);
}
/**
* Delete a message from a workflow
* Endpoint: DELETE /api/workflows/{workflowId}/messages/{messageId}
*/
export async function deleteMessageApi(
request: ApiRequestFunction,
workflowId: string,
messageId: string
): Promise<void> {
await request({
url: `/api/workflows/${workflowId}/messages/${messageId}`,
method: 'delete'
});
}
/**
* Delete a file reference from a message
* Endpoint: DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}
*/
export async function deleteFileFromMessageApi(
request: ApiRequestFunction,
workflowId: string,
messageId: string,
fileId: string
): Promise<void> {
await request({
url: `/api/workflows/${workflowId}/messages/${messageId}/files/${fileId}`,
method: 'delete'
});
}
/**
* Fetch attributes for a workflow type
* Endpoint: GET /api/attributes/{entityType}
*/
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: Array<{ value: string | number; label: string | { [key: string]: string } }> | string;
validation?: any;
ui?: any;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
placeholder?: string;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
}
export async function fetchAttributes(
request: ApiRequestFunction,
entityType: string = 'ChatWorkflow'
): Promise<AttributeDefinition[]> {
const data = await request({
url: `/api/attributes/${entityType}`,
method: 'get'
});
// Extract attributes from response - check if response.data.attributes exists, otherwise check if response.data is an array
let attrs: AttributeDefinition[] = [];
if (data?.attributes && Array.isArray(data.attributes)) {
attrs = data.attributes;
} else if (Array.isArray(data)) {
attrs = data;
} else if (data && typeof data === 'object') {
// Try to find any array property in the response
const keys = Object.keys(data);
for (const key of keys) {
if (Array.isArray(data[key])) {
attrs = data[key];
break;
}
}
}
return attrs;
}