Spaces:
No application file
No application file
| 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()} • 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"> | |
| “{rep.errorMessage}” | |
| </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" | |
| > | |
| </a> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| ); | |
| } | |