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

+94 -16
View File
@@ -1,5 +1,71 @@
import { Scenario } from './types';
import { Type } from "@google/genai";
import { Scenario, UserSettings } from './types';
export interface VoiceProfile {
id: UserSettings['voiceName'];
label: string;
gender: 'Male' | 'Female' | 'Neutral';
style: string;
description: string;
sampleText: string;
openRouterVoice: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer';
webSpeechTuning: {
pitch: number;
rate: number;
};
}
export const VOICE_PROFILES: VoiceProfile[] = [
{
id: 'Fenrir',
label: 'Fenrir (Salt of the Earth)',
gender: 'Male',
style: 'Gruff / Earthy',
description: 'A gravelly, direct, salt-of-the-earth tone. Grounded and weathered, perfect for boundary testing and blue-collar roleplays.',
sampleText: "Listen here, friend. Life's messy and things break down, but we take it one honest step at a time.",
openRouterVoice: 'onyx',
webSpeechTuning: { pitch: 0.75, rate: 0.92 }
},
{
id: 'Zephyr',
label: 'Zephyr',
gender: 'Neutral',
style: 'Helpful',
description: 'A balanced, clear, and professional tone suitable for most general scenarios.',
sampleText: "Hello. I'm here to provide a clear, balanced perspective as we explore different ways of thinking.",
openRouterVoice: 'alloy',
webSpeechTuning: { pitch: 1.0, rate: 1.0 }
},
{
id: 'Puck',
label: 'Puck',
gender: 'Male',
style: 'Youthful',
description: 'Energetic and friendly, ideal for peer-to-peer or informal interactions.',
sampleText: "Hey there! Ready to jump into this scenario and see what kind of creative solutions we can uncover?",
openRouterVoice: 'fable',
webSpeechTuning: { pitch: 1.15, rate: 1.05 }
},
{
id: 'Kore',
label: 'Kore',
gender: 'Female',
style: 'Calm',
description: 'Soothing and professional, perfect for clinical or high-stress roleplay.',
sampleText: "Take a steady breath. Whatever you're carrying right now, we can slow down and examine it together.",
openRouterVoice: 'nova',
webSpeechTuning: { pitch: 1.1, rate: 0.95 }
},
{
id: 'Charon',
label: 'Charon',
gender: 'Male',
style: 'Authoritative',
description: 'Deep and commanding, useful for workplace or boundary-testing scenarios.',
sampleText: "Let us be direct. In demanding situations, clear boundaries and disciplined reasoning are essential.",
openRouterVoice: 'echo',
webSpeechTuning: { pitch: 0.85, rate: 0.95 }
}
];
export interface GlossaryEntry {
term: string;
@@ -99,6 +165,18 @@ export const CBT_GLOSSARY: GlossaryEntry[] = [
definition: "Believing you are responsible for events outside your control or that other people's behavior is a direct reaction to you.",
example: "My boss looks upset today; it must be because of that email I sent."
},
{
term: "Labeling",
category: "Distortion",
definition: "Assigning a harsh, global, and definitive negative label to yourself or others based on a single instance or error.",
example: "I made an error on the slide, so 'I am an idiot' rather than 'I made a mistake.'"
},
{
term: "Overgeneralization",
category: "Distortion",
definition: "Taking a single negative event and seeing it as a never-ending pattern of defeat by using words like 'always' or 'never'.",
example: "I didn't get this promotion, so I will never advance in my career."
},
{
term: "Grounding Exercises",
category: "Technique",
@@ -116,7 +194,7 @@ export const SCENARIOS: Scenario[] = [
description: "You sent a text to a close friend hours ago and haven't heard back. You're starting to imagine they're angry with you or that you've done something wrong.",
category: 'Relationships',
difficulty: 'Beginner',
systemInstruction: "You are Alex, a busy friend who just hasn't had a chance to reply yet. You are friendly and non-confrontational. If asked, you explain you were just preoccupied with chores.",
systemInstruction: "You are Alex, a busy friend who just hasn't had a chance to reply yet. You are friendly and non-confrontational. If asked, you explain you were just preoccupied with chores. INTERNAL CBT LOGIC: If the user tests for reassurance or asks if you're upset, warmly reassure them that everything is fine.",
initialMessage: "Hey! Sorry I'm just getting back to you, it's been a crazy day. What's up?",
cbtFocus: ["Mind Reading", "Fortune Telling"]
},
@@ -138,7 +216,7 @@ export const SCENARIOS: Scenario[] = [
description: "You're at a networking event and you just made a small slip-up in a conversation. You feel like everyone noticed and is judging you.",
category: 'Social Anxiety',
difficulty: 'Advanced',
systemInstruction: "You are David, an attendee at a networking event. You are polite and didn't even notice the user's minor slip-up. You are focused on professional exchange.",
systemInstruction: "You are David, an attendee at a networking event. You are polite and didn't even notice the user's minor slip-up. You are focused on professional exchange. INTERNAL CBT LOGIC: If the user draws excessive attention to their mistake or apologizes repeatedly, casually dismiss it and keep the conversation on the topic.",
initialMessage: "That's an interesting point about the industry trends. So, how did you get started in this field?",
cbtFocus: ["Spotlight Effect", "Emotional Reasoning"]
},
@@ -149,7 +227,7 @@ export const SCENARIOS: Scenario[] = [
description: "A minor mistake at home triggers a spiral of self-deprecating thoughts. Practice internal cognitive restructuring by talking back to your personified inner critic.",
category: 'Self-Talk',
difficulty: 'Beginner',
systemInstruction: "You are the user's personified Inner Critic. You are harsh, cynical, and use common distortions. Your goal is to represent the user's negative self-talk so they can practice refuting it.",
systemInstruction: "You are the user's personified Inner Critic. You are harsh, cynical, and use common distortions. Your goal is to represent the user's negative self-talk so they can practice refuting it. INTERNAL CBT LOGIC: If the user responds with calm, objective evidence-based reframing, grudgingly lose ground. If the user engages in emotional reasoning or agrees with your insults, double down on catastrophic conclusions.",
initialMessage: "I can't believe you forgot to buy milk again. You're completely disorganized and you'll never get your life together.",
cbtFocus: ["Labeling", "Overgeneralization"]
},
@@ -266,30 +344,30 @@ export const SCENARIOS: Scenario[] = [
];
export const CBT_ANALYSIS_SCHEMA = {
type: Type.OBJECT,
type: 'object',
properties: {
analysis: {
type: Type.OBJECT,
type: 'object',
properties: {
distortions: { type: Type.ARRAY, items: { type: Type.STRING } },
isAdaptive: { type: Type.BOOLEAN },
evidence: { type: Type.STRING },
distortions: { type: 'array', items: { type: 'string' } },
isAdaptive: { type: 'boolean' },
evidence: { type: 'string' },
},
required: ['distortions', 'isAdaptive', 'evidence']
},
rating: {
type: Type.OBJECT,
type: 'object',
properties: {
score: { type: Type.INTEGER },
reason: { type: Type.STRING },
score: { type: 'integer' },
reason: { type: 'string' },
},
required: ['score', 'reason']
},
suggestions: {
type: Type.OBJECT,
type: 'object',
properties: {
alternativeThought: { type: Type.STRING },
behavioralStep: { type: Type.STRING },
alternativeThought: { type: 'string' },
behavioralStep: { type: 'string' },
},
required: ['alternativeThought', 'behavioralStep']
}