Merge pull request #15 from valueonag/int

fix feature instance passing
This commit is contained in:
Patrick Motsch 2026-03-13 08:31:28 +01:00 committed by GitHub
commit d3c3a5d465
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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 // 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) * Fetch workflows - feature-scoped: GET {apiBaseUrl}/workflows
* When apiBaseUrl is provided: GET {apiBaseUrl}/workflows
* Otherwise (legacy): GET /api/workflows/
*/ */
export async function fetchWorkflows( export async function fetchWorkflows(
request: ApiRequestFunction, request: ApiRequestFunction,
params?: { pagination?: string }, params?: { pagination?: string },
apiBaseUrl?: string apiBaseUrl?: string
): Promise<Workflow[] | { items: Workflow[]; pagination?: any }> { ): 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); console.log('📤 fetchWorkflows: Making API request to', url);
try { try {
@ -135,30 +141,28 @@ export async function fetchWorkflows(
} }
/** /**
* Fetch a single workflow by ID * Fetch a single workflow by ID: GET {apiBaseUrl}/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 apiBaseUrl?: string
): Promise<Workflow & { messages?: WorkflowMessage[]; logs?: WorkflowLog[] }> { ): 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' }); return await request({ url, method: 'get' });
} }
/** /**
* Fetch workflow status (lightweight status check) * Fetch workflow status (lightweight status check): GET {apiBaseUrl}/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 apiBaseUrl?: string
): Promise<Workflow | { status: string } | null> { ): 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' }); const data = await request({ url, method: 'get' });
if (data && typeof data === 'object') { if (data && typeof data === 'object') {
@ -172,9 +176,7 @@ export async function fetchWorkflowStatus(
} }
/** /**
* Fetch workflow messages * Fetch workflow messages: GET {apiBaseUrl}/workflows/{workflowId}/messages
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/messages
* Otherwise: GET /api/workflows/{workflowId}/messages
*/ */
export async function fetchWorkflowMessages( export async function fetchWorkflowMessages(
request: ApiRequestFunction, request: ApiRequestFunction,
@ -183,7 +185,8 @@ export async function fetchWorkflowMessages(
apiBaseUrl?: string apiBaseUrl?: string
): Promise<WorkflowMessage[]> { ): Promise<WorkflowMessage[]> {
const params = messageId ? { messageId } : undefined; 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 }); const data = await request({ url, method: 'get', params });
if (Array.isArray(data)) { if (Array.isArray(data)) {
@ -203,9 +206,7 @@ export async function fetchWorkflowMessages(
} }
/** /**
* Fetch workflow logs * Fetch workflow logs: GET {apiBaseUrl}/workflows/{workflowId}/logs
* When apiBaseUrl provided: GET {apiBaseUrl}/workflows/{workflowId}/logs
* Otherwise: GET /api/workflows/{workflowId}/logs
*/ */
export async function fetchWorkflowLogs( export async function fetchWorkflowLogs(
request: ApiRequestFunction, request: ApiRequestFunction,
@ -214,7 +215,8 @@ export async function fetchWorkflowLogs(
apiBaseUrl?: string apiBaseUrl?: string
): Promise<WorkflowLog[]> { ): Promise<WorkflowLog[]> {
const params = logId ? { logId } : undefined; 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 }); const data = await request({ url, method: 'get', params });
if (Array.isArray(data)) { if (Array.isArray(data)) {
@ -389,9 +391,7 @@ export async function stopWorkflowApi(
} }
/** /**
* Update workflow properties * Update workflow properties: PUT {apiBaseUrl}/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,
@ -399,34 +399,33 @@ export async function updateWorkflowApi(
updateData: Partial<{ name: string; description?: string; tags?: string[] }>, updateData: Partial<{ name: string; description?: string; tags?: string[] }>,
apiBaseUrl?: string apiBaseUrl?: string
): Promise<Workflow> { ): 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 }); return await request({ url, method: 'put', data: updateData });
} }
/** /**
* Delete a workflow and all associated data * Delete a workflow and all associated data: DELETE {apiBaseUrl}/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 apiBaseUrl?: string
): Promise<void> { ): 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' }); await request({ url, method: 'delete' });
} }
/** /**
* Delete multiple workflows * Delete multiple workflows: DELETE {apiBaseUrl}/workflows/{workflowId} (per workflow)
* 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 apiBaseUrl?: string
): Promise<void> { ): Promise<void> {
const base = apiBaseUrl ? `${apiBaseUrl}/workflows` : '/api/workflows'; const base = `${_requireApiBaseUrl(apiBaseUrl, 'deleteWorkflowsApi')}/workflows`;
const deletePromises = workflowIds.map(workflowId => const deletePromises = workflowIds.map(workflowId =>
request({ request({
url: `${base}/${workflowId}`, url: `${base}/${workflowId}`,
@ -441,9 +440,7 @@ export async function deleteWorkflowsApi(
} }
/** /**
* Delete a message from a workflow * Delete a message from a workflow: DELETE {apiBaseUrl}/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,
@ -451,14 +448,13 @@ export async function deleteMessageApi(
messageId: string, messageId: string,
apiBaseUrl?: string apiBaseUrl?: string
): Promise<void> { ): 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' }); await request({ url, method: 'delete' });
} }
/** /**
* Delete a file reference from a message * Delete a file reference from a message: DELETE {apiBaseUrl}/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,
@ -467,7 +463,8 @@ export async function deleteFileFromMessageApi(
fileId: string, fileId: string,
apiBaseUrl?: string apiBaseUrl?: string
): Promise<void> { ): 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' }); 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 // Track processed log IDs to avoid reprocessing
const processedLogIdsRef = useRef<Set<string>>(new Set()); 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 { useApiRequest } from '../useApi';
import { import {
type Workflow, type Workflow,
@ -11,6 +11,7 @@ import {
import { useWorkflowOperations } from './useWorkflowOperations'; import { useWorkflowOperations } from './useWorkflowOperations';
import { sortMessages, sortLogs } from './playgroundUtils'; import { sortMessages, sortLogs } from './playgroundUtils';
import { useWorkflowPolling } from './useWorkflowPolling'; import { useWorkflowPolling } from './useWorkflowPolling';
import { getWorkflowApiBaseUrl } from '../useWorkflows';
interface UnifiedChatDataItem { interface UnifiedChatDataItem {
type: 'message' | 'log' | 'stat'; type: 'message' | 'log' | 'stat';
@ -65,6 +66,8 @@ interface UnifiedChatDataItem {
*/ */
export function useWorkflowLifecycle(instanceId: string) { export function useWorkflowLifecycle(instanceId: string) {
const apiBaseUrl = useMemo(() => getWorkflowApiBaseUrl(instanceId, 'chatplayground'), [instanceId]);
// === STATE === // === STATE ===
const [workflowId, setWorkflowId] = useState<string | null>(null); const [workflowId, setWorkflowId] = useState<string | null>(null);
const [workflowStatus, setWorkflowStatus] = useState<string>('idle'); const [workflowStatus, setWorkflowStatus] = useState<string>('idle');
@ -333,7 +336,7 @@ export function useWorkflowLifecycle(instanceId: string) {
const afterTimestamp = lastRenderedTimestampRef.current || undefined; const afterTimestamp = lastRenderedTimestampRef.current || undefined;
// Fetch workflow status // Fetch workflow status
const workflowData = await fetchWorkflowApi(request, id).catch(() => null); const workflowData = await fetchWorkflowApi(request, id, apiBaseUrl).catch(() => null);
if (workflowData) { if (workflowData) {
const status = workflowData.status || 'idle'; const status = workflowData.status || 'idle';
@ -366,7 +369,7 @@ export function useWorkflowLifecycle(instanceId: string) {
} catch (error) { } catch (error) {
console.error('❌ Polling error:', error); console.error('❌ Polling error:', error);
} }
}, [request, instanceId, updateWorkflowStatus, processUnifiedChatData]); }, [request, instanceId, apiBaseUrl, updateWorkflowStatus, processUnifiedChatData]);
// === POLLING CONTROL EFFECT === // === POLLING CONTROL EFFECT ===
useEffect(() => { useEffect(() => {
@ -517,7 +520,7 @@ export function useWorkflowLifecycle(instanceId: string) {
setHasRenderedLastMessage(false); setHasRenderedLastMessage(false);
// Fetch workflow data // Fetch workflow data
const workflowData = await fetchWorkflowApi(request, workflowIdToSelect).catch(() => null); const workflowData = await fetchWorkflowApi(request, workflowIdToSelect, apiBaseUrl).catch(() => null);
if (!workflowData) { if (!workflowData) {
setMessages([]); setMessages([]);
@ -578,7 +581,7 @@ export function useWorkflowLifecycle(instanceId: string) {
} catch (error) { } catch (error) {
console.error('❌ Error selecting workflow:', error); console.error('❌ Error selecting workflow:', error);
} }
}, [request, instanceId, updateWorkflowStatus, processUnifiedChatData]); }, [request, instanceId, apiBaseUrl, updateWorkflowStatus, processUnifiedChatData]);
// === EXPOSE STATUS SETTER FOR OPTIMISTIC UPDATES === // === EXPOSE STATUS SETTER FOR OPTIMISTIC UPDATES ===
const setWorkflowStatusOptimistic = useCallback((status: string) => { 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 { useApiRequest } from '../useApi';
import { useWorkflowSelection } from '../../contexts/WorkflowSelectionContext'; import { useWorkflowSelection } from '../../contexts/WorkflowSelectionContext';
import { fetchWorkflows as fetchWorkflowsApi, type Workflow } from '../../api/workflowApi'; 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 [workflows, setWorkflows] = useState<Workflow[]>([]);
const [isRefetching, setIsRefetching] = useState(false); const [isRefetching, setIsRefetching] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -11,13 +12,22 @@ export function useWorkflows() {
const { request } = useApiRequest<null, Workflow[]>(); const { request } = useApiRequest<null, Workflow[]>();
const { selectedWorkflowId, clearWorkflow } = useWorkflowSelection(); const { selectedWorkflowId, clearWorkflow } = useWorkflowSelection();
const apiBaseUrl = useMemo(
() => getWorkflowApiBaseUrl(instanceId, featureCode),
[instanceId, featureCode]
);
const fetchWorkflows = useCallback(async () => { const fetchWorkflows = useCallback(async () => {
if (!apiBaseUrl) {
console.warn('⚠️ useWorkflows: No apiBaseUrl available (missing instanceId), skipping fetch');
return;
}
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
console.log('🔄 useWorkflows: Fetching workflows from API...'); console.log('🔄 useWorkflows: Fetching workflows from API...', { apiBaseUrl });
const workflowList = await fetchWorkflowsApi(request); const workflowList = await fetchWorkflowsApi(request, undefined, apiBaseUrl);
console.log('✅ useWorkflows: Fetched workflows:', workflowList); console.log('✅ useWorkflows: Fetched workflows:', workflowList);
if (Array.isArray(workflowList)) { if (Array.isArray(workflowList)) {
@ -34,7 +44,7 @@ export function useWorkflows() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [request]); }, [request, apiBaseUrl]);
useEffect(() => { useEffect(() => {
fetchWorkflows(); fetchWorkflows();

View file

@ -122,7 +122,11 @@ export function useUserWorkflows(options?: { instanceId?: string; featureCode?:
} }
let data: any; 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) { if (Object.keys(requestParams).length > 0) {
data = await request({ url, method: 'get', params: requestParams }); data = await request({ url, method: 'get', params: requestParams });
} else { } else {

View file

@ -27,7 +27,7 @@ export const WorkflowsPage: React.FC = () => {
const { instanceId, featureCode } = useCurrentInstance(); const { instanceId, featureCode } = useCurrentInstance();
const workflowOptions = instanceId && featureCode ? { instanceId, featureCode } : undefined; const workflowOptions = instanceId && featureCode ? { instanceId, featureCode } : undefined;
const apiBaseUrl = getWorkflowApiBaseUrl(instanceId, featureCode); 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 // Data hook - pass instance context when in feature route
const { const {