feat(ui): redesign voice cockpit with clinical companion drawer, Svelte 5 migration, and Gemini 3.8 voice

- 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
This commit is contained in:
jpremill committed 2026-09-29 23:35:05 -04:00
1 parent c82b112624
commit 1feea40684
148 files changed
+11230 -1706

No files matched your search

+76 -13
View File
@@ -1,14 +1,42 @@
import React from 'react';
import { AnalysisResult } from '../types';
import { ClipboardCheck, X, CheckCircle2, AlertTriangle, Lightbulb, TrendingUp } from 'lucide-react';
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, onExit }) => {
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">
@@ -16,12 +44,36 @@ const SessionReport: React.FC<SessionReportProps> = ({ analysis, onExit }) => {
<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">Clinical Analysis Complete</span>
<span className="text-[10px] font-bold uppercase tracking-widest text-teal-100 opacity-80">
{scenarioTitle} • Clinical Evaluation Complete
</span>
</div>
</div>
<button onClick={onExit} className="bg-white/10 hover:bg-white/20 p-2 rounded-xl border border-white/10 transition-colors">
<X size={20} />
</button>
<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">
@@ -55,10 +107,21 @@ const SessionReport: React.FC<SessionReportProps> = ({ analysis, onExit }) => {
</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">
<div className="flex items-center gap-2 text-teal-600 mb-3">
<Lightbulb size={20} />
<span className="text-[10px] font-black uppercase tracking-widest">Master Reframe</span>
<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>