stemstemstem / FailureReportGenerator.tsx
Ryanrealaf's picture
Upload 4 files
54f863b verified
Raw
History Blame Contribute Delete
31.3 kB
import React, { useState, useEffect } from "react";
import {
AlertOctagon,
Terminal,
Clipboard,
Download,
Mail,
FileText,
Check,
Calendar,
Settings,
Layers,
Radio,
User,
Info,
ChevronDown,
ChevronUp,
FileCode,
ShieldAlert,
Archive,
RefreshCw
} from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
export type FailureCategory =
| "TRANSCRIPTION_TIMEOUT"
| "EMPTY_RESPONSE"
| "API_KEY_ERROR"
| "CLIENT_DSP_ERROR"
| "AUDIO_LENGTH_TOO_LONG"
| "DASHBOARD_RENDER_ERROR"
| "OTHER_SYSTEM_FAILURE";
export interface FailureReport {
id: string;
timestamp: string;
category: FailureCategory;
categoryLabel: string;
errorMessage: string;
logs: string[];
systemSpecs: {
browser: string;
os: string;
webAudioSupported: boolean;
localStorageSupported: boolean;
screenResolution: string;
timezone: string;
};
audioMetadata: {
fileName: string;
fileSize: string;
mimeType: string;
durationSeconds: string;
} | null;
dspSettings: {
preset: string;
blend: number;
margin: number;
onsetThreshold: number;
offsetThreshold: number;
frameThreshold: number;
bpm: number;
quantize: boolean;
quantizeGrid: string;
};
userComments: string;
expectedBehavior: string;
}
interface FailureReportGeneratorProps {
latestError: string;
transcriptionLogs: string[];
audioFile: File | null;
audioDuration: number;
selectedPreset: string;
blend: number;
margin: number;
onsetThreshold: number;
offsetThreshold: number;
frameThreshold: number;
bpm: number;
quantize: boolean;
quantizeGrid: string;
onClearError?: () => void;
}
export default function FailureReportGenerator({
latestError,
transcriptionLogs,
audioFile,
audioDuration,
selectedPreset,
blend,
margin,
onsetThreshold,
offsetThreshold,
frameThreshold,
bpm,
quantize,
quantizeGrid,
onClearError
}: FailureReportGeneratorProps) {
const [isOpen, setIsOpen] = useState(false);
const [category, setCategory] = useState<FailureCategory>("EMPTY_RESPONSE");
const [userComments, setUserComments] = useState("");
const [expectedBehavior, setExpectedBehavior] = useState("");
const [copied, setCopied] = useState(false);
const [reportsHistory, setReportsHistory] = useState<FailureReport[]>([]);
const [selectedFormat, setSelectedFormat] = useState<"markdown" | "json">("markdown");
const [activeTab, setActiveTab] = useState<"build" | "view" | "history">("build");
// Load local failure report history from browser session
useEffect(() => {
try {
const stored = sessionStorage.getItem("stemtomidi_failure_reports");
if (stored) {
setReportsHistory(JSON.parse(stored));
}
} catch (e) {
console.warn("Could not read reports history from session storage", e);
}
}, []);
// Proactively open when error is received
useEffect(() => {
if (latestError) {
setIsOpen(true);
// Deduce category from error string
if (latestError.toLowerCase().includes("api") || latestError.toLowerCase().includes("key")) {
setCategory("API_KEY_ERROR");
} else if (latestError.toLowerCase().includes("timeout") || latestError.toLowerCase().includes("504")) {
setCategory("TRANSCRIPTION_TIMEOUT");
} else if (latestError.toLowerCase().includes("resample") || latestError.toLowerCase().includes("decode")) {
setCategory("CLIENT_DSP_ERROR");
} else if (latestError.toLowerCase().includes("empty") || latestError.toLowerCase().includes("0 notes")) {
setCategory("EMPTY_RESPONSE");
} else {
setCategory("OTHER_SYSTEM_FAILURE");
}
}
}, [latestError]);
const CATEGORY_LABELS: Record<FailureCategory, string> = {
TRANSCRIPTION_TIMEOUT: "Transcriber Timeout / Server Refused (504)",
EMPTY_RESPONSE: "Zero Pitch Notes Detected (Empty Output)",
API_KEY_ERROR: "Gemini Key or Authentication Rejected",
CLIENT_DSP_ERROR: "Client-side Audio DSP / Resampling Failure",
AUDIO_LENGTH_TOO_LONG: "Audio Timeline Truncated (> 50MB)",
DASHBOARD_RENDER_ERROR: "Vite Client WebGL or Canvas Frame Glitch",
OTHER_SYSTEM_FAILURE: "Unspecified System Pipeline Exception"
};
const getSystemSpecs = () => {
const userAgent = navigator.userAgent;
let browser = "Unknown Browser";
if (userAgent.match(/chrome|chromium|crios/i)) browser = "Google Chrome";
else if (userAgent.match(/firefox|fxios/i)) browser = "Mozilla Firefox";
else if (userAgent.match(/safari/i)) browser = "Apple Safari";
else if (userAgent.match(/opr\//i)) browser = "Opera";
else if (userAgent.match(/edg/i)) browser = "Microsoft Edge";
let os = "Unknown Operating System";
if (userAgent.match(/windows/i)) os = "Windows OS";
else if (userAgent.match(/macintosh|mac os/i)) os = "macOS";
else if (userAgent.match(/linux/i)) os = "Linux OS";
else if (userAgent.match(/android/i)) os = "Android OS";
else if (userAgent.match(/iphone|ipad/i)) os = "iOS";
return {
browser,
os,
webAudioSupported: typeof (window.AudioContext || (window as any).webkitAudioContext) !== "undefined",
localStorageSupported: typeof window.localStorage !== "undefined",
screenResolution: `${window.screen.width}x${window.screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"
};
};
const currentReportObj = (): FailureReport => {
return {
id: `REP-${Math.floor(1000 + Math.random() * 9000)}`,
timestamp: new Date().toISOString(),
category,
categoryLabel: CATEGORY_LABELS[category],
errorMessage: latestError || "None (Manual Diagnostic Report)",
logs: transcriptionLogs.length > 0 ? transcriptionLogs : ["No log files registered in this cycle."],
systemSpecs: getSystemSpecs(),
audioMetadata: audioFile ? {
fileName: audioFile.name,
fileSize: `${(audioFile.size / (1024 * 1024)).toFixed(2)} MB (${audioFile.size} bytes)`,
mimeType: audioFile.type || "audio/octet-stream",
durationSeconds: audioDuration > 0 ? `${audioDuration.toFixed(2)}s` : "Unknown"
} : null,
dspSettings: {
preset: selectedPreset,
blend,
margin,
onsetThreshold,
offsetThreshold,
frameThreshold,
bpm,
quantize,
quantizeGrid
},
userComments: userComments.trim() || "No customized provider notes entered.",
expectedBehavior: expectedBehavior.trim() || "No expected behavior specified."
};
};
const generateMarkdownReport = (report: FailureReport) => {
return `# STEMTOMIDI DIAGNOSTIC FAILURE REPORT
Generated: ${report.timestamp}
Report ID: ${report.id}
====================================================================
## 1. FATAL INCIDENT CLASSIFICATION
* **Category:** ${report.categoryLabel}
* **Event Severity:** HIGH - Pipeline Interrupted
* **Diagnostic Exception Code:** [${report.category}]
* **Engine Statement:** "${report.errorMessage}"
## 2. ACTIVE DIGITAL SIGNAL PROCESSING (DSP) STACK
* **Preset Configuration Template:** "${report.dspSettings.preset}"
* **Separation Filter Blend Factor:** ${report.dspSettings.blend}
* **Wiener Mask Spectral Margin:** ${report.dspSettings.margin}
* **Pitch Onset Sensitivity Threshold:** ${report.dspSettings.onsetThreshold} (Upstream Target)
* **Pitch Offset Exit Threshold:** ${report.dspSettings.offsetThreshold}
* **Frame Amplitude Salience Floor:** ${report.dspSettings.frameThreshold}
* **BPM Counter Estimate:** ${report.dspSettings.bpm} BPM
* **Grid Quantize Alignment Enabled:** ${report.dspSettings.quantize ? "YES" : "NO"}
* **Quantize Metric Grid Range:** "${report.dspSettings.quantizeGrid}"
## 3. FILE PROPERTY METADATA
${report.audioMetadata ? `* **Filename:** ${report.audioMetadata.fileName}
* **Payload Absolute Size:** ${report.audioMetadata.fileSize}
* **Encoded Mime-Type Flag:** ${report.audioMetadata.mimeType}
* **Audio Track Duration Timeline:** ${report.audioMetadata.durationSeconds}` : "* **Audio Track File Status:** No active file loaded when diagnostics were triggered."}
## 4. BROWSER ENVIRONMENT CONTEXT
* **Software Client:** ${report.systemSpecs.browser}
* **Base Platform Kernel:** ${report.systemSpecs.os}
* **HTML5 Web Audio API Core Enabled:** ${report.systemSpecs.webAudioSupported ? "YES" : "NO"}
* **Local State Keystore Engine Available:** ${report.systemSpecs.localStorageSupported ? "YES" : "NO"}
* **Display Output Standard:** ${report.systemSpecs.screenResolution}
* **Location Timezone Offset:** ${report.systemSpecs.timezone}
## 5. PRACTITIONER DIAGNOSTIC NOTES
* **Incident Description Memo:**
> ${report.userComments.replace(/\n/g, "\n > ")}
* **Expected Audio Output Desired:**
> ${report.expectedBehavior.replace(/\n/g, "\n > ")}
## 6. COMPLETE TRANSCRIPTION CONSOLE LOGS
\`\`\`
${report.logs.join("\n")}
\`\`\`
====================================================================
StemToMIDI Diagnostics Subsystem - End of Registry Pack.
`;
};
const getFormattedReportString = () => {
const report = currentReportObj();
if (selectedFormat === "json") {
return JSON.stringify(report, null, 2);
}
return generateMarkdownReport(report);
};
const handleCopy = () => {
navigator.clipboard.writeText(getFormattedReportString());
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const downloadReportFile = () => {
const report = currentReportObj();
const content = getFormattedReportString();
const extension = selectedFormat === "json" ? "json" : "md";
const mime = selectedFormat === "json" ? "application/json" : "text/markdown";
const blob = new Blob([content], { type: mime });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `StemToMIDI_FailureReport_${report.id.toLowerCase()}.${extension}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const handleSaveReport = () => {
const newReport = currentReportObj();
const updatedHistory = [newReport, ...reportsHistory].slice(0, 15); // limit to 15 records
setReportsHistory(updatedHistory);
sessionStorage.setItem("stemtomidi_failure_reports", JSON.stringify(updatedHistory));
// Switch to history view
setActiveTab("history");
};
const clearReportHistory = () => {
setReportsHistory([]);
sessionStorage.removeItem("stemtomidi_failure_reports");
};
const triggerEmailReport = () => {
const report = currentReportObj();
const subject = encodeURIComponent(`[StemToMIDI Failure Report] ${report.categoryLabel} (${report.id})`);
// Shortened body to avoid mailto browser character limits
const briefBody = `Hi Support,
I encountered an error while transcribing audio in StemToMIDI.
--- INCIDENT SUMMARY ---
Report ID: ${report.id}
Time: ${report.timestamp}
Error: ${report.errorMessage}
Category: ${report.categoryLabel}
File Name: ${report.audioMetadata?.fileName || "None"}
Browser OS: ${report.systemSpecs.browser} / ${report.systemSpecs.os}
Custom Comments:
${report.userComments}
Expected Behavior:
${report.expectedBehavior}
-------------------------
Please find the complete markdown diagnostic report and log registry below (please copy and paste the report if needed):
`;
const mailto = `mailto:purarecoveryryan@gmail.com?subject=${subject}&body=${encodeURIComponent(briefBody)}`;
window.open(mailto, "_blank");
};
const report = currentReportObj();
return (
<div className="bg-slate-900 border border-slate-800/80 rounded-2xl p-5 shadow-2xl relative overflow-hidden">
{/* Decorative Warning glow background */}
<div className="absolute -top-12 -right-12 w-24 h-24 bg-red-500/10 rounded-full blur-2xl pointer-events-none" />
{/* Accordion header bar */}
<div
onClick={() => setIsOpen(!isOpen)}
className="flex items-center justify-between cursor-pointer select-none pb-1"
>
<div className="flex items-center gap-3">
<div className="p-2.5 bg-amber-500/10 boarder border-amber-500/20 text-amber-400 rounded-xl">
<ShieldAlert className="w-5 h-5" />
</div>
<div>
<h3 className="text-sm font-bold tracking-wider text-slate-200 uppercase font-sans">
DIAGNOSTIC REPORT FRAMEWORK
</h3>
<p className="text-xs text-slate-400 mt-0.5">
Analyze, bundle, compile, and download diagnostic system reports when transcribing failures occur.
</p>
</div>
</div>
<button className="p-2 text-slate-400 hover:text-slate-200 bg-slate-950/60 rounded-lg hover:bg-slate-950 transition-all border border-slate-800/80">
{isOpen ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</button>
</div>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
className="overflow-hidden"
>
<div className="pt-5 mt-4 border-t border-slate-800/70 space-y-5">
{/* Tab selector menu */}
<div className="flex border-b border-slate-800/80 pb-px gap-2">
<button
onClick={() => setActiveTab("build")}
className={`px-4 py-2 text-xs font-mono font-bold tracking-wider uppercase transition-all border-b-2 -mb-px ${
activeTab === "build"
? "border-amber-500 text-amber-400"
: "border-transparent text-slate-400 hover:text-slate-100"
}`}
>
1. Customize Report Metadata
</button>
<button
onClick={() => setActiveTab("view")}
className={`px-4 py-2 text-xs font-mono font-bold tracking-wider uppercase transition-all border-b-2 -mb-px ${
activeTab === "view"
? "border-amber-500 text-amber-400"
: "border-transparent text-slate-400 hover:text-slate-100"
}`}
>
2. Compiled Preview
</button>
<button
onClick={() => setActiveTab("history")}
className={`px-4 py-2 text-xs font-mono font-bold tracking-wider uppercase transition-all border-b-2 -mb-px ${
activeTab === "history"
? "border-amber-500 text-amber-400"
: "border-transparent text-slate-400 hover:text-slate-100"
}`}
>
3. Session Registry ({reportsHistory.length})
</button>
</div>
{/* TAB 1: BUILD REPORT WITH METADATA */}
{activeTab === "build" && (
<div className="grid grid-cols-1 lg:grid-cols-12 gap-5">
{/* Left Parameter Inputs Column */}
<div className="lg:col-span-8 space-y-4">
{/* Failure Category */}
<div className="space-y-1.5">
<label className="text-xs font-mono uppercase text-zinc-400 font-bold flex items-center gap-1.5">
<AlertOctagon className="w-3.5 h-3.5 text-red-400" />
Classify Failure Category
</label>
<select
value={category}
onChange={(e) => setCategory(e.target.value as FailureCategory)}
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-xs text-slate-300 focus:outline-none focus:border-amber-500 transition-colors"
>
{Object.entries(CATEGORY_LABELS).map(([key, label]) => (
<option key={key} value={key}>{label}</option>
))}
</select>
</div>
{/* Incidents notes */}
<div className="space-y-1.5">
<label className="text-xs font-mono uppercase text-zinc-400 font-bold flex items-center gap-1.5">
<User className="w-3.5 h-3.5 text-amber-400" />
Diagnostic Case Notes (Optional Custom Context)
</label>
<textarea
value={userComments}
onChange={(e) => setUserComments(e.target.value)}
placeholder="E.g., Audio track contains heavy high-frequency distortion. API returned bad status code."
rows={3}
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-xs text-slate-300 placeholder:text-slate-600 focus:outline-none focus:border-amber-500 transition-colors resize-none"
/>
</div>
{/* Expected notes */}
<div className="space-y-1.5">
<label className="text-xs font-mono uppercase text-zinc-400 font-bold flex items-center gap-1.5">
<FileText className="w-3.5 h-3.5 text-emerald-400" />
Expected Result vs Actual Behavior (Optional)
</label>
<textarea
value={expectedBehavior}
onChange={(e) => setExpectedBehavior(e.target.value)}
placeholder="E.g., I expected the vocal melody to map directly to lead piano roll notes between C4 and G5."
rows={3}
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-xs text-slate-300 placeholder:text-slate-600 focus:outline-none focus:border-amber-500 transition-colors resize-none"
/>
</div>
{/* Interactive workflow buttons */}
<div className="flex flex-wrap items-center gap-3 pt-2">
<button
onClick={handleSaveReport}
className="bg-amber-500/10 hover:bg-amber-500 hover:text-slate-950 border border-amber-500/35 hover:border-transparent font-bold text-amber-400 px-5 py-2.5 rounded-xl text-xs transition-all flex items-center gap-2 cursor-pointer"
>
<Archive className="w-4 h-4" />
Register & Save to History
</button>
<button
onClick={triggerEmailReport}
className="bg-slate-950 hover:bg-slate-900 border border-slate-800 text-slate-300 px-5 py-2.5 rounded-xl text-xs transition-all flex items-center gap-2 cursor-pointer"
>
<Mail className="w-4 h-4 text-emerald-400" />
E-mail Support Diagnostic Pack
</button>
<button
onClick={() => {
setUserComments("");
setExpectedBehavior("");
}}
className="text-xs text-slate-500 hover:text-slate-300 font-semibold uppercase tracking-wider px-3"
>
Reset Form
</button>
</div>
</div>
{/* Right Summary Info Column */}
<div className="lg:col-span-4 bg-slate-950 border border-slate-850 rounded-xl p-4 flex flex-col justify-between">
<div className="space-y-4">
<div className="flex items-center gap-2 pb-2.5 border-b border-slate-900">
<Settings className="w-4 h-4 text-slate-400 animate-spin" style={{ animationDuration: '6s' }} />
<span className="text-xs font-mono uppercase font-bold text-zinc-300">INCIDENT STATE SNAPSHOT</span>
</div>
<div className="space-y-2 font-mono text-[10px] text-slate-400">
<div className="flex justify-between py-1 border-b border-slate-900">
<span>Report ID Tag:</span>
<span className="text-slate-200 font-bold">{report.id}</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-900">
<span>Date/Time UTC:</span>
<span className="text-slate-200">{new Date(report.timestamp).toLocaleTimeString()}</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-900">
<span>File Assigned:</span>
<span className="text-slate-200 truncate max-w-[150px]" title={report.audioMetadata?.fileName || "None"}>
{report.audioMetadata?.fileName || "None"}
</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-900">
<span>Browser Software:</span>
<span className="text-slate-200">{report.systemSpecs.browser}</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-900">
<span>OS Platform:</span>
<span className="text-slate-200">{report.systemSpecs.os}</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-900">
<span>Active Preset Template:</span>
<span className="text-slate-200 capitalize">{report.dspSettings.preset}</span>
</div>
<div className="flex justify-between py-1">
<span>Active Log Count:</span>
<span className="text-slate-200 font-bold">{transcriptionLogs.length} entries</span>
</div>
</div>
</div>
<div className="bg-slate-900/60 border border-slate-850 rounded-lg p-3 text-[10px] text-zinc-500 leading-normal flex gap-2 items-start mt-4">
<Info className="w-4 h-4 text-amber-500 flex-shrink-0 mt-0.5" />
<span>
This telemetry package aggregates environmental variables securely. Standard system variables help isolate server latency, codec mismatches, or translation limits.
</span>
</div>
</div>
</div>
)}
{/* TAB 2: COMPILED PREVIEW */}
{activeTab === "view" && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xs text-slate-400 font-mono">Format selection:</span>
<button
onClick={() => setSelectedFormat("markdown")}
className={`px-3 py-1 text-[10px] font-mono font-bold tracking-wider rounded-lg border uppercase transition-colors cursor-pointer ${
selectedFormat === "markdown"
? "bg-amber-500/10 text-amber-400 border-amber-500/30"
: "bg-slate-950 border-slate-800 text-slate-400 hover:text-slate-200"
}`}
>
Markdown Output (.md)
</button>
<button
onClick={() => setSelectedFormat("json")}
className={`px-3 py-1 text-[10px] font-mono font-bold tracking-wider rounded-lg border uppercase transition-colors cursor-pointer ${
selectedFormat === "json"
? "bg-amber-500/10 text-amber-400 border-amber-500/30"
: "bg-slate-950 border-slate-800 text-slate-400 hover:text-slate-200"
}`}
>
Structured JSON format (.json)
</button>
</div>
<div className="flex items-center gap-1.5">
<button
onClick={handleCopy}
className="bg-slate-950 hover:bg-slate-900 border border-slate-800 text-slate-300 font-bold py-2 px-4 rounded-xl text-xs transition-colors cursor-pointer flex items-center gap-1.5"
>
{copied ? <Check className="w-4 h-4 text-emerald-400" /> : <Clipboard className="w-4 h-4" />}
{copied ? "Copied to Clipboard!" : "Copy Report"}
</button>
<button
onClick={downloadReportFile}
className="bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-bold py-2 px-4 rounded-xl text-xs transition-colors cursor-pointer flex items-center gap-1.5"
>
<Download className="w-4 h-4" />
Download Report
</button>
</div>
</div>
<div className="bg-slate-950 border border-slate-850 rounded-xl p-4 max-h-[350px] overflow-y-auto custom-scrollbar">
<pre className="font-mono text-[10px] text-zinc-300 leading-normal whitespace-pre-wrap select-all">
{getFormattedReportString()}
</pre>
</div>
</div>
)}
{/* TAB 3: REGISTERED REPORTS HISTORY */}
{activeTab === "history" && (
<div className="space-y-4">
<div className="flex items-center justify-between pb-2 border-b border-slate-800">
<span className="text-xs font-semibold text-slate-300 uppercase tracking-widest font-sans">
DURABLE REPORT REGISTER
</span>
{reportsHistory.length > 0 && (
<button
onClick={clearReportHistory}
className="text-[10px] font-mono text-red-400 hover:text-red-300 uppercase cursor-pointer"
>
Clear Session History
</button>
)}
</div>
{reportsHistory.length === 0 ? (
<div className="bg-slate-950/45 border border-slate-850 rounded-xl p-8 text-center space-y-2">
<FileCode className="w-8 h-8 text-slate-600 mx-auto" />
<h4 className="text-xs font-bold text-zinc-400 uppercase tracking-wider">No registered reports found</h4>
<p className="text-xs text-zinc-500 max-w-md mx-auto">
Reports you register during this browser session are serialized here. Generate and submit errors for quick diagnostic tracking.
</p>
</div>
) : (
<div className="space-y-3 max-h-[350px] overflow-y-auto custom-scrollbar">
{reportsHistory.map((rep) => (
<div
key={rep.id}
className="bg-slate-950/80 border border-slate-850 rounded-xl p-4 flex flex-col md:flex-row items-start md:items-center justify-between gap-4"
>
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="bg-amber-500/10 text-amber-400 border border-amber-500/20 font-mono text-[9px] uppercase font-bold px-1.5 py-0.5 rounded">
{rep.id}
</span>
<span className="text-xs font-bold text-slate-200">
{rep.categoryLabel}
</span>
</div>
<p className="text-[10px] text-zinc-500 pl-3">
Generated {new Date(rep.timestamp).toLocaleString()} &bull; File: {rep.audioMetadata?.fileName || "Manual Entry"}
</p>
<p className="text-xs text-slate-400 italic pr-4 pl-3 py-1 max-w-2xl truncate">
&ldquo;{rep.errorMessage}&rdquo;
</p>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<button
onClick={() => {
setCategory(rep.category);
setUserComments(rep.userComments);
setExpectedBehavior(rep.expectedBehavior);
setActiveTab("view");
}}
className="text-[10px] font-bold text-emerald-400 hover:text-emerald-300 font-mono uppercase bg-emerald-500/5 px-2.5 py-1.5 rounded-lg border border-emerald-500/20 cursor-pointer"
>
Load Case
</button>
<a
href={`mailto:purarecoveryryan@gmail.com?subject=StemToMIDI%20Diagnostic%20Registry%20%7B${rep.id}%7D&body=${encodeURIComponent(generateMarkdownReport(rep).substring(0, 800) + "\n...[truncated report text]")}`}
target="_blank"
rel="noreferrer"
className="text-[10px] font-bold text-slate-400 hover:text-slate-200 font-mono uppercase bg-slate-900 px-2.5 py-1.5 rounded-lg border border-slate-800 cursor-pointer"
>
Email
</a>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}