diff --git a/src/langbot/pkg/api/http/controller/groups/monitoring.py b/src/langbot/pkg/api/http/controller/groups/monitoring.py index c8f7bc76..3b9f1e08 100644 --- a/src/langbot/pkg/api/http/controller/groups/monitoring.py +++ b/src/langbot/pkg/api/http/controller/groups/monitoring.py @@ -348,9 +348,22 @@ class MonitoringRouterGroup(group.RouterGroup): end_time=end_time, limit=limit, ) - headers = ['id', 'timestamp', 'bot_id', 'bot_name', 'pipeline_id', 'pipeline_name', - 'runner_name', 'message_content', 'message_text', 'session_id', 'status', 'level', - 'platform', 'user_id'] + headers = [ + 'id', + 'timestamp', + 'bot_id', + 'bot_name', + 'pipeline_id', + 'pipeline_name', + 'runner_name', + 'message_content', + 'message_text', + 'session_id', + 'status', + 'level', + 'platform', + 'user_id', + ] elif export_type == 'llm-calls': data = await self.ap.monitoring_service.export_llm_calls( bot_ids=bot_ids if bot_ids else None, @@ -359,18 +372,46 @@ class MonitoringRouterGroup(group.RouterGroup): end_time=end_time, limit=limit, ) - headers = ['id', 'timestamp', 'model_name', 'input_tokens', 'output_tokens', - 'total_tokens', 'duration_ms', 'cost', 'status', 'bot_id', 'bot_name', - 'pipeline_id', 'pipeline_name', 'session_id', 'message_id', 'error_message'] + headers = [ + 'id', + 'timestamp', + 'model_name', + 'input_tokens', + 'output_tokens', + 'total_tokens', + 'duration_ms', + 'cost', + 'status', + 'bot_id', + 'bot_name', + 'pipeline_id', + 'pipeline_name', + 'session_id', + 'message_id', + 'error_message', + ] elif export_type == 'embedding-calls': data = await self.ap.monitoring_service.export_embedding_calls( start_time=start_time, end_time=end_time, limit=limit, ) - headers = ['id', 'timestamp', 'model_name', 'prompt_tokens', 'total_tokens', - 'duration_ms', 'input_count', 'status', 'error_message', 'knowledge_base_id', - 'query_text', 'session_id', 'message_id', 'call_type'] + headers = [ + 'id', + 'timestamp', + 'model_name', + 'prompt_tokens', + 'total_tokens', + 'duration_ms', + 'input_count', + 'status', + 'error_message', + 'knowledge_base_id', + 'query_text', + 'session_id', + 'message_id', + 'call_type', + ] elif export_type == 'errors': data = await self.ap.monitoring_service.export_errors( bot_ids=bot_ids if bot_ids else None, @@ -379,8 +420,19 @@ class MonitoringRouterGroup(group.RouterGroup): end_time=end_time, limit=limit, ) - headers = ['id', 'timestamp', 'error_type', 'error_message', 'bot_id', 'bot_name', - 'pipeline_id', 'pipeline_name', 'session_id', 'message_id', 'stack_trace'] + headers = [ + 'id', + 'timestamp', + 'error_type', + 'error_message', + 'bot_id', + 'bot_name', + 'pipeline_id', + 'pipeline_name', + 'session_id', + 'message_id', + 'stack_trace', + ] elif export_type == 'sessions': data = await self.ap.monitoring_service.export_sessions( bot_ids=bot_ids if bot_ids else None, @@ -389,9 +441,19 @@ class MonitoringRouterGroup(group.RouterGroup): end_time=end_time, limit=limit, ) - headers = ['session_id', 'bot_id', 'bot_name', 'pipeline_id', 'pipeline_name', - 'message_count', 'start_time', 'last_activity', 'is_active', - 'platform', 'user_id'] + headers = [ + 'session_id', + 'bot_id', + 'bot_name', + 'pipeline_id', + 'pipeline_name', + 'message_count', + 'start_time', + 'last_activity', + 'is_active', + 'platform', + 'user_id', + ] else: return self.error(message=f'Invalid export type: {export_type}', code=400) @@ -417,6 +479,8 @@ class MonitoringRouterGroup(group.RouterGroup): # Return as file download response = await quart.make_response(csv_content) response.headers['Content-Type'] = 'text/csv; charset=utf-8' - response.headers['Content-Disposition'] = f'attachment; filename="monitoring-{export_type}-{int(datetime.datetime.now().timestamp())}.csv"' + response.headers['Content-Disposition'] = ( + f'attachment; filename="monitoring-{export_type}-{int(datetime.datetime.now().timestamp())}.csv"' + ) return response, 200 diff --git a/src/langbot/pkg/api/http/service/monitoring.py b/src/langbot/pkg/api/http/service/monitoring.py index b3f90f61..93ad981a 100644 --- a/src/langbot/pkg/api/http/service/monitoring.py +++ b/src/langbot/pkg/api/http/service/monitoring.py @@ -820,6 +820,7 @@ class MonitoringService: try: import json + message_chain = json.loads(message_content) if not isinstance(message_chain, list): return message_content @@ -909,7 +910,9 @@ class MonitoringService: 'pipeline_name': row[0].pipeline_name if isinstance(row, tuple) else row.pipeline_name, 'runner_name': row[0].runner_name if isinstance(row, tuple) else row.runner_name, 'message_content': row[0].message_content if isinstance(row, tuple) else row.message_content, - 'message_text': self._extract_message_text(row[0].message_content if isinstance(row, tuple) else row.message_content), + 'message_text': self._extract_message_text( + row[0].message_content if isinstance(row, tuple) else row.message_content + ), 'session_id': row[0].session_id if isinstance(row, tuple) else row.session_id, 'status': row[0].status if isinstance(row, tuple) else row.status, 'level': row[0].level if isinstance(row, tuple) else row.level, @@ -1108,7 +1111,9 @@ class MonitoringService: 'pipeline_name': row[0].pipeline_name if isinstance(row, tuple) else row.pipeline_name, 'message_count': row[0].message_count if isinstance(row, tuple) else row.message_count, 'start_time': self._format_timestamp(row[0].start_time if isinstance(row, tuple) else row.start_time), - 'last_activity': self._format_timestamp(row[0].last_activity if isinstance(row, tuple) else row.last_activity), + 'last_activity': self._format_timestamp( + row[0].last_activity if isinstance(row, tuple) else row.last_activity + ), 'is_active': str(row[0].is_active if isinstance(row, tuple) else row.is_active), 'platform': row[0].platform if isinstance(row, tuple) else row.platform, 'user_id': row[0].user_id if isinstance(row, tuple) else row.user_id, diff --git a/web/src/app/home/monitoring/components/ExportDropdown.tsx b/web/src/app/home/monitoring/components/ExportDropdown.tsx index d7e8adce..4efabd43 100644 --- a/web/src/app/home/monitoring/components/ExportDropdown.tsx +++ b/web/src/app/home/monitoring/components/ExportDropdown.tsx @@ -2,7 +2,14 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Download, FileText, Database, AlertCircle, Users, Layers } from 'lucide-react'; +import { + Download, + FileText, + Database, + AlertCircle, + Users, + Layers, +} from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, @@ -13,8 +20,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { backendClient } from '@/app/infra/http'; -import { FilterState, TimeRangeOption } from '../types/monitoring'; -import { DateRange, dateUtils } from '../utils/dateUtils'; +import { FilterState } from '../types/monitoring'; export type ExportType = | 'messages' @@ -104,7 +110,9 @@ export function ExportDropdown({ filterState }: ExportDropdownProps) { const contentDisposition = response.headers['content-disposition']; let filename = `monitoring-${type}-${Date.now()}.csv`; if (contentDisposition) { - const filenameMatch = contentDisposition.match(/filename="?([^";\n]+)"?/); + const filenameMatch = contentDisposition.match( + /filename="?([^";\n]+)"?/, + ); if (filenameMatch) { filename = filenameMatch[1]; } @@ -129,7 +137,11 @@ export function ExportDropdown({ filterState }: ExportDropdownProps) { } }; - const exportOptions: { type: ExportType; label: string; icon: React.ReactNode }[] = [ + const exportOptions: { + type: ExportType; + label: string; + icon: React.ReactNode; + }[] = [ { type: 'messages', label: t('monitoring.export.messages'),