Sets up the basic project configuration for Cognitive Clarity. This includes: - Initializing `package.json` with necessary dependencies (React, Gemini API, Lucide React) and build scripts for Vite. - Creating a basic `index.html` with Tailwind CSS and Google Fonts integration. - Setting up `index.tsx` as the React application entry point. - Configuring `tsconfig.json` for TypeScript compilation with Vite. - Establishing `vite.config.ts` for development server setup and environment variable handling. - Adding a standard `.gitignore` file. - Updating `README.md` with local run instructions and AI Studio links. - Defining `metadata.json` for application details. - Creating core type definitions in `types.ts`. - Populating `constants.ts` with initial CBT glossary terms. - Adding utility functions for audio processing in `services/audioUtils.ts`.
232 lines
10 KiB
TypeScript
232 lines
10 KiB
TypeScript
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';
|
|
|
|
interface ScenarioGeneratorProps {
|
|
onGenerate: (scenario: Scenario) => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
const ScenarioGenerator: React.FC<ScenarioGeneratorProps> = ({ onGenerate, onCancel }) => {
|
|
const [prompt, setPrompt] = useState('');
|
|
const [selectedConcepts, setSelectedConcepts] = useState<string[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const toggleConcept = (term: string) => {
|
|
setSelectedConcepts(prev =>
|
|
prev.includes(term) ? prev.filter(t => t !== term) : [...prev, term]
|
|
);
|
|
};
|
|
|
|
const handleGenerate = async () => {
|
|
if (!prompt.trim() && selectedConcepts.length === 0) return;
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
|
|
|
|
const schema = {
|
|
type: Type.OBJECT,
|
|
properties: {
|
|
id: { type: Type.STRING },
|
|
title: { type: Type.STRING },
|
|
description: { type: Type.STRING },
|
|
category: {
|
|
type: Type.STRING,
|
|
enum: ['Social Anxiety', 'Workplace', 'Relationships', 'Self-Talk']
|
|
},
|
|
difficulty: {
|
|
type: Type.STRING,
|
|
enum: ['Beginner', 'Intermediate', 'Advanced']
|
|
},
|
|
systemInstruction: { type: Type.STRING },
|
|
initialMessage: { type: Type.STRING },
|
|
cbtFocus: {
|
|
type: Type.ARRAY,
|
|
items: { type: Type.STRING }
|
|
}
|
|
},
|
|
required: ['id', 'title', 'description', 'category', 'difficulty', 'systemInstruction', 'initialMessage', 'cbtFocus']
|
|
};
|
|
|
|
const selectedContext = selectedConcepts.length > 0
|
|
? `Focus specifically on these CBT concepts: ${selectedConcepts.join(', ')}.`
|
|
: '';
|
|
|
|
const result = await ai.models.generateContent({
|
|
model: 'gemini-3-flash-preview',
|
|
config: {
|
|
responseMimeType: 'application/json',
|
|
responseSchema: schema,
|
|
systemInstruction: `
|
|
You are a Clinical CBT Scenario Architect.
|
|
Your goal is to transform a user's prompt into a structured clinical simulation.
|
|
|
|
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:
|
|
- '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.
|
|
`
|
|
},
|
|
contents: [
|
|
{ role: 'user', parts: [{ text: `Generate a clinical scenario for: ${prompt || 'A scenario exploring the chosen CBT concepts.'}` }] }
|
|
]
|
|
});
|
|
|
|
if (result.text) {
|
|
const scenario = JSON.parse(result.text) as Scenario;
|
|
scenario.id = `custom-${Date.now()}-${scenario.id}`;
|
|
onGenerate(scenario);
|
|
} else {
|
|
throw new Error("No content generated");
|
|
}
|
|
|
|
} catch (err: any) {
|
|
console.error("Generation error:", err);
|
|
setError("Clinical scenario synthesis failed. Please refine your parameters.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const getConceptColor = (category: string, isSelected: boolean) => {
|
|
if (!isSelected) return 'bg-white border-slate-200 text-slate-500 hover:border-slate-300';
|
|
switch (category) {
|
|
case 'Distortion': return 'bg-rose-600 border-rose-600 text-white shadow-md shadow-rose-100';
|
|
case 'Technique': return 'bg-teal-600 border-teal-600 text-white shadow-md shadow-teal-100';
|
|
case 'Concept': return 'bg-amber-600 border-amber-600 text-white shadow-md shadow-amber-100';
|
|
default: return 'bg-slate-800 border-slate-800 text-white';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-md overflow-y-auto">
|
|
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-2xl overflow-hidden animate-in fade-in zoom-in duration-300 border border-slate-100 my-8">
|
|
{/* Header */}
|
|
<div className="p-6 bg-teal-600 text-white flex justify-between items-start relative overflow-hidden">
|
|
<div className="relative z-10">
|
|
<h2 className="text-2xl font-bold flex items-center gap-3">
|
|
<Sparkles size={28} className="text-teal-200" />
|
|
Scenario Synthesis
|
|
</h2>
|
|
<p className="text-teal-50 text-sm mt-1 font-light opacity-90">
|
|
Construct an evidence-based clinical environment.
|
|
</p>
|
|
</div>
|
|
<button onClick={onCancel} className="relative z-10 text-white/70 hover:text-white transition-colors bg-white/10 p-2 rounded-xl border border-white/10">
|
|
<X size={20} />
|
|
</button>
|
|
<div className="absolute top-0 right-0 w-32 h-32 bg-white/5 rounded-full -mr-10 -mt-10 blur-2xl"></div>
|
|
</div>
|
|
|
|
<div className="p-8 space-y-6 bg-[#FCFDFF]">
|
|
{/* Prompting Guide */}
|
|
<div className="bg-teal-50/50 border border-teal-100 rounded-xl p-4 space-y-3">
|
|
<div className="flex items-center gap-2 text-teal-700">
|
|
<Info size={16} />
|
|
<h3 className="text-xs font-bold uppercase tracking-wider">Clinical Blueprint</h3>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-1.5 text-[10px] font-bold text-teal-600 uppercase">
|
|
<UserCircle2 size={12} /> Character Role
|
|
</div>
|
|
<p className="text-[10px] text-slate-500 leading-tight">Define who the AI plays.</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-1.5 text-[10px] font-bold text-teal-600 uppercase">
|
|
<Target size={12} /> The Challenge
|
|
</div>
|
|
<p className="text-[10px] text-slate-500 leading-tight">Describe the social friction.</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-1.5 text-[10px] font-bold text-teal-600 uppercase">
|
|
<BrainCircuit size={12} /> CBT Focus
|
|
</div>
|
|
<p className="text-[10px] text-slate-500 leading-tight">Target specific distortions.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Clinical Targets (Selection) */}
|
|
<div className="space-y-3">
|
|
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest">
|
|
Clinical Targets (Optional)
|
|
</label>
|
|
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto p-1 scrollbar-hide">
|
|
{CBT_GLOSSARY.map(concept => {
|
|
const isSelected = selectedConcepts.includes(concept.term);
|
|
return (
|
|
<button
|
|
key={concept.term}
|
|
onClick={() => toggleConcept(concept.term)}
|
|
className={`px-3 py-1.5 rounded-lg text-[10px] font-bold border transition-all flex items-center gap-1.5 ${getConceptColor(concept.category, isSelected)}`}
|
|
>
|
|
{isSelected && <Check size={10} />}
|
|
{concept.term}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-3">
|
|
Simulation Context / Custom Instructions
|
|
</label>
|
|
<textarea
|
|
value={prompt}
|
|
onChange={(e) => setPrompt(e.target.value)}
|
|
placeholder="e.g. Practicing assertiveness with a pushy salesperson who won't take 'no' for an answer."
|
|
className="w-full h-28 p-5 border border-slate-200 rounded-2xl focus:ring-4 focus:ring-teal-500/10 focus:border-teal-500 focus:outline-none resize-none text-slate-800 placeholder:text-slate-400 text-sm leading-relaxed shadow-inner bg-white"
|
|
disabled={isLoading}
|
|
/>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="bg-rose-50 text-rose-600 p-4 rounded-xl text-xs flex items-center gap-3 border border-rose-100 font-medium">
|
|
<AlertCircle size={18} />
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex justify-end pt-2">
|
|
<button
|
|
onClick={handleGenerate}
|
|
disabled={(!prompt.trim() && selectedConcepts.length === 0) || isLoading}
|
|
className="bg-teal-600 text-white px-8 py-3.5 rounded-2xl font-bold hover:bg-teal-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 transition-all shadow-lg shadow-teal-100 text-sm uppercase tracking-widest"
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<Loader2 size={20} className="animate-spin" />
|
|
Synthesizing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Sparkles size={20} />
|
|
Initiate Build
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ScenarioGenerator; |