/** * ToolActivityLog -- Real-time tool call activity display. * * Renders tool calls in a human-readable format: * - Friendly tool names instead of internal identifiers * - Args filtered to hide UUIDs/internal codes on success * - Full details shown on error for debugging */ import React, { useState} from 'react'; import type { ToolActivity } from './useWorkspace'; import { useLanguage } from '../../../providers/language/LanguageContext'; interface ToolActivityLogProps { activities: ToolActivity[]; } const _UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const _HIDDEN_ARG_KEYS = new Set([ 'mandateId', 'userId', 'featureInstanceId', 'workflowId', 'sessionId', ]); function _isInternalValue(v: unknown): boolean { if (typeof v !== 'string') return false; if (_UUID_RE.test(v)) return true; if (v.length > 60 && !v.includes(' ')) return true; return false; } function _formatArgs(args: Record, isError: boolean): string { const parts: string[] = []; for (const [k, v] of Object.entries(args)) { if (!isError && _HIDDEN_ARG_KEYS.has(k)) continue; if (!isError && _isInternalValue(v)) continue; let display: string; if (typeof v === 'string') { display = v.length > 80 ? v.slice(0, 77) + '...' : v; } else if (typeof v === 'number' || typeof v === 'boolean') { display = String(v); } else if (v === null || v === undefined) { continue; } else { const json = JSON.stringify(v); display = json.length > 80 ? json.slice(0, 77) + '...' : json; } parts.push(`${k}: ${display}`); } return parts.join(', ') || (isError ? JSON.stringify(args) : ''); } export const ToolActivityLog: React.FC = ({ activities }) => { const { t } = useLanguage(); const [expandedId, setExpandedId] = useState(null); const _formatResult = (result: string): string => { if (!result) return ''; const trimmed = result.trim(); if (trimmed.startsWith('{') || trimmed.startsWith('[')) { try { const parsed = JSON.parse(trimmed); if (Array.isArray(parsed)) { return t('{count} Ergebnisse', { count: parsed.length }); } if (typeof parsed === 'object' && parsed !== null) { const keys = Object.keys(parsed); if (parsed.count !== undefined) return t('{count} Einträge', { count: parsed.count }); if (parsed.total !== undefined) return t('{count} Einträge', { count: parsed.total }); if (parsed.rows && Array.isArray(parsed.rows)) { return t('{count} Zeilen', { count: parsed.rows.length }); } if (parsed.data && Array.isArray(parsed.data)) { return t('{count} Einträge', { count: parsed.data.length }); } if (parsed.result !== undefined) { const r = String(parsed.result); return r.length > 120 ? r.slice(0, 117) + '...' : r; } if (keys.length <= 3) { return keys.map((k) => `${k}: ${String(parsed[k]).slice(0, 40)}`).join(', '); } return t('Objekt ({count} Felder)', { count: keys.length }); } } catch { // not valid JSON } } return trimmed.length > 150 ? trimmed.slice(0, 147) + '...' : trimmed; }; const _getToolLabel = (toolName: string): string => { switch (toolName) { case 'addNode': return t('Knoten hinzufügen'); case 'aggregateTable': return t('Tabelle aggregieren'); case 'browseContainer': return t('Datei durchsuchen'); case 'browseDataSource': return t('Datenquelle durchsuchen'); case 'browseTable': return t('Tabelle durchsuchen'); case 'clickup_createTask': return t('ClickUp-Aufgabe erstellen'); case 'clickup_searchTasks': return t('ClickUp-Aufgaben suchen'); case 'clickup_updateTask': return t('ClickUp-Aufgabe aktualisieren'); case 'connectNodes': return t('Knoten verbinden'); case 'copyFile': return t('Datei kopieren'); case 'createChart': return t('Diagramm erstellen'); case 'createGroup': return t('Gruppe anlegen'); case 'createFolder': return t('Ordner anlegen'); case 'createRecord': return t('Datensatz erstellen'); case 'deleteFile': return t('Datei löschen'); case 'deleteGroup': return t('Gruppe löschen'); case 'deleteFolder': return t('Ordner löschen'); case 'deleteRecord': return t('Datensatz löschen'); case 'describeImage': return t('Bild beschreiben'); case 'detectLanguage': return t('Sprache erkennen'); case 'downloadFromDataSource': return t('Aus Datenquelle laden'); case 'executeCode': return t('Code ausführen'); case 'extractContainerItem': return t('Element extrahieren'); case 'generateImage': return t('Bild erzeugen'); case 'getFileInfo': return t('Datei-Info abrufen'); case 'getTableSchema': return t('Tabellenschema abrufen'); case 'jira_connect': return t('Jira verbinden'); case 'jira_exportTickets': return t('Jira-Tickets exportieren'); case 'jira_importTickets': return t('Jira-Tickets importieren'); case 'listAvailableNodeTypes': return t('Verfügbare Knotentypen auflisten'); case 'listConnections': return t('Verbindungen auflisten'); case 'listFiles': return t('Dateien auflisten'); case 'listGroups': return t('Gruppen auflisten'); case 'listItemsInGroup': return t('Gruppeninhalt auflisten'); case 'addItemsToGroup': return t('Zu Gruppe hinzufügen'); case 'moveItemsBetweenGroups': return t('Zwischen Gruppen verschieben'); case 'ensureInstanceGroup': return t('Instanzgruppe sicherstellen'); case 'ensureTempGroup': return t('Temp-Gruppe sicherstellen'); case 'listFolders': return t('Ordner auflisten'); case 'listTables': return t('Tabellen auflisten'); case 'listWorkflowHistory': return t('Workflow-Verlauf'); case 'moveFile': return t('Datei verschieben'); case 'moveGroup': return t('Gruppe verschieben'); case 'renameGroup': return t('Gruppe umbenennen'); case 'moveFolder': return t('Ordner verschieben'); case 'neutralizeData': return t('Daten neutralisieren'); case 'outlook_composeAndDraftReply': return t('Outlook-Antwort entwerfen'); case 'outlook_readEmails': return t('Outlook-E-Mails lesen'); case 'outlook_searchEmails': return t('Outlook durchsuchen'); case 'outlook_sendDraft': return t('Outlook-Entwurf senden'); case 'queryFeatureInstance': return t('Feature abfragen'); case 'queryTable': return t('Tabelle abfragen'); case 'readContentObjects': return t('Inhalte lesen'); case 'readFile': return t('Datei lesen'); case 'readUrl': return t('URL lesen'); case 'readWorkflowGraph': return t('Workflow-Graph lesen'); case 'readWorkflowMessages': return t('Workflow-Nachrichten lesen'); case 'removeNode': return t('Knoten entfernen'); case 'renderDocument': return t('Dokument rendern'); case 'replaceInFile': return t('Text in Datei ersetzen'); case 'requestToolbox': return t('Toolbox anfordern'); case 'renameFile': return t('Datei umbenennen'); case 'renameFolder': return t('Ordner umbenennen'); case 'searchDataSource': return t('In Datenquelle suchen'); case 'searchDocuments': return t('Dokumente suchen'); case 'searchInFileContent': return t('In Datei suchen'); case 'sendMail': return t('E-Mail senden'); case 'setNodeParameter': return t('Knotenparameter setzen'); case 'sharepoint_findDocuments': return t('SharePoint-Dokumente finden'); case 'sharepoint_readDocuments': return t('SharePoint-Dokumente lesen'); case 'sharepoint_upload': return t('SharePoint-Upload'); case 'speechToText': return t('Sprache zu Text'); case 'summarizeContent': return t('Inhalt zusammenfassen'); case 'tagFile': return t('Datei taggen'); case 'textToSpeech': return t('Text zu Sprache'); case 'translateText': return t('Text übersetzen'); case 'trustee_refreshAccountingData': return t('Treuhand-Buchhaltung aktualisieren'); case 'uploadToExternal': return t('Extern hochladen'); case 'validateGraph': return t('Graph validieren'); case 'webSearch': return t('Websuche'); case 'writeFile': return t('Datei schreiben'); default: return toolName; } }; const _getStatusLabel = (status: string): string => { switch (status) { case 'calling': return t('läuft'); case 'success': return t('OK'); case 'error': return t('Fehler'); default: return status; } }; if (!activities.length) { return (
{t('Noch keine Aktivität')}
); } return (
{activities.map(activity => { const isError = activity.status === 'error'; const isExpanded = expandedId === activity.id; const friendlyName = _getToolLabel(activity.toolName); const argsText = activity.args && Object.keys(activity.args).length > 0 ? _formatArgs(activity.args, isError) : ''; const resultText = activity.result ? _formatResult(activity.result) : ''; return (
setExpandedId(isExpanded ? null : activity.id)} >
{friendlyName} {friendlyName !== activity.toolName && ( {activity.toolName} )} {_getStatusLabel(activity.status)}
{argsText && (
{argsText}
)} {resultText && !isError && (
{resultText}
)} {activity.error && (
{activity.error}
)} {isExpanded && activity.args && Object.keys(activity.args).length > 0 && (
{t('Alle Parameter')}
                  {JSON.stringify(activity.args, null, 2)}
                
)} {isExpanded && activity.result && (
{t('Vollständiges Ergebnis')}
                  {activity.result}
                
)}
); })}
); };