diff --git a/App.tsx b/App.tsx index eb4a26e..a8f4284 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { SCENARIOS } from './constants'; import { Scenario, SessionMode, UserSettings } from './types'; import ScenarioCard from './components/ScenarioCard'; @@ -17,26 +17,44 @@ const DEFAULT_SETTINGS: UserSettings = { genderPreference: 'Neutral' }; +const CUSTOM_SCENARIOS_KEY = 'cognitive-clarity-custom-scenarios'; +const SETTINGS_KEY = 'cognitive-clarity-settings'; + const App: React.FC = () => { const [activeScenario, setActiveScenario] = useState(null); const [sessionMode, setSessionMode] = useState('text'); const [selectedDifficulty, setSelectedDifficulty] = useState('All'); const [isPreparing, setIsPreparing] = useState(false); - // Custom Scenarios State - const [customScenarios, setCustomScenarios] = useState([]); + // Custom Scenarios State with LocalStorage persistence + const [customScenarios, setCustomScenarios] = useState(() => { + try { + const saved = localStorage.getItem(CUSTOM_SCENARIOS_KEY); + return saved ? JSON.parse(saved) : []; + } catch (e) { + console.error("Failed to load custom scenarios", e); + return []; + } + }); + const [showGenerator, setShowGenerator] = useState(false); const [showReference, setShowReference] = useState(false); const [showSettings, setShowSettings] = useState(false); // User Settings State const [userSettings, setUserSettings] = useState(() => { - const saved = localStorage.getItem('cognitive-clarity-settings'); + const saved = localStorage.getItem(SETTINGS_KEY); return saved ? JSON.parse(saved) : DEFAULT_SETTINGS; }); + // Save scenarios whenever they change useEffect(() => { - localStorage.setItem('cognitive-clarity-settings', JSON.stringify(userSettings)); + localStorage.setItem(CUSTOM_SCENARIOS_KEY, JSON.stringify(customScenarios)); + }, [customScenarios]); + + // Save settings whenever they change + useEffect(() => { + localStorage.setItem(SETTINGS_KEY, JSON.stringify(userSettings)); }, [userSettings]); const handleSelectScenario = (scenario: Scenario, mode: SessionMode) => { @@ -57,6 +75,10 @@ const App: React.FC = () => { setShowGenerator(false); }; + const handleDeleteScenario = useCallback((id: string) => { + setCustomScenarios(prev => prev.filter(s => s.id !== id)); + }, []); + const allScenarios = [...customScenarios, ...SCENARIOS]; const filteredScenarios = allScenarios.filter(scenario => selectedDifficulty === 'All' || scenario.difficulty === selectedDifficulty @@ -125,6 +147,7 @@ const App: React.FC = () => { scenario={activeScenario} onExit={handleExitSession} voiceSettings={userSettings} + onUpdateSettings={setUserSettings} /> )} @@ -233,7 +256,12 @@ const App: React.FC = () => {
{filteredScenarios.map(scenario => ( - + ))} + )) + )} +
+ + ); +}; + +export default AudioDeviceSelector; diff --git a/components/ScenarioCard.tsx b/components/ScenarioCard.tsx index 4a5f664..52058ff 100644 --- a/components/ScenarioCard.tsx +++ b/components/ScenarioCard.tsx @@ -1,15 +1,16 @@ import React from 'react'; import { Scenario } from '../types'; -import { Mic, MessageSquare, Info } from 'lucide-react'; +import { Mic, MessageSquare, Trash2 } from 'lucide-react'; import Tooltip from './Tooltip'; interface ScenarioCardProps { scenario: Scenario; onSelect: (scenario: Scenario, mode: 'text' | 'voice') => void; + onDelete?: (id: string) => void; } -const ScenarioCard: React.FC = ({ scenario, onSelect }) => { +const ScenarioCard: React.FC = ({ scenario, onSelect, onDelete }) => { const getDifficultyColor = (diff: string) => { switch (diff) { case 'Beginner': return 'bg-teal-50 text-teal-800 border-teal-100'; @@ -26,19 +27,34 @@ const ScenarioCard: React.FC = ({ scenario, onSelect }) => { }; return ( -
+
+ {onDelete && ( + + )} +
{scenario.difficulty} - - {scenario.category} - + {!onDelete && ( + + {scenario.category} + + )}
-

{scenario.title}

+

{scenario.title}

{scenario.description}

diff --git a/components/ScenarioGenerator.tsx b/components/ScenarioGenerator.tsx index 3778273..7888326 100644 --- a/components/ScenarioGenerator.tsx +++ b/components/ScenarioGenerator.tsx @@ -1,8 +1,9 @@ + import React, { useState } from 'react'; import { GoogleGenAI, Type } from "@google/genai"; import { Scenario } from '../types'; import { CBT_GLOSSARY } from '../constants'; -import { Sparkles, X, Loader2, AlertCircle, Info, Target, UserCircle2, BrainCircuit, Check } from 'lucide-react'; +import { Sparkles, X, Loader2, AlertCircle, Info, Target, UserCircle2, BrainCircuit, Check, BarChart } from 'lucide-react'; interface ScenarioGeneratorProps { onGenerate: (scenario: Scenario) => void; @@ -12,6 +13,7 @@ interface ScenarioGeneratorProps { const ScenarioGenerator: React.FC = ({ onGenerate, onCancel }) => { const [prompt, setPrompt] = useState(''); const [selectedConcepts, setSelectedConcepts] = useState([]); + const [selectedDifficulty, setSelectedDifficulty] = useState('Intermediate'); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -35,6 +37,7 @@ const ScenarioGenerator: React.FC = ({ onGenerate, onCan properties: { id: { type: Type.STRING }, title: { type: Type.STRING }, + characterName: { type: Type.STRING }, description: { type: Type.STRING }, category: { type: Type.STRING, @@ -51,7 +54,7 @@ const ScenarioGenerator: React.FC = ({ onGenerate, onCan items: { type: Type.STRING } } }, - required: ['id', 'title', 'description', 'category', 'difficulty', 'systemInstruction', 'initialMessage', 'cbtFocus'] + required: ['id', 'title', 'characterName', 'description', 'category', 'difficulty', 'systemInstruction', 'initialMessage', 'cbtFocus'] }; const selectedContext = selectedConcepts.length > 0 @@ -69,17 +72,18 @@ const ScenarioGenerator: React.FC = ({ onGenerate, onCan CORE INSTRUCTIONS: 1. CLINICAL FOCUS: ${selectedContext} - 2. CATEGORIZATION: You MUST assign the scenario to exactly one of the following categories based on its primary context: + 2. CATEGORIZATION: Based on the user's input, you MUST suggest the most appropriate category for the scenario: - 'Social Anxiety': Situations involving social performance, fear of judgment, or networking. - 'Workplace': Scenarios involving colleagues, supervisors, career growth, or professional conflict. - 'Relationships': Dynamics with family, friends, or romantic partners; boundary setting. - 'Self-Talk': Purely internal cognitive restructuring, rumination, or self-compassion work. - 3. DIFFICULTY CALIBRATION: Assign a level based on cognitive load: - - 'Beginner': Low stakes, high support, clear-cut distortions. - - 'Intermediate': Ambiguous stakes, moderate social friction, subtle distortions. - - 'Advanced': High emotional triggers, persistent clinical resistance from AI character, deep-seated patterns. - 4. INTERNAL LOGIC: The 'systemInstruction' must include a detailed "INTERNAL CBT LOGIC" section. This tells the AI how to react to the user's progress or cognitive lapses. - 5. CONSISTENCY: Ensure 'cbtFocus' includes the selected concepts. + 3. DIFFICULTY CALIBRATION: The user has explicitly requested a '${selectedDifficulty}' level. + Generate the scenario content and the AI character's behavior to match this complexity: + - 'Beginner': Low stakes, high support, clear-cut distortions, AI character is cooperative. + - 'Intermediate': Ambiguous stakes, moderate social friction, subtle distortions, AI character has specific needs/points. + - 'Advanced': High emotional triggers, persistent clinical resistance from AI character, complex deep-seated cognitive patterns. + 4. INTERNAL LOGIC: The 'systemInstruction' for the character MUST include a detailed "INTERNAL CBT LOGIC" section. This instructs the AI character how to react to the user's progress or cognitive lapses. + 5. CHARACTER VOICE: Define a realistic voice profile for the character, including their likely emotional state (frustrated, busy, anxious, etc.). ` }, contents: [ @@ -89,6 +93,8 @@ const ScenarioGenerator: React.FC = ({ onGenerate, onCan if (result.text) { const scenario = JSON.parse(result.text) as Scenario; + // Enforce the user-selected difficulty in the final object + scenario.difficulty = selectedDifficulty; scenario.id = `custom-${Date.now()}-${scenario.id}`; onGenerate(scenario); } else { @@ -162,6 +168,29 @@ const ScenarioGenerator: React.FC = ({ onGenerate, onCan
+ {/* Difficulty Selection */} +
+ +
+ {(['Beginner', 'Intermediate', 'Advanced'] as const).map((level) => ( + + ))} +
+
+ {/* Clinical Targets (Selection) */}