fix feature instance passing

This commit is contained in:
ValueOn AG 2026-03-13 08:29:30 +01:00
parent 5ceb243de9
commit 9cad69fd0c
6 changed files with 66 additions and 52 deletions

View file

@ -68,17 +68,23 @@ export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<an
// API REQUEST FUNCTIONS
// ============================================================================
function _requireApiBaseUrl(apiBaseUrl: string | undefined, caller: string): string {
if (!apiBaseUrl) {
throw new Error(`${caller}: apiBaseUrl is required (instanceId/featureCode missing)`);
}
return apiBaseUrl;
}
/**
* Fetch workflows - feature-scoped or global (legacy)
* When apiBaseUrl is provided: GET {apiBaseUrl}/workflows
* Otherwise (legacy): GET /api/workflows/
* Fetch workflows - feature-scoped: GET {apiBaseUrl}/workflows
*/
export async function fetchWorkflows(
request: ApiRequestFunction,
params?: { pagination?: string },
apiBaseUrl?: string
): Promise<Workflow[] | { items: Workflow[]; pagination?: any }> {
const url = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows/';
const base = _requireApiBaseUrl(apiBaseUrl, 'fetchWorkflows');
const url = `${base}/workflows`;
console.log('📤 fetchWorkflows: Making API request to', url);
try {
@ -135,30 +141,28 @@ export async function fetchWorkflows(
}
/**
* Fetch a single workflow by ID
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}
* Otherwise: GET /api/workflows/{workflowId}
* Fetch a single workflow by ID: GET {apiBaseUrl}/workflows/{workflowId}
*/
export async function fetchWorkflow(
request: ApiRequestFunction,
workflowId: string,
apiBaseUrl?: string
): Promise<Workflow & { messages?: WorkflowMessage[]; logs?: WorkflowLog[] }> {
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}` : `/api/workflows/${workflowId}`;
const base = _requireApiBaseUrl(apiBaseUrl, 'fetchWorkflow');
const url = `${base}/workflows/${workflowId}`;
return await request({ url, method: 'get' });
}
/**
* Fetch workflow status (lightweight status check)
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/status
* Otherwise: GET /api/workflows/{workflowId}/status
* Fetch workflow status (lightweight status check): GET {apiBaseUrl}/workflows/{workflowId}/status
*/
export async function fetchWorkflowStatus(
request: ApiRequestFunction,
workflowId: string,
apiBaseUrl?: string
): Promise<Workflow | { status: string } | null> {
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/status` : `/api/workflows/${workflowId}/status`;
const base = _requireApiBaseUrl(apiBaseUrl, 'fetchWorkflowStatus');
const url = `${base}/workflows/${workflowId}/status`;
const data = await request({ url, method: 'get' });
if (data && typeof data === 'object') {
@ -172,9 +176,7 @@ export async function fetchWorkflowStatus(
}
/**
* Fetch workflow messages
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/messages
* Otherwise: GET /api/workflows/{workflowId}/messages
* Fetch workflow messages: GET {apiBaseUrl}/workflows/{workflowId}/messages
*/
export async function fetchWorkflowMessages(
request: ApiRequestFunction,
@ -183,7 +185,8 @@ export async function fetchWorkflowMessages(
apiBaseUrl?: string
): Promise<WorkflowMessage[]> {
const params = messageId ? { messageId } : undefined;
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/messages` : `/api/workflows/${workflowId}/messages`;
const base = _requireApiBaseUrl(apiBaseUrl, 'fetchWorkflowMessages');
const url = `${base}/workflows/${workflowId}/messages`;
const data = await request({ url, method: 'get', params });
if (Array.isArray(data)) {
@ -203,9 +206,7 @@ export async function fetchWorkflowMessages(
}
/**
* Fetch workflow logs
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/logs
* Otherwise: GET /api/workflows/{workflowId}/logs
* Fetch workflow logs: GET {apiBaseUrl}/workflows/{workflowId}/logs
*/
export async function fetchWorkflowLogs(
request: ApiRequestFunction,
@ -214,7 +215,8 @@ export async function fetchWorkflowLogs(
apiBaseUrl?: string
): Promise<WorkflowLog[]> {
const params = logId ? { logId } : undefined;
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/logs` : `/api/workflows/${workflowId}/logs`;
const base = _requireApiBaseUrl(apiBaseUrl, 'fetchWorkflowLogs');
const url = `${base}/workflows/${workflowId}/logs`;
const data = await request({ url, method: 'get', params });
if (Array.isArray(data)) {
@ -389,9 +391,7 @@ export async function stopWorkflowApi(
}
/**
* Update workflow properties
* When apiBaseUrl provided: PUT {apiBaseUrl}/workflows/{workflowId}
* Otherwise: PUT /api/workflows/{workflowId}
* Update workflow properties: PUT {apiBaseUrl}/workflows/{workflowId}
*/
export async function updateWorkflowApi(
request: ApiRequestFunction,
@ -399,34 +399,33 @@ export async function updateWorkflowApi(
updateData: Partial<{ name: string; description?: string; tags?: string[] }>,
apiBaseUrl?: string
): Promise<Workflow> {
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}` : `/api/workflows/${workflowId}`;
const base = _requireApiBaseUrl(apiBaseUrl, 'updateWorkflowApi');
const url = `${base}/workflows/${workflowId}`;
return await request({ url, method: 'put', data: updateData });
}
/**
* Delete a workflow and all associated data
* When apiBaseUrl provided: DELETE {apiBaseUrl}/workflows/{workflowId}
* Otherwise: DELETE /api/workflows/{workflowId}
* Delete a workflow and all associated data: DELETE {apiBaseUrl}/workflows/{workflowId}
*/
export async function deleteWorkflowApi(
request: ApiRequestFunction,
workflowId: string,
apiBaseUrl?: string
): Promise<void> {
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}` : `/api/workflows/${workflowId}`;
const base = _requireApiBaseUrl(apiBaseUrl, 'deleteWorkflowApi');
const url = `${base}/workflows/${workflowId}`;
await request({ url, method: 'delete' });
}
/**
* Delete multiple workflows
* When apiBaseUrl provided, uses feature-scoped delete endpoint
* Delete multiple workflows: DELETE {apiBaseUrl}/workflows/{workflowId} (per workflow)
*/
export async function deleteWorkflowsApi(
request: ApiRequestFunction,
workflowIds: string[],
apiBaseUrl?: string
): Promise<void> {
const base = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows';
const base = `${_requireApiBaseUrl(apiBaseUrl, 'deleteWorkflowsApi')}/workflows`;
const deletePromises = workflowIds.map(workflowId =>
request({
url: `${base}/${workflowId}`,
@ -441,9 +440,7 @@ export async function deleteWorkflowsApi(
}
/**
* Delete a message from a workflow
* When apiBaseUrl provided: DELETE {apiBaseUrl}/workflows/{workflowId}/messages/{messageId}
* Otherwise: DELETE /api/workflows/{workflowId}/messages/{messageId}
* Delete a message from a workflow: DELETE {apiBaseUrl}/workflows/{workflowId}/messages/{messageId}
*/
export async function deleteMessageApi(
request: ApiRequestFunction,
@ -451,14 +448,13 @@ export async function deleteMessageApi(
messageId: string,
apiBaseUrl?: string
): Promise<void> {
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/messages/${messageId}` : `/api/workflows/${workflowId}/messages/${messageId}`;
const base = _requireApiBaseUrl(apiBaseUrl, 'deleteMessageApi');
const url = `${base}/workflows/${workflowId}/messages/${messageId}`;
await request({ url, method: 'delete' });
}
/**
* Delete a file reference from a message
* When apiBaseUrl provided: DELETE {apiBaseUrl}/workflows/{workflowId}/messages/{messageId}/files/{fileId}
* Otherwise: DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}
* Delete a file reference from a message: DELETE {apiBaseUrl}/workflows/{workflowId}/messages/{messageId}/files/{fileId}
*/
export async function deleteFileFromMessageApi(
request: ApiRequestFunction,
@ -467,7 +463,8 @@ export async function deleteFileFromMessageApi(
fileId: string,
apiBaseUrl?: string
): Promise<void> {
const url = apiBaseUrl ? `${apiBaseUrl}/workflows/${workflowId}/messages/${messageId}/files/${fileId}` : `/api/workflows/${workflowId}/messages/${messageId}/files/${fileId}`;
const base = _requireApiBaseUrl(apiBaseUrl, 'deleteFileFromMessageApi');
const url = `${base}/workflows/${workflowId}/messages/${messageId}/files/${fileId}`;
await request({ url, method: 'delete' });
}

View file

@ -152,7 +152,7 @@ export function useDashboardInputForm(instanceId: string) {
};
}, []);
const { workflows, loading: workflowsLoading, refetch: refetchWorkflows } = useWorkflows();
const { workflows, loading: workflowsLoading, refetch: refetchWorkflows } = useWorkflows(instanceId);
// Track processed log IDs to avoid reprocessing
const processedLogIdsRef = useRef<Set<string>>(new Set());

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useApiRequest } from '../useApi';
import {
type Workflow,
@ -11,6 +11,7 @@ import {
import { useWorkflowOperations } from './useWorkflowOperations';
import { sortMessages, sortLogs } from './playgroundUtils';
import { useWorkflowPolling } from './useWorkflowPolling';
import { getWorkflowApiBaseUrl } from '../useWorkflows';
interface UnifiedChatDataItem {
type: 'message' | 'log' | 'stat';
@ -65,6 +66,8 @@ interface UnifiedChatDataItem {
*/
export function useWorkflowLifecycle(instanceId: string) {
const apiBaseUrl = useMemo(() => getWorkflowApiBaseUrl(instanceId, 'chatplayground'), [instanceId]);
// === STATE ===
const [workflowId, setWorkflowId] = useState<string | null>(null);
const [workflowStatus, setWorkflowStatus] = useState<string>('idle');
@ -333,7 +336,7 @@ export function useWorkflowLifecycle(instanceId: string) {
const afterTimestamp = lastRenderedTimestampRef.current || undefined;
// Fetch workflow status
const workflowData = await fetchWorkflowApi(request, id).catch(() => null);
const workflowData = await fetchWorkflowApi(request, id, apiBaseUrl).catch(() => null);
if (workflowData) {
const status = workflowData.status || 'idle';
@ -366,7 +369,7 @@ export function useWorkflowLifecycle(instanceId: string) {
} catch (error) {
console.error('❌ Polling error:', error);
}
}, [request, instanceId, updateWorkflowStatus, processUnifiedChatData]);
}, [request, instanceId, apiBaseUrl, updateWorkflowStatus, processUnifiedChatData]);
// === POLLING CONTROL EFFECT ===
useEffect(() => {
@ -517,7 +520,7 @@ export function useWorkflowLifecycle(instanceId: string) {
setHasRenderedLastMessage(false);
// Fetch workflow data
const workflowData = await fetchWorkflowApi(request, workflowIdToSelect).catch(() => null);
const workflowData = await fetchWorkflowApi(request, workflowIdToSelect, apiBaseUrl).catch(() => null);
if (!workflowData) {
setMessages([]);
@ -578,7 +581,7 @@ export function useWorkflowLifecycle(instanceId: string) {
} catch (error) {
console.error('❌ Error selecting workflow:', error);
}
}, [request, instanceId, updateWorkflowStatus, processUnifiedChatData]);
}, [request, instanceId, apiBaseUrl, updateWorkflowStatus, processUnifiedChatData]);
// === EXPOSE STATUS SETTER FOR OPTIMISTIC UPDATES ===
const setWorkflowStatusOptimistic = useCallback((status: string) => {

View file

@ -1,9 +1,10 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useApiRequest } from '../useApi';
import { useWorkflowSelection } from '../../contexts/WorkflowSelectionContext';
import { fetchWorkflows as fetchWorkflowsApi, type Workflow } from '../../api/workflowApi';
import { getWorkflowApiBaseUrl } from '../useWorkflows';
export function useWorkflows() {
export function useWorkflows(instanceId?: string, featureCode: string = 'chatplayground') {
const [workflows, setWorkflows] = useState<Workflow[]>([]);
const [isRefetching, setIsRefetching] = useState(false);
const [loading, setLoading] = useState(false);
@ -11,13 +12,22 @@ export function useWorkflows() {
const { request } = useApiRequest<null, Workflow[]>();
const { selectedWorkflowId, clearWorkflow } = useWorkflowSelection();
const apiBaseUrl = useMemo(
() => getWorkflowApiBaseUrl(instanceId, featureCode),
[instanceId, featureCode]
);
const fetchWorkflows = useCallback(async () => {
if (!apiBaseUrl) {
console.warn('⚠️ useWorkflows: No apiBaseUrl available (missing instanceId), skipping fetch');
return;
}
try {
setLoading(true);
setError(null);
console.log('🔄 useWorkflows: Fetching workflows from API...');
const workflowList = await fetchWorkflowsApi(request);
console.log('🔄 useWorkflows: Fetching workflows from API...', { apiBaseUrl });
const workflowList = await fetchWorkflowsApi(request, undefined, apiBaseUrl);
console.log('✅ useWorkflows: Fetched workflows:', workflowList);
if (Array.isArray(workflowList)) {
@ -34,7 +44,7 @@ export function useWorkflows() {
} finally {
setLoading(false);
}
}, [request]);
}, [request, apiBaseUrl]);
useEffect(() => {
fetchWorkflows();

View file

@ -122,7 +122,11 @@ export function useUserWorkflows(options?: { instanceId?: string; featureCode?:
}
let data: any;
const url = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows/';
if (!apiBaseUrl) {
console.error('useUserWorkflows: apiBaseUrl is required (missing instanceId/featureCode)');
return;
}
const url = `${apiBaseUrl}/workflows`;
if (Object.keys(requestParams).length > 0) {
data = await request({ url, method: 'get', params: requestParams });
} else {

View file

@ -27,7 +27,7 @@ export const WorkflowsPage: React.FC = () => {
const { instanceId, featureCode } = useCurrentInstance();
const workflowOptions = instanceId && featureCode ? { instanceId, featureCode } : undefined;
const apiBaseUrl = getWorkflowApiBaseUrl(instanceId, featureCode);
const apiEndpoint = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows/';
const apiEndpoint = apiBaseUrl ? `${apiBaseUrl}/workflows` : '';
// Data hook - pass instance context when in feature route
const {