streamlined workflow process, added workflow stats
This commit is contained in:
parent
e616541ee1
commit
aa5c321f09
12 changed files with 420 additions and 317 deletions
|
|
@ -1,39 +1,26 @@
|
|||
import React from "react";
|
||||
import { Prompt } from "../../../hooks/usePrompts";
|
||||
|
||||
import { Prompt, WorkflowState, WorkflowActions } from "./dashboardChatAreaTypes";
|
||||
import DashboardChatArea from './DashboardChatArea.tsx';
|
||||
|
||||
import styles from './DashboardChatAreaStyles/DashboardChat.module.css';
|
||||
|
||||
interface DashboardChatProps {
|
||||
isExpanded: boolean;
|
||||
onToggleExpand: () => void;
|
||||
selectedPrompt?: Prompt | null;
|
||||
onPromptUsed?: () => void;
|
||||
onWorkflowIdChange?: (workflowId: string | null) => void;
|
||||
onWorkflowCompletedChange?: (completed: boolean) => void;
|
||||
currentWorkflowId?: string | null;
|
||||
workflowState: WorkflowState;
|
||||
workflowActions: WorkflowActions;
|
||||
}
|
||||
|
||||
const DashboardChat: React.FC<DashboardChatProps> = ({
|
||||
selectedPrompt,
|
||||
onPromptUsed,
|
||||
onWorkflowIdChange,
|
||||
onWorkflowCompletedChange,
|
||||
currentWorkflowId
|
||||
workflowState,
|
||||
workflowActions
|
||||
}) => {
|
||||
// Pass the current workflow ID directly to the chat area
|
||||
// This handles both dropdown selection and workflow resume scenarios
|
||||
const effectiveWorkflowId = currentWorkflowId;
|
||||
|
||||
return (
|
||||
<div className={styles.dashboard_chat}>
|
||||
<DashboardChatArea
|
||||
selectedPrompt={selectedPrompt}
|
||||
onPromptUsed={onPromptUsed}
|
||||
onWorkflowIdChange={onWorkflowIdChange}
|
||||
onWorkflowCompletedChange={onWorkflowCompletedChange}
|
||||
resumeWorkflowId={effectiveWorkflowId}
|
||||
selectedPrompt={selectedPrompt}
|
||||
workflowState={workflowState}
|
||||
workflowActions={workflowActions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,64 +1,18 @@
|
|||
import React, { useState, useRef } from "react";
|
||||
import React, { useState } from "react";
|
||||
import MessageList from "./DashboardChatAreaMessageList";
|
||||
import FilePreview from "./DashboardChatAreaFilePreview";
|
||||
import InputArea from "./DashboardChatAreaInput";
|
||||
import ConnectedFiles from "./DashboardChatAreaConnectedFiles";
|
||||
import { DashboardChatAreaProps } from "./dashboardChatAreaTypes";
|
||||
import { useWorkflowManager } from "./useWorkflowManager";
|
||||
import styles from './DashboardChatAreaStyles/DashboardChat.module.css';
|
||||
|
||||
const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
|
||||
selectedPrompt,
|
||||
onPromptUsed,
|
||||
onWorkflowIdChange,
|
||||
onWorkflowCompletedChange,
|
||||
resumeWorkflowId
|
||||
workflowState,
|
||||
workflowActions
|
||||
}) => {
|
||||
// Fixed grid layout - no resizing
|
||||
|
||||
// File selection state
|
||||
const [selectedFile, setSelectedFile] = useState<any>(null);
|
||||
const [attachedFiles, setAttachedFiles] = useState<any[]>([]);
|
||||
|
||||
// Track the last resumeWorkflowId to prevent feedback loops
|
||||
const lastResumeWorkflowIdRef = useRef<string | null>(resumeWorkflowId);
|
||||
|
||||
// Centralized workflow management
|
||||
const [workflowState, workflowActions] = useWorkflowManager(resumeWorkflowId);
|
||||
|
||||
// Only notify parent when a NEW workflow is created internally (not when selecting existing ones)
|
||||
// For workflow selection via dropdown, the Dashboard already manages the currentWorkflowId
|
||||
React.useEffect(() => {
|
||||
// Only notify when a workflow is created from scratch (currentWorkflowId exists but resumeWorkflowId was null)
|
||||
if (onWorkflowIdChange && workflowState.currentWorkflowId && !resumeWorkflowId && !lastResumeWorkflowIdRef.current) {
|
||||
console.log(`🔔 Notifying parent of NEW workflow created: ${workflowState.currentWorkflowId}`);
|
||||
onWorkflowIdChange(workflowState.currentWorkflowId);
|
||||
}
|
||||
|
||||
// Update the ref to track the current resumeWorkflowId
|
||||
lastResumeWorkflowIdRef.current = resumeWorkflowId;
|
||||
}, [workflowState.currentWorkflowId, onWorkflowIdChange, resumeWorkflowId]);
|
||||
|
||||
// Notify parent when workflow is completed
|
||||
React.useEffect(() => {
|
||||
if (onWorkflowCompletedChange && workflowState.workflow) {
|
||||
const isCompleted = ['completed', 'failed', 'stopped'].includes(workflowState.workflow.status);
|
||||
onWorkflowCompletedChange(isCompleted);
|
||||
}
|
||||
}, [workflowState.workflow?.status, onWorkflowCompletedChange]);
|
||||
|
||||
// Note: useWorkflowManager handles resumeWorkflowId changes automatically
|
||||
|
||||
// No resizing functionality needed
|
||||
|
||||
console.log('🎯 DashboardChatArea render:', {
|
||||
currentWorkflowId: workflowState.currentWorkflowId,
|
||||
resumeWorkflowId,
|
||||
workflowStatus: workflowState.workflow?.status,
|
||||
messagesCount: workflowState.messages?.length || 0,
|
||||
isLoading: workflowState.isLoading,
|
||||
isPolling: workflowState.isPolling
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.chat_grid}>
|
||||
|
|
@ -79,11 +33,10 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
|
|||
<div className={`${styles.quadrant} ${styles.input_quadrant}`}>
|
||||
<InputArea
|
||||
selectedPrompt={selectedPrompt}
|
||||
onPromptUsed={onPromptUsed}
|
||||
workflowState={workflowState}
|
||||
workflowActions={workflowActions}
|
||||
onAttachedFilesChange={setAttachedFiles}
|
||||
attachedFiles={attachedFiles}
|
||||
onAttachedFilesChange={setAttachedFiles}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Prompt } from '../../../hooks/usePrompts';
|
||||
import FileAttachmentPopup from './FileAttachmentPopup';
|
||||
import { WorkflowManagerState, WorkflowManagerActions } from './useWorkflowManager';
|
||||
import { Prompt, WorkflowState, WorkflowActions } from './dashboardChatAreaTypes';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
|
||||
import styles from './DashboardChatAreaStyles/DashboardChatAreaInput.module.css';
|
||||
|
|
@ -9,11 +8,10 @@ import sharedStyles from './DashboardChatAreaStyles/DashboardChat.module.css';
|
|||
|
||||
interface InputAreaProps {
|
||||
selectedPrompt?: Prompt | null;
|
||||
onPromptUsed?: () => void;
|
||||
workflowState: WorkflowManagerState;
|
||||
workflowActions: WorkflowManagerActions;
|
||||
onAttachedFilesChange?: (files: AttachedFile[]) => void;
|
||||
workflowState: WorkflowState;
|
||||
workflowActions: WorkflowActions;
|
||||
attachedFiles?: AttachedFile[];
|
||||
onAttachedFilesChange?: (files: AttachedFile[]) => void;
|
||||
}
|
||||
|
||||
interface AttachedFile {
|
||||
|
|
@ -27,11 +25,10 @@ interface AttachedFile {
|
|||
|
||||
const InputArea: React.FC<InputAreaProps> = ({
|
||||
selectedPrompt,
|
||||
onPromptUsed,
|
||||
workflowState,
|
||||
workflowActions,
|
||||
onAttachedFilesChange,
|
||||
attachedFiles: externalAttachedFiles = []
|
||||
attachedFiles: externalAttachedFiles = [],
|
||||
onAttachedFilesChange
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
|
@ -109,7 +106,7 @@ const InputArea: React.FC<InputAreaProps> = ({
|
|||
if (onAttachedFilesChange) {
|
||||
onAttachedFilesChange([]);
|
||||
}
|
||||
if (onPromptUsed) onPromptUsed();
|
||||
// Prompt was used
|
||||
} else {
|
||||
setSendError('Failed to send message. Please try again.');
|
||||
}
|
||||
|
|
@ -122,22 +119,8 @@ const InputArea: React.FC<InputAreaProps> = ({
|
|||
};
|
||||
|
||||
const handleStop = async () => {
|
||||
setIsSending(true);
|
||||
setSendError(null);
|
||||
|
||||
try {
|
||||
console.log('🛑 Stopping workflow...');
|
||||
const success = await workflowActions.stopWorkflow();
|
||||
|
||||
if (!success) {
|
||||
setSendError('Failed to stop workflow. Please try again.');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to stop workflow:', error);
|
||||
setSendError(error.message || 'Failed to stop workflow. Please try again.');
|
||||
} finally {
|
||||
setIsSending(false);
|
||||
}
|
||||
// Stop the workflow but keep it selected
|
||||
await workflowActions.stopWorkflow();
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
|
|
@ -215,10 +198,7 @@ const InputArea: React.FC<InputAreaProps> = ({
|
|||
const isWorkflowActive = workflowState.workflow &&
|
||||
['running', 'processing', 'started'].includes(workflowState.workflow.status);
|
||||
|
||||
|
||||
|
||||
// Use polling as an indicator that workflow might be active
|
||||
const shouldShowStopButton = isWorkflowActive || (workflowState.isPolling && workflowState.currentWorkflowId);
|
||||
const shouldShowStopButton = !!isWorkflowActive;
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React from 'react';
|
||||
import { useApiRequest } from '../../../hooks/useApi';
|
||||
import MessageItem from './DashboardChatAreaMessageItem';
|
||||
import { WorkflowMessage, Document } from './dashboardChatAreaTypes';
|
||||
import { WorkflowManagerState } from './useWorkflowManager';
|
||||
import { WorkflowMessage, Document, WorkflowState } from './dashboardChatAreaTypes';
|
||||
|
||||
interface MessageListProps {
|
||||
workflowState: WorkflowManagerState;
|
||||
workflowState: WorkflowState;
|
||||
onFilePreview?: (file: any) => void;
|
||||
}
|
||||
|
||||
|
|
@ -13,8 +12,20 @@ interface MessageListProps {
|
|||
const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, request: any): Promise<any> => {
|
||||
let documents: Document[] = [];
|
||||
|
||||
// Fetch file metadata if fileIds exist
|
||||
if (workflowMessage.fileIds && workflowMessage.fileIds.length > 0) {
|
||||
// Check if we have documents directly from the API or need to fetch via fileIds
|
||||
if (workflowMessage.documents && workflowMessage.documents.length > 0) {
|
||||
// Use documents directly from the API
|
||||
documents = workflowMessage.documents.map(doc => ({
|
||||
id: doc.id || doc.fileId,
|
||||
fileId: parseInt(doc.fileId),
|
||||
name: doc.filename,
|
||||
ext: doc.filename.split('.').pop() || 'unknown',
|
||||
type: doc.mimeType,
|
||||
size: doc.fileSize,
|
||||
downloadUrl: `/api/workflows/files/${doc.fileId}/download`
|
||||
}));
|
||||
} else if (workflowMessage.fileIds && workflowMessage.fileIds.length > 0) {
|
||||
// Fallback to legacy fileIds approach
|
||||
console.log(`📎 Processing ${workflowMessage.fileIds.length} files for message ${workflowMessage.id}`);
|
||||
|
||||
const documentPromises = workflowMessage.fileIds.map(async (fileId, fileIndex) => {
|
||||
|
|
@ -54,9 +65,9 @@ const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, reques
|
|||
documents = await Promise.all(documentPromises);
|
||||
}
|
||||
|
||||
// Try different possible field names for content
|
||||
const possibleContent = workflowMessage.content ||
|
||||
(workflowMessage as any).message ||
|
||||
// Try different possible field names for content (API uses 'message', some legacy might use 'content')
|
||||
const possibleContent = workflowMessage.message ||
|
||||
workflowMessage.content ||
|
||||
(workflowMessage as any).text ||
|
||||
(workflowMessage as any).body ||
|
||||
'';
|
||||
|
|
@ -117,14 +128,15 @@ const MessageList: React.FC<MessageListProps> = ({
|
|||
const transformed = await Promise.all(
|
||||
workflowState.messages.map(async (msg: WorkflowMessage, index: number) => {
|
||||
console.log(`🔄 Transforming message ${index + 1}/${workflowState.messages.length}: ${msg.id}`);
|
||||
const content = msg.message || msg.content || '';
|
||||
console.log(`📝 RAW API Message (${msg.role}):`, {
|
||||
id: msg.id,
|
||||
rawMessage: msg,
|
||||
contentType: typeof msg.content,
|
||||
contentValue: msg.content,
|
||||
contentLength: msg.content?.length || 0,
|
||||
hasContent: !!msg.content,
|
||||
contentPreview: msg.content?.substring(0, 100) + (msg.content?.length > 100 ? '...' : ''),
|
||||
contentType: typeof content,
|
||||
contentValue: content,
|
||||
contentLength: content.length,
|
||||
hasContent: !!content,
|
||||
contentPreview: content.substring(0, 100) + (content.length > 100 ? '...' : ''),
|
||||
fileCount: msg.fileIds?.length || 0,
|
||||
allKeys: Object.keys(msg)
|
||||
});
|
||||
|
|
@ -200,7 +212,7 @@ const MessageList: React.FC<MessageListProps> = ({
|
|||
}
|
||||
}, [transformedMessages.length, scrollToBottom]);
|
||||
|
||||
const { currentWorkflowId, workflow, isLoading, error, isPolling } = workflowState;
|
||||
const { currentWorkflowId, workflow, isLoading, error } = workflowState;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -255,7 +267,7 @@ const MessageList: React.FC<MessageListProps> = ({
|
|||
<strong>Status:</strong> {workflow.status}
|
||||
{workflow.currentRound && ` (Round ${workflow.currentRound})`}
|
||||
{/* Show polling indicator */}
|
||||
{isPolling && (
|
||||
{workflow && ['running', 'processing', 'started'].includes(workflow.status) && (
|
||||
<span style={{
|
||||
marginLeft: '8px',
|
||||
fontSize: '12px',
|
||||
|
|
|
|||
|
|
@ -200,11 +200,10 @@
|
|||
}
|
||||
|
||||
.button_primary:disabled {
|
||||
background-color: var(--color-gray-disabled);
|
||||
border: 1px solid var(--color-gray-disabled);
|
||||
background-color: var(--color-secondary-disabled);
|
||||
border: 1px solid var(--color-secondary-disabled);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
border: 1px solid var(--color-gray-disabled);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,134 @@
|
|||
import { Prompt } from "../../../../hooks/usePrompts";
|
||||
// Simplified types - everything in one place
|
||||
export interface Prompt {
|
||||
id: number;
|
||||
name: string;
|
||||
content: string;
|
||||
createdAt?: string;
|
||||
isShared?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardChatAreaProps {
|
||||
selectedPrompt?: Prompt | null;
|
||||
onPromptUsed?: () => void;
|
||||
onWorkflowIdChange?: (workflowId: string | null) => void;
|
||||
onWorkflowCompletedChange?: (completed: boolean) => void;
|
||||
resumeWorkflowId?: string | null;
|
||||
workflowState: WorkflowState;
|
||||
workflowActions: WorkflowActions;
|
||||
}
|
||||
|
||||
// Workflow interfaces - Updated to match full API response
|
||||
export interface WorkflowStats {
|
||||
id: string;
|
||||
processingTime: number;
|
||||
tokenCount: number;
|
||||
bytesSent: number;
|
||||
bytesReceived: number;
|
||||
successRate: number;
|
||||
errorCount: number;
|
||||
}
|
||||
|
||||
export interface WorkflowDocument {
|
||||
id: string;
|
||||
fileId: string;
|
||||
filename: string;
|
||||
fileSize: number;
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
export interface WorkflowMessageStats extends WorkflowStats {}
|
||||
|
||||
export interface WorkflowMessage {
|
||||
id: string;
|
||||
workflowId: string;
|
||||
parentMessageId?: string;
|
||||
documents?: WorkflowDocument[];
|
||||
documentsLabel?: string;
|
||||
message: string;
|
||||
content?: string; // For backward compatibility
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
timestamp?: string;
|
||||
fileIds?: number[];
|
||||
status: string;
|
||||
sequenceNr: number;
|
||||
publishedAt: string;
|
||||
timestamp?: string; // For backward compatibility
|
||||
fileIds?: number[]; // For backward compatibility
|
||||
stats?: WorkflowMessageStats;
|
||||
success: boolean;
|
||||
actionId?: string;
|
||||
actionMethod?: string;
|
||||
actionName?: string;
|
||||
}
|
||||
|
||||
export interface WorkflowLog {
|
||||
id: string;
|
||||
workflowId: string;
|
||||
message: string;
|
||||
type: string;
|
||||
timestamp: string;
|
||||
status: string;
|
||||
progress: number;
|
||||
performance: any;
|
||||
}
|
||||
|
||||
export interface WorkflowAction {
|
||||
id: string;
|
||||
execMethod: string;
|
||||
execAction: string;
|
||||
execParameters: any;
|
||||
execResultLabel: string;
|
||||
expectedDocumentFormats?: any[];
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
error?: string;
|
||||
retryCount: number;
|
||||
retryMax: number;
|
||||
processingTime: number;
|
||||
timestamp: string;
|
||||
result?: string;
|
||||
resultDocuments?: WorkflowDocument[];
|
||||
}
|
||||
|
||||
export interface WorkflowTask {
|
||||
id: string;
|
||||
workflowId: string;
|
||||
userInput: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
error?: string;
|
||||
startedAt: string;
|
||||
finishedAt?: string;
|
||||
actionList: WorkflowAction[];
|
||||
retryCount: number;
|
||||
retryMax: number;
|
||||
rollbackOnFailure: boolean;
|
||||
dependencies: string[];
|
||||
feedback?: string;
|
||||
processingTime: number;
|
||||
resultLabels: any;
|
||||
}
|
||||
|
||||
export interface Workflow {
|
||||
id: string;
|
||||
mandateId: string;
|
||||
status: string;
|
||||
name?: string;
|
||||
currentRound: number;
|
||||
lastActivity: string;
|
||||
startedAt: string;
|
||||
logs?: WorkflowLog[];
|
||||
messages?: WorkflowMessage[];
|
||||
stats?: WorkflowStats;
|
||||
tasks?: WorkflowTask[];
|
||||
}
|
||||
|
||||
export interface WorkflowState {
|
||||
currentWorkflowId: string | null;
|
||||
workflow: Workflow | null;
|
||||
messages: WorkflowMessage[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface WorkflowActions {
|
||||
loadWorkflow: (workflowId: string) => void;
|
||||
startNewWorkflow: (prompt: string, fileIds?: number[]) => Promise<string | null>;
|
||||
continueWorkflow: (prompt: string, fileIds?: number[]) => Promise<boolean>;
|
||||
stopWorkflow: () => Promise<boolean>;
|
||||
clearWorkflow: () => void;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,8 @@
|
|||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { useWorkflow, useWorkflowStatus, useWorkflowMessages, useWorkflowOperations, StartWorkflowRequest } from '../../../hooks/useWorkflows';
|
||||
import { WorkflowState, WorkflowActions } from './dashboardChatAreaTypes';
|
||||
|
||||
export interface WorkflowManagerState {
|
||||
currentWorkflowId: string | null;
|
||||
workflow: any;
|
||||
messages: any[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
isPolling: boolean;
|
||||
}
|
||||
|
||||
export interface WorkflowManagerActions {
|
||||
loadWorkflow: (workflowId: string) => Promise<void>;
|
||||
startNewWorkflow: (prompt: string, fileIds: number[]) => Promise<string | null>;
|
||||
continueWorkflow: (prompt: string, fileIds: number[]) => Promise<boolean>;
|
||||
stopWorkflow: () => Promise<boolean>;
|
||||
clearWorkflow: () => void;
|
||||
refreshMessages: () => Promise<void>;
|
||||
setPolling: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export function useWorkflowManager(initialWorkflowId?: string | null): [WorkflowManagerState, WorkflowManagerActions] {
|
||||
console.log('🏭 useWorkflowManager called with initialWorkflowId:', initialWorkflowId);
|
||||
|
||||
export function useWorkflowManager(initialWorkflowId?: string | null): [WorkflowState, WorkflowActions] {
|
||||
// Core state
|
||||
const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(initialWorkflowId || null);
|
||||
const [isPolling, setIsPolling] = useState(false);
|
||||
|
|
@ -46,27 +26,19 @@ export function useWorkflowManager(initialWorkflowId?: string | null): [Workflow
|
|||
// Auto-polling for active workflows and message updates
|
||||
useEffect(() => {
|
||||
if (isPolling && currentWorkflowId) {
|
||||
console.log(`🔄 Starting auto-polling for workflow: ${currentWorkflowId}`);
|
||||
|
||||
pollingIntervalRef.current = window.setInterval(() => {
|
||||
console.log('🔄 Auto-polling status...');
|
||||
// Always poll for status to detect workflow state changes
|
||||
refetchStatus();
|
||||
|
||||
// Only poll messages if workflow is running
|
||||
if (currentWorkflow) {
|
||||
const isActive = ['running', 'processing', 'started'].includes(currentWorkflow.status);
|
||||
if (isActive) {
|
||||
console.log('🔄 Workflow is active, also polling messages...');
|
||||
refetchMessages();
|
||||
}
|
||||
}
|
||||
}, 2000); // Poll every 2 seconds for smoother updates
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (pollingIntervalRef.current) {
|
||||
console.log('🛑 Stopping auto-polling');
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
|
|
@ -75,14 +47,10 @@ export function useWorkflowManager(initialWorkflowId?: string | null): [Workflow
|
|||
|
||||
// Actions
|
||||
const loadWorkflow = useCallback(async (workflowId: string) => {
|
||||
console.log(`📂 Loading workflow: ${workflowId}`);
|
||||
setCurrentWorkflowId(workflowId);
|
||||
// The hooks will automatically fetch data when workflowId changes
|
||||
}, []);
|
||||
|
||||
const startNewWorkflow = useCallback(async (prompt: string, fileIds: number[] = []): Promise<string | null> => {
|
||||
console.log('🚀 Starting new workflow with prompt:', prompt.substring(0, 50) + '...');
|
||||
|
||||
const workflowData: StartWorkflowRequest = {
|
||||
prompt,
|
||||
listFileId: fileIds
|
||||
|
|
@ -92,30 +60,17 @@ export function useWorkflowManager(initialWorkflowId?: string | null): [Workflow
|
|||
|
||||
if (result.success && result.data) {
|
||||
const newWorkflowId = result.data.id;
|
||||
console.log(`✅ New workflow started: ${newWorkflowId}`);
|
||||
setCurrentWorkflowId(newWorkflowId);
|
||||
setIsPolling(true); // Start polling immediately for new workflows
|
||||
|
||||
// Also immediately fetch messages to get the user's message
|
||||
setTimeout(() => {
|
||||
refetchMessages();
|
||||
}, 500);
|
||||
|
||||
setIsPolling(true);
|
||||
setTimeout(() => refetchMessages(), 500);
|
||||
return newWorkflowId;
|
||||
} else {
|
||||
console.error('❌ Failed to start workflow:', result.error);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}, [startWorkflow, refetchMessages]);
|
||||
|
||||
const continueWorkflow = useCallback(async (prompt: string, fileIds: number[] = []): Promise<boolean> => {
|
||||
if (!currentWorkflowId) {
|
||||
console.error('❌ Cannot continue workflow: no current workflow ID');
|
||||
return false;
|
||||
}
|
||||
if (!currentWorkflowId) return false;
|
||||
|
||||
console.log(`➡️ Continuing workflow ${currentWorkflowId} with prompt:`, prompt.substring(0, 50) + '...');
|
||||
|
||||
const workflowData: StartWorkflowRequest = {
|
||||
prompt,
|
||||
listFileId: fileIds
|
||||
|
|
@ -124,71 +79,39 @@ export function useWorkflowManager(initialWorkflowId?: string | null): [Workflow
|
|||
const result = await startWorkflow(workflowData, currentWorkflowId);
|
||||
|
||||
if (result.success) {
|
||||
console.log(`✅ Workflow ${currentWorkflowId} continued`);
|
||||
setIsPolling(true); // Ensure polling is enabled
|
||||
|
||||
// Immediately start polling for new messages
|
||||
setTimeout(() => {
|
||||
refetchMessages();
|
||||
}, 500);
|
||||
|
||||
setIsPolling(true);
|
||||
setTimeout(() => refetchMessages(), 500);
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ Failed to continue workflow:', result.error);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}, [currentWorkflowId, startWorkflow, refetchMessages]);
|
||||
|
||||
const stopWorkflow = useCallback(async (): Promise<boolean> => {
|
||||
if (!currentWorkflowId) {
|
||||
console.error('❌ Cannot stop workflow: no current workflow ID');
|
||||
return false;
|
||||
}
|
||||
if (!currentWorkflowId) return false;
|
||||
|
||||
console.log(`🛑 Stopping workflow ${currentWorkflowId}`);
|
||||
|
||||
const success = await stopWorkflowRequest(currentWorkflowId);
|
||||
|
||||
if (success) {
|
||||
console.log(`✅ Workflow ${currentWorkflowId} stopped`);
|
||||
setIsPolling(false); // Stop polling immediately
|
||||
|
||||
// Refresh workflow status to get the updated status
|
||||
const result = await stopWorkflowRequest(currentWorkflowId);
|
||||
if (result) {
|
||||
// Immediately refresh status to reflect the stopped state
|
||||
setTimeout(() => {
|
||||
refetchStatus();
|
||||
refetchMessages();
|
||||
}, 500);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ Failed to stop workflow');
|
||||
return false;
|
||||
}
|
||||
}, [currentWorkflowId, stopWorkflowRequest, refetchStatus]);
|
||||
return false;
|
||||
}, [currentWorkflowId, stopWorkflowRequest, refetchStatus, refetchMessages]);
|
||||
|
||||
const clearWorkflow = useCallback(() => {
|
||||
console.log('🧹 Clearing workflow');
|
||||
setCurrentWorkflowId(null);
|
||||
setIsPolling(false);
|
||||
}, []);
|
||||
|
||||
const refreshMessages = useCallback(async () => {
|
||||
console.log('🔄 Manually refreshing messages');
|
||||
await refetchMessages();
|
||||
}, [refetchMessages]);
|
||||
|
||||
const setPollingEnabled = useCallback((enabled: boolean) => {
|
||||
console.log(`🔄 Setting polling to: ${enabled}`);
|
||||
setIsPolling(enabled);
|
||||
}, []);
|
||||
|
||||
// Sync with external workflow ID changes
|
||||
useEffect(() => {
|
||||
if (initialWorkflowId !== currentWorkflowId) {
|
||||
if (initialWorkflowId) {
|
||||
console.log(`🔄 useWorkflowManager: Loading workflow ${initialWorkflowId}`);
|
||||
loadWorkflow(initialWorkflowId);
|
||||
} else {
|
||||
console.log(`🧹 useWorkflowManager: Clearing workflow due to null initialWorkflowId`);
|
||||
setCurrentWorkflowId(null);
|
||||
setIsPolling(false);
|
||||
}
|
||||
|
|
@ -199,39 +122,27 @@ export function useWorkflowManager(initialWorkflowId?: string | null): [Workflow
|
|||
useEffect(() => {
|
||||
if (currentWorkflowId && currentWorkflow) {
|
||||
const isActive = ['running', 'processing', 'started'].includes(currentWorkflow.status);
|
||||
|
||||
if (isActive) {
|
||||
console.log(`🟢 Workflow ${currentWorkflowId} is active (${currentWorkflow.status}), enabling polling`);
|
||||
setIsPolling(true);
|
||||
} else {
|
||||
console.log(`🔴 Workflow ${currentWorkflowId} is inactive/completed (${currentWorkflow.status}), disabling polling`);
|
||||
setIsPolling(false);
|
||||
}
|
||||
setIsPolling(isActive);
|
||||
} else {
|
||||
// No workflow ID or no workflow data, stop polling
|
||||
console.log(`🛑 No workflow or workflow data, disabling polling`);
|
||||
setIsPolling(false);
|
||||
}
|
||||
}, [currentWorkflowId, currentWorkflow?.status]);
|
||||
|
||||
const state: WorkflowManagerState = {
|
||||
const state: WorkflowState = {
|
||||
currentWorkflowId,
|
||||
workflow: currentWorkflow,
|
||||
messages,
|
||||
isLoading,
|
||||
error,
|
||||
isPolling
|
||||
error
|
||||
};
|
||||
|
||||
const actions: WorkflowManagerActions = useMemo(() => ({
|
||||
const actions: WorkflowActions = useMemo(() => ({
|
||||
loadWorkflow,
|
||||
startNewWorkflow,
|
||||
continueWorkflow,
|
||||
stopWorkflow,
|
||||
clearWorkflow,
|
||||
refreshMessages,
|
||||
setPolling: setPollingEnabled
|
||||
}), [loadWorkflow, startNewWorkflow, continueWorkflow, stopWorkflow, clearWorkflow, refreshMessages, setPollingEnabled]);
|
||||
clearWorkflow
|
||||
}), [loadWorkflow, startNewWorkflow, continueWorkflow, stopWorkflow, clearWorkflow]);
|
||||
|
||||
return [state, actions];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,9 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useApiRequest } from './useApi';
|
||||
|
||||
// Workflow interfaces (matching backend ChatWorkflow model)
|
||||
export interface Workflow {
|
||||
id: string;
|
||||
mandateId: string;
|
||||
status: string;
|
||||
name?: string;
|
||||
title?: string; // Keep for backward compatibility
|
||||
currentRound: number;
|
||||
lastActivity: string;
|
||||
startedAt: string;
|
||||
logs?: any[];
|
||||
messages?: any[];
|
||||
stats?: Record<string, any>;
|
||||
tasks?: any[];
|
||||
dataStats?: Record<string, any>; // Keep for backward compatibility
|
||||
userId?: number; // Keep for backward compatibility
|
||||
messageIds?: string[]; // Keep for backward compatibility
|
||||
}
|
||||
|
||||
export interface WorkflowMessage {
|
||||
id: string;
|
||||
content: string;
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
timestamp?: string;
|
||||
sequenceNo?: number;
|
||||
fileIds?: number[];
|
||||
}
|
||||
// Import the centralized workflow interfaces
|
||||
import type { Workflow, WorkflowMessage, WorkflowStats, WorkflowDocument } from '../components/Dashboard/DashboardChat/dashboardChatAreaTypes';
|
||||
export type { Workflow, WorkflowMessage, WorkflowStats, WorkflowDocument };
|
||||
|
||||
export interface StartWorkflowRequest {
|
||||
prompt: string;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,19 @@ export default {
|
|||
'dashboard.workflow_dropdown.available_workflows': 'Verfügbare Workflows',
|
||||
'dashboard.workflow_dropdown.no_workflows': 'Keine Workflows verfügbar',
|
||||
|
||||
// Workflow Stats
|
||||
'dashboard.stats.workflow': 'Workflow',
|
||||
'dashboard.stats.status': 'Status',
|
||||
'dashboard.stats.rounds': 'Runden',
|
||||
'dashboard.stats.messages': 'Nachrichten',
|
||||
'dashboard.stats.files': 'Dateien',
|
||||
'dashboard.stats.tokens': 'Token',
|
||||
'dashboard.stats.data_sent': 'Daten gesendet',
|
||||
'dashboard.stats.data_received': 'Daten empfangen',
|
||||
'dashboard.stats.success_rate': 'Erfolgsrate',
|
||||
'dashboard.stats.errors': 'Fehler',
|
||||
'dashboard.stats.started': 'Gestartet',
|
||||
|
||||
// Prompt Set
|
||||
'promptset.loading': 'Prompts werden geladen...',
|
||||
'promptset.error.loading': 'Fehler beim Laden der Prompts',
|
||||
|
|
|
|||
|
|
@ -63,6 +63,19 @@ export default {
|
|||
'dashboard.workflow_dropdown.available_workflows': 'Available Workflows',
|
||||
'dashboard.workflow_dropdown.no_workflows': 'No workflows available',
|
||||
|
||||
// Workflow Stats
|
||||
'dashboard.stats.workflow': 'Workflow',
|
||||
'dashboard.stats.status': 'Status',
|
||||
'dashboard.stats.rounds': 'Rounds',
|
||||
'dashboard.stats.messages': 'Messages',
|
||||
'dashboard.stats.files': 'Files',
|
||||
'dashboard.stats.tokens': 'Tokens',
|
||||
'dashboard.stats.data_sent': 'Data Sent',
|
||||
'dashboard.stats.data_received': 'Data Received',
|
||||
'dashboard.stats.success_rate': 'Success Rate',
|
||||
'dashboard.stats.errors': 'Errors',
|
||||
'dashboard.stats.started': 'Started',
|
||||
|
||||
// Prompt Set
|
||||
'promptset.loading': 'Loading prompts...',
|
||||
'promptset.error.loading': 'Error loading prompts',
|
||||
|
|
|
|||
|
|
@ -63,6 +63,19 @@ export default {
|
|||
'dashboard.workflow_dropdown.available_workflows': 'Workflows disponibles',
|
||||
'dashboard.workflow_dropdown.no_workflows': 'Aucun workflow disponible',
|
||||
|
||||
// Workflow Stats
|
||||
'dashboard.stats.workflow': 'Workflow',
|
||||
'dashboard.stats.status': 'Statut',
|
||||
'dashboard.stats.rounds': 'Tours',
|
||||
'dashboard.stats.messages': 'Messages',
|
||||
'dashboard.stats.files': 'Fichiers',
|
||||
'dashboard.stats.tokens': 'Jetons',
|
||||
'dashboard.stats.data_sent': 'Données envoyées',
|
||||
'dashboard.stats.data_received': 'Données reçues',
|
||||
'dashboard.stats.success_rate': 'Taux de succès',
|
||||
'dashboard.stats.errors': 'Erreurs',
|
||||
'dashboard.stats.started': 'Démarré',
|
||||
|
||||
// Prompt Set
|
||||
'promptset.loading': 'Chargement des prompts...',
|
||||
'promptset.error.loading': 'Erreur lors du chargement des prompts',
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { IoMdRefresh, IoMdArrowDropdown } from 'react-icons/io';
|
||||
import { Prompt } from '../../hooks/usePrompts';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import { useWorkflows, Workflow } from '../../hooks/useWorkflows';
|
||||
import { useWorkflows } from '../../hooks/useWorkflows';
|
||||
import { Prompt, Workflow } from '../../components/Dashboard/DashboardChat/dashboardChatAreaTypes';
|
||||
import { useWorkflowManager } from '../../components/Dashboard/DashboardChat/useWorkflowManager';
|
||||
import styles from './HomeStyles/Dashboard.module.css'
|
||||
import sharedStyles from '../../components/PageManager/pages.module.css';
|
||||
|
||||
|
|
@ -10,32 +11,25 @@ import DashboardChat from '../../components/Dashboard/DashboardChat/DashboardCha
|
|||
|
||||
function Dashboard () {
|
||||
const { t } = useLanguage();
|
||||
const [isChatExpanded, setIsChatExpanded] = useState(false);
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
|
||||
const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
|
||||
|
||||
console.log('🏠 Dashboard render with currentWorkflowId:', currentWorkflowId);
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Central workflow management
|
||||
const [workflowState, workflowActions] = useWorkflowManager();
|
||||
|
||||
// Fetch workflows for dropdown
|
||||
const { workflows, loading: workflowsLoading, error: workflowsError } = useWorkflows();
|
||||
|
||||
const handleChatToggleExpand = () => {
|
||||
setIsChatExpanded(!isChatExpanded);
|
||||
};
|
||||
|
||||
const handleWorkflowIdChange = useCallback((workflowId: string | null) => {
|
||||
console.log('🔄 Dashboard.handleWorkflowIdChange called with:', workflowId);
|
||||
setCurrentWorkflowId(workflowId);
|
||||
}, []);
|
||||
|
||||
const handleWorkflowSelect = useCallback((workflowId: string) => {
|
||||
console.log('📋 Dashboard.handleWorkflowSelect called with:', workflowId);
|
||||
// Set the workflow ID when selected from dropdown
|
||||
setCurrentWorkflowId(workflowId);
|
||||
workflowActions.loadWorkflow(workflowId);
|
||||
setIsDropdownOpen(false);
|
||||
}, []);
|
||||
}, [workflowActions]);
|
||||
|
||||
const handleResetWorkflow = useCallback(() => {
|
||||
workflowActions.clearWorkflow();
|
||||
setSelectedPrompt(null);
|
||||
}, [workflowActions]);
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
|
|
@ -44,33 +38,99 @@ function Dashboard () {
|
|||
setIsDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// Helper functions for workflow dropdown
|
||||
const getWorkflowDisplayName = (workflow: Workflow) => {
|
||||
// Use name first, then title, then fallback to id (first 8 chars)
|
||||
return workflow.name || workflow.title || `${workflow.id.substring(0, 8)}...`;
|
||||
};
|
||||
const getWorkflowDisplayName = (workflow: Workflow) =>
|
||||
workflow.name || `${workflow.id.substring(0, 8)}...`;
|
||||
|
||||
const formatWorkflowId = (id: string) => {
|
||||
return `${id.substring(0, 8)}...`;
|
||||
};
|
||||
|
||||
const handleResetWorkflow = useCallback(() => {
|
||||
setCurrentWorkflowId(null);
|
||||
setSelectedPrompt(null);
|
||||
}, []);
|
||||
const formatWorkflowId = (id: string) => `${id.substring(0, 8)}...`;
|
||||
|
||||
// Format workflow ID for display
|
||||
const displayWorkflowId = currentWorkflowId ?
|
||||
`${currentWorkflowId.substring(0, 8)}...` :
|
||||
const displayWorkflowId = workflowState.currentWorkflowId ?
|
||||
`${workflowState.currentWorkflowId.substring(0, 8)}...` :
|
||||
t('dashboard.log.no_workflow');
|
||||
|
||||
// Calculate workflow stats
|
||||
const getWorkflowStats = () => {
|
||||
if (!workflowState.workflow) return null;
|
||||
|
||||
const workflow = workflowState.workflow;
|
||||
const messages = workflowState.messages; // Use messages from workflowState, not workflow.messages
|
||||
const messageCount = messages?.length || 0;
|
||||
const fileCount = messages?.reduce((count, msg) => {
|
||||
return count + (msg.documents?.length || msg.fileIds?.length || 0);
|
||||
}, 0) || 0;
|
||||
|
||||
// Aggregate stats from workflow stats and message stats
|
||||
const totalTokens = (workflow.stats?.tokenCount || 0) +
|
||||
(messages?.reduce((sum, msg) => sum + (msg.stats?.tokenCount || 0), 0) || 0);
|
||||
const totalBytesSent = (workflow.stats?.bytesSent || 0) +
|
||||
(messages?.reduce((sum, msg) => sum + (msg.stats?.bytesSent || 0), 0) || 0);
|
||||
const totalBytesReceived = (workflow.stats?.bytesReceived || 0) +
|
||||
(messages?.reduce((sum, msg) => sum + (msg.stats?.bytesReceived || 0), 0) || 0);
|
||||
const totalErrors = (workflow.stats?.errorCount || 0) +
|
||||
(messages?.reduce((sum, msg) => sum + (msg.stats?.errorCount || 0), 0) || 0);
|
||||
|
||||
// Calculate overall success rate
|
||||
const successfulMessages = messages?.filter(msg => msg.success)?.length || 0;
|
||||
const successRate = messageCount > 0 ? (successfulMessages / messageCount) * 100 : 100;
|
||||
|
||||
return {
|
||||
status: workflow.status,
|
||||
rounds: workflow.currentRound,
|
||||
startedAt: workflow.startedAt,
|
||||
messageCount,
|
||||
fileCount,
|
||||
tokenCount: totalTokens,
|
||||
bytesSent: totalBytesSent,
|
||||
bytesReceived: totalBytesReceived,
|
||||
successRate: Math.round(successRate),
|
||||
errorCount: totalErrors
|
||||
};
|
||||
};
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleString();
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'completed':
|
||||
case 'success':
|
||||
case 'finished':
|
||||
return 'var(--color-success)';
|
||||
case 'running':
|
||||
case 'processing':
|
||||
case 'started':
|
||||
case 'in_progress':
|
||||
return 'var(--color-warning)';
|
||||
case 'failed':
|
||||
case 'error':
|
||||
case 'cancelled':
|
||||
return 'var(--color-error)';
|
||||
case 'pending':
|
||||
case 'queued':
|
||||
case 'waiting':
|
||||
return 'var(--color-text-secondary)';
|
||||
default:
|
||||
return 'var(--color-text)';
|
||||
}
|
||||
};
|
||||
|
||||
const formatStatus = (status: string) => {
|
||||
return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={sharedStyles.pageContainer}>
|
||||
<div className={`${sharedStyles.pageCard} ${styles.dashboardPageCard}`}>
|
||||
|
|
@ -80,13 +140,87 @@ function Dashboard () {
|
|||
<div className={sharedStyles.pageHeader}>
|
||||
<h1 className={sharedStyles.pageTitle}>{t('nav.dashboard')}</h1>
|
||||
<div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
|
||||
{currentWorkflowId ? (
|
||||
{workflowState.currentWorkflowId ? (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '8px 16px', backgroundColor: 'var(--color-gray-disabled)', borderRadius: '20px' }}>
|
||||
<span style={{ fontSize: '14px', color: 'var(--color-text)', fontWeight: '500' }}>
|
||||
{t('dashboard.log.workflow')}: {displayWorkflowId}
|
||||
</span>
|
||||
</div>
|
||||
{(() => {
|
||||
const stats = getWorkflowStats();
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '20px',
|
||||
padding: '12px 20px',
|
||||
backgroundColor: 'var(--color-gray-disabled)',
|
||||
borderRadius: '12px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500'
|
||||
}}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.workflow')}</span>
|
||||
<span style={{ color: 'var(--color-text)', fontFamily: 'monospace' }}>{displayWorkflowId}</span>
|
||||
</div>
|
||||
|
||||
{stats && (
|
||||
<>
|
||||
<div style={{ width: '1px', height: '40px', backgroundColor: 'var(--color-primary)', opacity: 0.3 }}></div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.status')}</span>
|
||||
<span style={{ color: getStatusColor(stats.status), fontWeight: '600' }}>{formatStatus(stats.status)}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.rounds')}</span>
|
||||
<span style={{ color: 'var(--color-text)' }}>{stats.rounds}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.messages')}</span>
|
||||
<span style={{ color: 'var(--color-text)' }}>{stats.messageCount}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.files')}</span>
|
||||
<span style={{ color: 'var(--color-text)' }}>{stats.fileCount}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.tokens')}</span>
|
||||
<span style={{ color: 'var(--color-text)' }}>{stats.tokenCount.toLocaleString()}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.data_sent')}</span>
|
||||
<span style={{ color: 'var(--color-text)' }}>{formatBytes(stats.bytesSent)}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.data_received')}</span>
|
||||
<span style={{ color: 'var(--color-text)' }}>{formatBytes(stats.bytesReceived)}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.success_rate')}</span>
|
||||
<span style={{
|
||||
color: stats.successRate >= 90 ? 'var(--color-success)' :
|
||||
stats.successRate >= 70 ? 'var(--color-warning)' : 'var(--color-error)'
|
||||
}}>{stats.successRate}%</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.errors')}</span>
|
||||
<span style={{ color: stats.errorCount > 0 ? 'var(--color-error)' : 'var(--color-text)' }}>{stats.errorCount}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
|
||||
<span style={{ color: 'var(--color-text-secondary)', fontSize: '11px', textTransform: 'uppercase' }}>{t('dashboard.stats.started')}</span>
|
||||
<span style={{ color: 'var(--color-text)', fontSize: '12px' }}>{formatDate(stats.startedAt)}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<button
|
||||
className={sharedStyles.secondaryButton}
|
||||
onClick={handleResetWorkflow}
|
||||
|
|
@ -218,15 +352,12 @@ function Dashboard () {
|
|||
<div className={sharedStyles.horizontalDivider}></div>
|
||||
|
||||
<div className={styles.dashboardContentArea}>
|
||||
<div className={`${styles.chatLogContainer} ${isChatExpanded ? styles.expanded : ''}`}>
|
||||
<div className={styles.chatLogContainer}>
|
||||
<div className={styles.chatArea}>
|
||||
<DashboardChat
|
||||
isExpanded={isChatExpanded}
|
||||
onToggleExpand={handleChatToggleExpand}
|
||||
selectedPrompt={selectedPrompt}
|
||||
onPromptUsed={() => setSelectedPrompt(null)}
|
||||
onWorkflowIdChange={handleWorkflowIdChange}
|
||||
currentWorkflowId={currentWorkflowId}
|
||||
workflowState={workflowState}
|
||||
workflowActions={workflowActions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue