workflow fixes
This commit is contained in:
parent
9e74daeaa5
commit
d6d57a2113
8 changed files with 67 additions and 130 deletions
|
|
@ -49,9 +49,12 @@ export const UserSection: React.FC = () => {
|
|||
}
|
||||
|
||||
// Initialen für Avatar
|
||||
const initials = user.fullName
|
||||
? user.fullName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
|
||||
: user.username.slice(0, 2).toUpperCase();
|
||||
const initials = (() => {
|
||||
const name = user.fullName || user.username || '';
|
||||
const parts = name.trim().split(/\s+/);
|
||||
if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toLocaleUpperCase();
|
||||
return [...name.trim()].slice(0, 2).join('').toLocaleUpperCase() || '?';
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className={styles.userSection}>
|
||||
|
|
|
|||
|
|
@ -2,90 +2,32 @@ import React, { useMemo } from 'react';
|
|||
import { WorkflowStatusProps, WorkflowStatusType } from './WorkflowStatusTypes';
|
||||
import styles from './WorkflowStatus.module.css';
|
||||
|
||||
// Helper function to extract workflow status and round from log message
|
||||
const _STATUS_MAP: Record<string, WorkflowStatusType> = {
|
||||
success: 'completed',
|
||||
completed: 'completed',
|
||||
started: 'started',
|
||||
running: 'started',
|
||||
resumed: 'resumed',
|
||||
stopped: 'stopped',
|
||||
failed: 'failed',
|
||||
error: 'failed',
|
||||
};
|
||||
|
||||
const extractWorkflowStatus = (logs: any[]): { status: WorkflowStatusType; round: number | null; timestamp: number } => {
|
||||
// First, check for completion messages with success status (these take priority)
|
||||
const completionMessages = logs.filter(log => {
|
||||
const message = (log.message || '').toLowerCase();
|
||||
if (!logs.length) return { status: null, round: null, timestamp: 0 };
|
||||
|
||||
const sorted = [...logs].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
||||
|
||||
for (const log of sorted) {
|
||||
const logStatus = (log.status || '').toLowerCase();
|
||||
return (message.includes('fast path completed') ||
|
||||
message.includes('completed successfully')) &&
|
||||
logStatus === 'success';
|
||||
});
|
||||
|
||||
// If we have completion messages, use the latest one
|
||||
if (completionMessages.length > 0) {
|
||||
const latestCompletion = completionMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0];
|
||||
|
||||
// Try to extract round from completion message
|
||||
let round: number | null = null;
|
||||
const message = (latestCompletion.message || '').toLowerCase();
|
||||
const roundMatch = message.match(/\(?round\s+(\d+)\)?/i);
|
||||
if (roundMatch) {
|
||||
round = parseInt(roundMatch[1], 10);
|
||||
} else {
|
||||
// If no round in completion message, get round from latest workflow status message
|
||||
const statusMessages = logs.filter(log => {
|
||||
const msg = (log.message || '').toLowerCase();
|
||||
return msg.includes('workflow started') || msg.includes('workflow resumed');
|
||||
});
|
||||
if (statusMessages.length > 0) {
|
||||
const latestWorkflowStatus = statusMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0];
|
||||
const workflowMessage = (latestWorkflowStatus.message || '').toLowerCase();
|
||||
const workflowRoundMatch = workflowMessage.match(/\(?round\s+(\d+)\)?/i);
|
||||
if (workflowRoundMatch) {
|
||||
round = parseInt(workflowRoundMatch[1], 10);
|
||||
}
|
||||
}
|
||||
const mapped = _STATUS_MAP[logStatus];
|
||||
if (mapped) {
|
||||
const roundMatch = (log.message || '').match(/\(?round\s+(\d+)\)?/i);
|
||||
return { status: mapped, round: roundMatch ? parseInt(roundMatch[1], 10) : null, timestamp: log.timestamp || 0 };
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'completed',
|
||||
round,
|
||||
timestamp: latestCompletion.timestamp || 0
|
||||
};
|
||||
}
|
||||
|
||||
// If no completion messages, look for workflow started/resumed/stopped messages
|
||||
const statusMessages = logs.filter(log => {
|
||||
const message = (log.message || '').toLowerCase();
|
||||
return message.includes('workflow started') ||
|
||||
message.includes('workflow resumed') ||
|
||||
message.includes('workflow stopped') ||
|
||||
message.includes('workflow failed') ||
|
||||
message.includes('workflow completed');
|
||||
});
|
||||
|
||||
if (statusMessages.length === 0) {
|
||||
return { status: null, round: null, timestamp: 0 };
|
||||
}
|
||||
|
||||
// Get the latest status message
|
||||
const latestStatus = statusMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0];
|
||||
const message = (latestStatus.message || '').toLowerCase();
|
||||
|
||||
let status: WorkflowStatusType = null;
|
||||
if (message.includes('started')) {
|
||||
status = 'started';
|
||||
} else if (message.includes('resumed')) {
|
||||
status = 'resumed';
|
||||
} else if (message.includes('stopped')) {
|
||||
status = 'stopped';
|
||||
} else if (message.includes('failed')) {
|
||||
status = 'failed';
|
||||
} else if (message.includes('completed')) {
|
||||
status = 'completed';
|
||||
}
|
||||
|
||||
// Extract round number from message (e.g., "round 4", "round 2", or "(round 4)")
|
||||
const roundMatch = message.match(/\(?round\s+(\d+)\)?/i);
|
||||
const round = roundMatch ? parseInt(roundMatch[1], 10) : null;
|
||||
|
||||
return {
|
||||
status,
|
||||
round,
|
||||
timestamp: latestStatus.timestamp || 0
|
||||
};
|
||||
return { status: null, round: null, timestamp: 0 };
|
||||
};
|
||||
|
||||
const _formatCurrency = (amount?: number): string => {
|
||||
|
|
@ -103,40 +45,10 @@ const WorkflowStatus: React.FC<WorkflowStatusProps> = ({
|
|||
}) => {
|
||||
// Use workflow status and round from API response, fallback to extracting from logs
|
||||
const workflowStatus = useMemo(() => {
|
||||
// If we have status from API, use it
|
||||
if (workflowStatusFromApi) {
|
||||
let status: WorkflowStatusType = null;
|
||||
const statusLower = workflowStatusFromApi.toLowerCase();
|
||||
|
||||
if (statusLower === 'completed') {
|
||||
status = 'completed';
|
||||
} else if (statusLower === 'running') {
|
||||
// Check if it's started or resumed from logs
|
||||
const startedResumedLogs = logs.filter(log => {
|
||||
const message = (log.message || '').toLowerCase();
|
||||
return message.includes('workflow started') || message.includes('workflow resumed');
|
||||
});
|
||||
if (startedResumedLogs.length > 0) {
|
||||
const latest = startedResumedLogs.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0];
|
||||
const message = (latest.message || '').toLowerCase();
|
||||
status = message.includes('resumed') ? 'resumed' : 'started';
|
||||
} else {
|
||||
status = 'started';
|
||||
}
|
||||
} else if (statusLower === 'stopped') {
|
||||
status = 'stopped';
|
||||
} else if (statusLower === 'failed') {
|
||||
status = 'failed';
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
round: currentRoundFromApi || null,
|
||||
timestamp: Date.now() / 1000 // Use current time since we don't have timestamp from API
|
||||
};
|
||||
const mapped = _STATUS_MAP[workflowStatusFromApi.toLowerCase()] || null;
|
||||
return { status: mapped, round: currentRoundFromApi || null, timestamp: Date.now() / 1000 };
|
||||
}
|
||||
|
||||
// Fallback to extracting from logs
|
||||
return extractWorkflowStatus(logs);
|
||||
}, [workflowStatusFromApi, currentRoundFromApi, logs]);
|
||||
|
||||
|
|
|
|||
|
|
@ -276,12 +276,7 @@ export function useUserWorkflows(options?: { instanceId?: string; featureCode?:
|
|||
} else if (attr.type === 'textarea') {
|
||||
fieldType = 'textarea';
|
||||
} else if (attr.type === 'text') {
|
||||
// Check if it should be textarea based on name
|
||||
if (attr.name.toLowerCase().includes('description') || attr.name.toLowerCase().includes('note')) {
|
||||
fieldType = 'textarea';
|
||||
} else {
|
||||
fieldType = 'string';
|
||||
}
|
||||
fieldType = (attr as any).multiline === true ? 'textarea' : 'string';
|
||||
}
|
||||
// Note: Legacy 'boolean' and 'enum' types are not in the AttributeDefinition type union
|
||||
// If needed, they should be handled via type casting: (attr as any).type === 'boolean'
|
||||
|
|
|
|||
|
|
@ -22,12 +22,16 @@ interface ConversationListProps {
|
|||
instanceId: string;
|
||||
activeWorkflowId: string | null;
|
||||
onSelect: (workflowId: string) => void;
|
||||
onCreateNew?: () => void;
|
||||
refreshTrigger?: number;
|
||||
}
|
||||
|
||||
export const ConversationList: React.FC<ConversationListProps> = ({
|
||||
instanceId,
|
||||
activeWorkflowId,
|
||||
onSelect,
|
||||
onCreateNew,
|
||||
refreshTrigger,
|
||||
}) => {
|
||||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -65,6 +69,16 @@ export const ConversationList: React.FC<ConversationListProps> = ({
|
|||
_loadConversations();
|
||||
}, [_loadConversations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshTrigger) _loadConversations();
|
||||
}, [refreshTrigger, _loadConversations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeWorkflowId && !conversations.find(c => c.id === activeWorkflowId)) {
|
||||
_loadConversations();
|
||||
}
|
||||
}, [activeWorkflowId, conversations, _loadConversations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingId && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
|
|
@ -145,15 +159,7 @@ export const ConversationList: React.FC<ConversationListProps> = ({
|
|||
};
|
||||
|
||||
const _handleCreateNew = () => {
|
||||
api.post(`/api/workspace/${instanceId}/workflows`, {})
|
||||
.then(res => {
|
||||
const wf = res.data;
|
||||
if (wf?.id) {
|
||||
_loadConversations();
|
||||
onSelect(wf.id);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
if (onCreateNew) onCreateNew();
|
||||
};
|
||||
|
||||
const _filtered = (items: Conversation[], query: string): Conversation[] => {
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
|
|||
setShowAutocomplete(false);
|
||||
setShowSourcePicker(false);
|
||||
setAttachedFileIds([]);
|
||||
setAttachedDataSourceIds([]);
|
||||
}, [prompt, isProcessing, _extractFileRefs, attachedFileIds, attachedDataSourceIds, onSend]);
|
||||
|
||||
const _handleKeyDown = useCallback(
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
instanceId={instanceId}
|
||||
activeWorkflowId={workspace.workflowId}
|
||||
onSelect={_handleConversationSelect}
|
||||
onCreateNew={workspace.resetToNew}
|
||||
refreshTrigger={workspace.workflowVersion}
|
||||
/>
|
||||
)}
|
||||
{leftTab === 'files' && (
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ interface UseWorkspaceReturn {
|
|||
sendMessage: (prompt: string, fileIds?: string[], dataSourceIds?: string[], allowedProviders?: string[]) => void;
|
||||
stopProcessing: () => void;
|
||||
loadWorkflow: (workflowId: string) => void;
|
||||
resetToNew: () => void;
|
||||
files: WorkspaceFile[];
|
||||
folders: WorkspaceFolder[];
|
||||
dataSources: DataSource[];
|
||||
|
|
@ -86,6 +87,7 @@ interface UseWorkspaceReturn {
|
|||
acceptEdit: (editId: string) => void;
|
||||
rejectEdit: (editId: string) => void;
|
||||
workflowId: string | null;
|
||||
workflowVersion: number;
|
||||
refreshFiles: () => void;
|
||||
refreshFolders: () => void;
|
||||
refreshDataSources: () => void;
|
||||
|
|
@ -102,6 +104,7 @@ export function useWorkspace(instanceId: string): UseWorkspaceReturn {
|
|||
const [toolActivities, setToolActivities] = useState<ToolActivity[]>([]);
|
||||
const [pendingEdits, setPendingEdits] = useState<FileEditProposal[]>([]);
|
||||
const [workflowId, setWorkflowId] = useState<string | null>(null);
|
||||
const [workflowVersion, setWorkflowVersion] = useState(0);
|
||||
const [dataSourceAccesses, setDataSourceAccesses] = useState<DataSourceAccessEvent[]>([]);
|
||||
const cleanupRef = useRef<(() => void) | null>(null);
|
||||
|
||||
|
|
@ -156,6 +159,15 @@ export function useWorkspace(instanceId: string): UseWorkspaceReturn {
|
|||
.catch(() => {});
|
||||
}, [instanceId]);
|
||||
|
||||
const resetToNew = useCallback(() => {
|
||||
setWorkflowId(null);
|
||||
setMessages([]);
|
||||
setToolActivities([]);
|
||||
setPendingEdits([]);
|
||||
setAgentProgress(null);
|
||||
setDataSourceAccesses([]);
|
||||
}, []);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
(prompt: string, fileIds: string[] = [], dataSourceIds: string[] = [], allowedProviders: string[] = []) => {
|
||||
if (!instanceId || isProcessing) return;
|
||||
|
|
@ -292,6 +304,10 @@ export function useWorkspace(instanceId: string): UseWorkspaceReturn {
|
|||
]);
|
||||
}
|
||||
},
|
||||
onWorkflowUpdated: (event) => {
|
||||
if (event.workflowId) setWorkflowId(event.workflowId);
|
||||
setWorkflowVersion(v => v + 1);
|
||||
},
|
||||
onComplete: (event) => {
|
||||
setIsProcessing(false);
|
||||
if (event.workflowId) setWorkflowId(event.workflowId);
|
||||
|
|
@ -365,6 +381,7 @@ export function useWorkspace(instanceId: string): UseWorkspaceReturn {
|
|||
sendMessage,
|
||||
stopProcessing,
|
||||
loadWorkflow,
|
||||
resetToNew,
|
||||
files,
|
||||
folders,
|
||||
dataSources,
|
||||
|
|
@ -374,6 +391,7 @@ export function useWorkspace(instanceId: string): UseWorkspaceReturn {
|
|||
acceptEdit,
|
||||
rejectEdit,
|
||||
workflowId,
|
||||
workflowVersion,
|
||||
refreshFiles,
|
||||
refreshFolders,
|
||||
refreshDataSources,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export interface SseEventHandlers {
|
|||
onFileCreated?: (event: SseEvent) => void;
|
||||
onDataSourceAccess?: (event: SseEvent) => void;
|
||||
onVoiceResponse?: (event: SseEvent) => void;
|
||||
onWorkflowUpdated?: (event: SseEvent) => void;
|
||||
onComplete?: (event: SseEvent) => void;
|
||||
onStopped?: (event: SseEvent) => void;
|
||||
onError?: (event: SseEvent) => void;
|
||||
|
|
@ -58,6 +59,7 @@ const _EVENT_ROUTER: Record<string, keyof SseEventHandlers> = {
|
|||
fileCreated: 'onFileCreated',
|
||||
dataSourceAccess: 'onDataSourceAccess',
|
||||
voiceResponse: 'onVoiceResponse',
|
||||
workflowUpdated: 'onWorkflowUpdated',
|
||||
complete: 'onComplete',
|
||||
stopped: 'onStopped',
|
||||
error: 'onError',
|
||||
|
|
|
|||
Loading…
Reference in a new issue