From ea322aef5ce2da08dbd65637e9b371b19cc0ebc0 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Fri, 3 Apr 2026 15:28:27 +0200 Subject: [PATCH] improve report modal: cumulative file picker for agent logs and screenshots Browse button now adds files incrementally instead of replacing the previous selection. Each file shows name, size, and an x-button to remove it. Duplicate files (same name+size) are silently skipped. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/ReportSessionModal.tsx | 110 +++++++++++++++--- 1 file changed, 96 insertions(+), 14 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/ReportSessionModal.tsx b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/ReportSessionModal.tsx index 8d54b68d..17db1ee9 100644 --- a/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/ReportSessionModal.tsx +++ b/src/Web/autopilot-monitor-web/app/sessions/[sessionId]/components/ReportSessionModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { zipSync } from "fflate"; import { Session, EnrollmentEvent, RuleResult } from "@/types"; @@ -20,6 +20,12 @@ interface ReportSessionModalProps { submitting: boolean; } +function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +} + export default function ReportSessionModal({ show, session, events, analysisResults, onSubmit, onCancel, submitting }: ReportSessionModalProps) { @@ -31,6 +37,9 @@ export default function ReportSessionModal({ const [submitResult, setSubmitResult] = useState<'success' | 'error' | null>(null); const [submitErrorMessage, setSubmitErrorMessage] = useState(null); + const agentLogInputRef = useRef(null); + const screenshotInputRef = useRef(null); + useEffect(() => { if (show) { setSubmitResult(null); @@ -40,15 +49,42 @@ export default function ReportSessionModal({ if (!show || !session) return null; - const handleAgentLogChange = (files: File[]) => { + const addAgentLogs = (newFiles: File[]) => { setAgentLogError(null); - const totalSize = files.reduce((sum, f) => sum + f.size, 0); + const merged = [...agentLogFiles]; + for (const f of newFiles) { + if (!merged.some(existing => existing.name === f.name && existing.size === f.size)) { + merged.push(f); + } + } + const totalSize = merged.reduce((sum, f) => sum + f.size, 0); if (totalSize > MAX_AGENT_LOG_SIZE) { - setAgentLogError(`Files too large (${(totalSize / 1024 / 1024).toFixed(1)} MB total). Maximum size is 5 MB.`); - setAgentLogFiles([]); + setAgentLogError(`Total size (${formatFileSize(totalSize)}) exceeds 5 MB limit. Remove some files or add smaller ones.`); + // Keep existing files, don't add the new ones return; } - setAgentLogFiles(files); + setAgentLogFiles(merged); + if (agentLogInputRef.current) agentLogInputRef.current.value = ""; + }; + + const removeAgentLog = (index: number) => { + setAgentLogError(null); + setAgentLogFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const addScreenshots = (newFiles: File[]) => { + const merged = [...screenshotFiles]; + for (const f of newFiles) { + if (!merged.some(existing => existing.name === f.name && existing.size === f.size)) { + merged.push(f); + } + } + setScreenshotFiles(merged); + if (screenshotInputRef.current) screenshotInputRef.current.value = ""; + }; + + const removeScreenshot = (index: number) => { + setScreenshotFiles(prev => prev.filter((_, i) => i !== index)); }; const fileToBase64 = async (file: File): Promise => { @@ -68,7 +104,6 @@ export default function ReportSessionModal({ screenshotBase64 = await fileToBase64(screenshotFiles[0]); screenshotFileName = screenshotFiles[0].name; } else if (screenshotFiles.length > 1) { - // Zip multiple screenshots client-side const entries: Record = {}; for (const file of screenshotFiles) { const buffer = await file.arrayBuffer(); @@ -116,6 +151,8 @@ export default function ReportSessionModal({ onCancel(); }; + const agentLogTotalSize = agentLogFiles.reduce((sum, f) => sum + f.size, 0); + return (
e.stopPropagation()}> @@ -205,22 +242,46 @@ export default function ReportSessionModal({

- {/* Agent Log File */} + {/* Agent Log Files */}
handleAgentLogChange(e.target.files ? Array.from(e.target.files) : [])} + onChange={e => addAgentLogs(e.target.files ? Array.from(e.target.files) : [])} className="w-full text-sm text-gray-500 dark:text-gray-400 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 dark:file:bg-blue-900/40 dark:file:text-blue-300" disabled={submitting} /> {agentLogError && (

{agentLogError}

)} + {agentLogFiles.length > 0 && ( +
+ {agentLogFiles.map((file, i) => ( +
+ {file.name} ({formatFileSize(file.size)}) + +
+ ))} +

+ {agentLogFiles.length} file{agentLogFiles.length !== 1 ? "s" : ""} — {formatFileSize(agentLogTotalSize)} total +

+
+ )}

Located at %ProgramData%\AutopilotMonitor\Logs\ on the device.

@@ -232,16 +293,37 @@ export default function ReportSessionModal({ {/* Screenshots */}
setScreenshotFiles(e.target.files ? Array.from(e.target.files) : [])} + onChange={e => addScreenshots(e.target.files ? Array.from(e.target.files) : [])} className="w-full text-sm text-gray-500 dark:text-gray-400 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 dark:file:bg-blue-900/40 dark:file:text-blue-300" disabled={submitting} /> + {screenshotFiles.length > 0 && ( +
+ {screenshotFiles.map((file, i) => ( +
+ {file.name} ({formatFileSize(file.size)}) + +
+ ))} +
+ )}
{/* Data summary */} @@ -253,8 +335,8 @@ export default function ReportSessionModal({
  • {analysisResults.length} analysis result{analysisResults.length !== 1 ? "s" : ""}
  • Timeline export (TXT)
  • Table export (CSV)
  • - {agentLogFiles.length === 1 &&
  • Agent log: {agentLogFiles[0].name} ({(agentLogFiles[0].size / 1024).toFixed(0)} KB)
  • } - {agentLogFiles.length > 1 &&
  • {agentLogFiles.length} agent logs (will be zipped)
  • } + {agentLogFiles.length === 1 &&
  • Agent log: {agentLogFiles[0].name} ({formatFileSize(agentLogFiles[0].size)})
  • } + {agentLogFiles.length > 1 &&
  • {agentLogFiles.length} agent logs ({formatFileSize(agentLogTotalSize)}, will be zipped)
  • } {screenshotFiles.length === 1 &&
  • Screenshot: {screenshotFiles[0].name}
  • } {screenshotFiles.length > 1 &&
  • {screenshotFiles.length} screenshots (will be zipped)
  • }