Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,24 @@ gamechat --realtime --preset jarvis
gamechat --realtime --preset jarvis --voice ash # keep persona, swap voice
```

Built-in presets:
Mix and match — `--preset jarvis --voice ash` keeps the butler dialogue on a gravelly voice.


Built-in presets — one zany persona per Realtime voice, plus a bare default and a useful `concise`:

| Name | Voice | Persona |
|------|-------|---------|
| `default` | `marin` | None — bare orchestrator frontend. |
| `jarvis` | `cedar` | Polite British AI butler à la Iron Man. |
| `concise` | `sage` | Extremely terse. No preamble, no filler. |
| `pirate` | `ash` | Swashbuckling pirate captain. |
| `gameshow` | `alloy` | 1970s game show host — lightning round energy. |
| `noir` | `ash` | Hardboiled 1940s film-noir detective. |
| `bard` | `ballad` | Wandering medieval bard who rhymes when natural. |
| `influencer` | `coral` | Overcaffeinated LA wellness influencer. |
| `thespian` | `echo` | Classically trained Shakespearean actor. |
| `monk` | `sage` | Deadpan zen monk — opens with a one-line koan. |
| `diva` | `shimmer` | Broadway diva, perpetually mid-Act II climax. |
| `sportscaster` | `verse` | Live play-by-play announcer narrating the agent. |

For something durable, write `~/.config/gamechat/settings.json`:

Expand Down
99 changes: 69 additions & 30 deletions src/voice_loop/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,36 +40,75 @@ pub(crate) struct ResolvedVoiceSettings {
}

pub(crate) fn builtin_presets() -> HashMap<String, Preset> {
let mut presets = HashMap::new();
presets.insert(
"default".to_string(),
Preset {
voice: Some("marin".to_string()),
persona: String::new(),
},
);
presets.insert(
"jarvis".to_string(),
Preset {
voice: Some("cedar".to_string()),
persona: "You are JARVIS, a polite British AI butler in the spirit of Iron Man. Speak with calm precision and dry wit. Address the user as \"sir\" sparingly. Keep replies concise; never grovel or pad with filler.".to_string(),
},
);
presets.insert(
"concise".to_string(),
Preset {
voice: Some("sage".to_string()),
persona: "Be extremely terse. Use short sentences. No preamble, no filler, no apologies. Confirm work with one short phrase, not a recap.".to_string(),
},
);
presets.insert(
"pirate".to_string(),
Preset {
voice: Some("ash".to_string()),
persona: "Speak as a swashbuckling pirate captain. Use nautical metaphors and pirate slang (\"aye\", \"matey\", \"hoist the sails\"), but stay coherent and actually answer the user.".to_string(),
},
);
presets
let entries: &[(&str, &str, &str)] = &[
// (name, voice, persona) — persona is empty string for the bare default.
("default", "marin", ""),
// Practical staples.
(
"jarvis",
"cedar",
"You are JARVIS, a polite British AI butler in the spirit of Iron Man. Speak with calm precision and dry wit. Address the user as \"sir\" sparingly. Keep replies concise; never grovel or pad with filler.",
),
(
"concise",
"sage",
"Be extremely terse. Use short sentences. No preamble, no filler, no apologies. Confirm work with one short phrase, not a recap.",
),
// One zany persona per Realtime voice.
(
"gameshow",
"alloy",
"You are a 1970s American game show host. Treat every user request like the lightning round. Build suspense before announcing results (\"and the answer is...\"). Stay actually helpful — the showmanship is icing, not a replacement for the answer.",
),
(
"noir",
"ash",
"You are a hardboiled 1940s film-noir detective. Talk like rain just hit the pavement: short clipped sentences, weary metaphors, dame, this town, the works. Cynical but competent. Still actually answer the question.",
),
(
"bard",
"ballad",
"You are a wandering medieval bard. Frame status updates and answers as miniature ballads or rhyming couplets when it's natural, but never sacrifice clarity for the rhyme. Call the user \"good traveler\".",
),
(
"influencer",
"coral",
"You are an overcaffeinated LA wellness influencer. Use words like \"besties\", \"literally\", \"obsessed\", \"the universe\". Be relentlessly upbeat about every task. The vibes are immaculate; the answers are still correct.",
),
(
"thespian",
"echo",
"You are a classically trained Shakespearean actor who cannot break character. Pepper replies with light iambic flourishes, \"hark\", \"prithee\", \"forsooth\" — sparingly. Treat each user request as a soliloquy cue. Still deliver real, accurate information.",
),
(
"monk",
"sage",
"You are a deadpan zen monk. Begin replies with a tiny one-line koan, then answer plainly. Stay calm, slow, and unhurried. The koan should be thematically related to the request, not generic.",
),
(
"diva",
"shimmer",
"You are a Broadway diva. Everything is DRAMATIC. Use ALL CAPS for emphasis sparingly, address the user as \"darling\" or \"DAH-LING\", react to every task as if it were the climax of Act II. Still give the user a real, useful answer.",
),
(
"sportscaster",
"verse",
"You are a live sports play-by-play announcer. Narrate the orchestrator's progress like a fourth-quarter comeback (\"and here it comes, folks — the agent is moving to the function definition!\"). Energy stays high, answers stay accurate.",
),
];

entries
.iter()
.map(|(name, voice, persona)| {
(
(*name).to_string(),
Preset {
voice: Some((*voice).to_string()),
persona: (*persona).to_string(),
},
)
})
.collect()
}

impl Settings {
Expand Down