- 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
133 lines
3.8 KiB
TypeScript
133 lines
3.8 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|