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:
1 parent
c82b112624
commit
1feea40684
148 files changed
+11230
-1706
No files matched your search
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Session Storage & Export Service
|
||||
*
|
||||
* Manages persistent storage of completed CBT roleplay sessions,
|
||||
* enabling history review, markdown report exports, and JSON backups.
|
||||
*/
|
||||
|
||||
import { SavedSession, Scenario, SessionMode, Message, AnalysisResult } from '../types';
|
||||
|
||||
const SESSIONS_STORAGE_KEY = 'cognitive-clarity-saved-sessions';
|
||||
|
||||
/**
|
||||
* Retrieves all saved sessions from LocalStorage.
|
||||
*/
|
||||
export function getSavedSessions(): SavedSession[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(SESSIONS_STORAGE_KEY);
|
||||
return raw ? JSON.parse(raw) : [];
|
||||
} catch (e) {
|
||||
console.error("Failed to load saved sessions", e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a completed session to LocalStorage.
|
||||
*/
|
||||
export function saveSession(
|
||||
scenario: Scenario,
|
||||
mode: SessionMode,
|
||||
messages: Message[],
|
||||
finalReport?: AnalysisResult
|
||||
): SavedSession {
|
||||
const newSession: SavedSession = {
|
||||
id: `session-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
|
||||
scenarioId: scenario.id,
|
||||
scenarioTitle: scenario.title,
|
||||
category: scenario.category,
|
||||
difficulty: scenario.difficulty,
|
||||
timestamp: new Date().toISOString(),
|
||||
mode,
|
||||
messages,
|
||||
finalReport
|
||||
};
|
||||
|
||||
try {
|
||||
const existing = getSavedSessions();
|
||||
const updated = [newSession, ...existing].slice(0, 50); // Keep last 50 sessions
|
||||
localStorage.setItem(SESSIONS_STORAGE_KEY, JSON.stringify(updated));
|
||||
} catch (e) {
|
||||
console.error("Failed to save session", e);
|
||||
}
|
||||
|
||||
return newSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a session by ID.
|
||||
*/
|
||||
export function deleteSession(sessionId: string): void {
|
||||
try {
|
||||
const existing = getSavedSessions();
|
||||
const filtered = existing.filter(s => s.id !== sessionId);
|
||||
localStorage.setItem(SESSIONS_STORAGE_KEY, JSON.stringify(filtered));
|
||||
} catch (e) {
|
||||
console.error("Failed to delete session", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an Apple HIG-styled Markdown summary of a CBT session report.
|
||||
*/
|
||||
export function formatSessionAsMarkdown(
|
||||
scenarioTitle: string,
|
||||
analysis: AnalysisResult,
|
||||
messages?: Message[]
|
||||
): string {
|
||||
const dateStr = new Date().toLocaleDateString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
let md = `# Cognitive Clarity — Clinical CBT Session Report\n\n`;
|
||||
md += `**Scenario:** ${scenarioTitle}\n`;
|
||||
md += `**Date:** ${dateStr}\n`;
|
||||
md += `**Clinical Evaluation:** ${analysis.analysis.isAdaptive ? '✅ Adaptive Growth Observed' : '⚠️ Clinical Patterns / Distortions Detected'}\n`;
|
||||
md += `**Mastery Rating:** ${analysis.rating.score} / 5\n\n`;
|
||||
md += `---\n\n`;
|
||||
|
||||
md += `## 1. Clinical Analysis\n\n`;
|
||||
if (analysis.analysis.distortions.length > 0) {
|
||||
md += `**Cognitive Distortions Identified:**\n`;
|
||||
analysis.analysis.distortions.forEach(d => {
|
||||
md += `- ${d}\n`;
|
||||
});
|
||||
md += `\n`;
|
||||
}
|
||||
md += `**Behavioral Evidence:**\n> "${analysis.analysis.evidence}"\n\n`;
|
||||
md += `**Clinical Justification:**\n${analysis.rating.reason}\n\n`;
|
||||
|
||||
md += `## 2. Cognitive Reframing & Tactical Steps\n\n`;
|
||||
md += `**Master Reframe:**\n> "${analysis.suggestions.alternativeThought}"\n\n`;
|
||||
md += `**Homework & Behavioral Step:**\n${analysis.suggestions.behavioralStep}\n\n`;
|
||||
|
||||
if (messages && messages.length > 0) {
|
||||
md += `---\n\n## 3. Session Transcript\n\n`;
|
||||
messages.forEach(m => {
|
||||
const sender = m.role === 'user' ? 'You' : 'AI Partner';
|
||||
md += `**[${sender}]**: ${m.text}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
return md;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a client-side file download of text content.
|
||||
*/
|
||||
export function downloadFile(content: string, fileName: string, contentType: string): void {
|
||||
const blob = new Blob([content], { type: contentType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
Reference in new issue
Block a user