- Re-architect VoiceSession into full-bleed studio cockpit with overlay slide-over companion drawer - Eliminate width squishing defect on LiveInsightPanel and add native dark/light theme support - Integrate authentic Google Gemini 3.8 Flash TTS with Fenrir persona and fallback audio pipeline - Implement comprehensive Playwright E2E visual regression suite across Desktop, iPad, and iPhone (21 tests, 45 snapshots) - Migrate core application to Svelte 5 runes (, , ) with reactive stores - Maintain .NET 8 Azure Functions serverless endpoints and clean build hygiene
151 lines
7.0 KiB
TypeScript
151 lines
7.0 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { AnalysisResult, Message } from '../types';
|
|
import { ClipboardCheck, X, CheckCircle2, AlertTriangle, Lightbulb, TrendingUp, Download, Copy, Check, FileText } from 'lucide-react';
|
|
import { Button } from './ui/button';
|
|
import { formatSessionAsMarkdown, downloadFile } from '../services/sessionStorage';
|
|
|
|
interface SessionReportProps {
|
|
analysis: AnalysisResult;
|
|
scenarioTitle?: string;
|
|
messages?: Message[];
|
|
onExit: () => void;
|
|
}
|
|
|
|
const SessionReport: React.FC<SessionReportProps> = ({
|
|
analysis,
|
|
scenarioTitle = "CBT Roleplay Session",
|
|
messages = [],
|
|
onExit
|
|
}) => {
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
const handleCopyReframe = () => {
|
|
navigator.clipboard.writeText(analysis.suggestions.alternativeThought);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
};
|
|
|
|
const handleExportMarkdown = () => {
|
|
const md = formatSessionAsMarkdown(scenarioTitle, analysis, messages);
|
|
const fileName = `cbt-report-${scenarioTitle.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}.md`;
|
|
downloadFile(md, fileName, 'text/markdown');
|
|
};
|
|
|
|
const handleExportJSON = () => {
|
|
const json = JSON.stringify({ scenarioTitle, analysis, messages, exportedAt: new Date().toISOString() }, null, 2);
|
|
const fileName = `cbt-report-${scenarioTitle.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}.json`;
|
|
downloadFile(json, fileName, 'application/json');
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full bg-[#FDFDFF] rounded-3xl border border-slate-200 overflow-hidden shadow-2xl animate-in zoom-in-95 duration-500 w-full max-w-4xl mx-auto">
|
|
<div className="bg-teal-600 p-6 flex justify-between items-center text-white">
|
|
<div className="flex items-center gap-3">
|
|
<ClipboardCheck size={28} />
|
|
<div>
|
|
<h2 className="text-xl font-bold leading-none">Session Summary Report</h2>
|
|
<span className="text-[10px] font-bold uppercase tracking-widest text-teal-100 opacity-80">
|
|
{scenarioTitle} • Clinical Evaluation Complete
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleExportMarkdown}
|
|
className="text-white hover:bg-white/20 rounded-xl text-xs flex items-center gap-1.5"
|
|
title="Download Markdown Report"
|
|
>
|
|
<FileText size={16} />
|
|
<span className="hidden sm:inline">Export MD</span>
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleExportJSON}
|
|
className="text-white hover:bg-white/20 rounded-xl text-xs flex items-center gap-1.5"
|
|
title="Download JSON Report"
|
|
>
|
|
<Download size={16} />
|
|
<span className="hidden sm:inline">JSON</span>
|
|
</Button>
|
|
<button onClick={onExit} className="bg-white/10 hover:bg-white/20 p-2 rounded-xl border border-white/10 transition-colors ml-1">
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-grow overflow-y-auto p-8 flex flex-col gap-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className={`p-6 rounded-2xl border-l-8 ${analysis.analysis.isAdaptive ? 'bg-teal-50/50 border-teal-500' : 'bg-rose-50/50 border-rose-500'}`}>
|
|
<div className="flex items-center gap-2 mb-4">
|
|
{analysis.analysis.isAdaptive ? <CheckCircle2 className="text-teal-600" /> : <AlertTriangle className="text-rose-600" />}
|
|
<span className="font-black text-xs uppercase tracking-widest">{analysis.analysis.isAdaptive ? 'Adaptive Growth Observed' : 'Clinical Patterns Detected'}</span>
|
|
</div>
|
|
{analysis.analysis.distortions.length > 0 && (
|
|
<div className="flex flex-wrap gap-2 mb-4">
|
|
{analysis.analysis.distortions.map(d => (
|
|
<span key={d} className="bg-white border border-slate-200 px-3 py-1 rounded-lg text-[10px] font-bold text-slate-600 shadow-sm">{d}</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
<p className="text-sm leading-relaxed text-slate-700 font-medium italic">"{analysis.analysis.evidence}"</p>
|
|
</div>
|
|
|
|
<div className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm flex flex-col justify-center">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Mastery Level</span>
|
|
<div className="flex gap-1.5">
|
|
{[1,2,3,4,5].map(s => (
|
|
<div key={s} className={`h-2 w-8 rounded-full ${s <= analysis.rating.score ? 'bg-teal-500' : 'bg-slate-100'}`} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-slate-600 leading-relaxed font-light">{analysis.rating.reason}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="bg-white p-6 rounded-2xl border border-teal-100 shadow-sm relative group">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-2 text-teal-600">
|
|
<Lightbulb size={20} />
|
|
<span className="text-[10px] font-black uppercase tracking-widest">Master Reframe</span>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleCopyReframe}
|
|
className="h-7 px-2 text-[10px] text-slate-400 hover:text-teal-700 flex items-center gap-1"
|
|
>
|
|
{copied ? <Check size={12} className="text-teal-600" /> : <Copy size={12} />}
|
|
{copied ? 'Copied' : 'Copy'}
|
|
</Button>
|
|
</div>
|
|
<p className="text-base text-slate-800 italic font-medium leading-relaxed">"{analysis.suggestions.alternativeThought}"</p>
|
|
</div>
|
|
<div className="bg-white p-6 rounded-2xl border border-teal-100 shadow-sm">
|
|
<div className="flex items-center gap-2 text-teal-600 mb-3">
|
|
<TrendingUp size={20} />
|
|
<span className="text-[10px] font-black uppercase tracking-widest">Homework & Next Steps</span>
|
|
</div>
|
|
<p className="text-sm text-slate-700 leading-relaxed">{analysis.suggestions.behavioralStep}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-6 bg-slate-50 border-t border-slate-100 flex justify-center">
|
|
<button
|
|
onClick={onExit}
|
|
className="bg-teal-600 text-white px-12 py-3 rounded-xl font-bold hover:bg-teal-700 shadow-lg shadow-teal-100 transition-all uppercase tracking-widest text-xs"
|
|
>
|
|
Close Report & Return
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(SessionReport);
|