526 lines
15 KiB
TypeScript
526 lines
15 KiB
TypeScript
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;
|
||
}
|
||
|