From ff50b28641295b1f1798a474f718c153ee931ffa Mon Sep 17 00:00:00 2001 From: Hikari Date: Sun, 25 Jan 2026 18:15:47 -0800 Subject: [PATCH] feat: add streamer mode for privacy during streaming - Add quick toggle button in InputBar for easy access - Mask API keys in settings when streamer mode active - Optional path masking to hide usernames in file paths - Visual LIVE indicator in both InputBar and StatusBar - Keyboard shortcut Ctrl+Shift+S for quick toggle - Privacy section in settings for additional options Closes #35 --- src-tauri/src/config.rs | 10 ++ src/lib/components/ConfigSidebar.svelte | 132 +++++++++++++++++------- src/lib/components/InputBar.svelte | 74 +++++++++++++ src/lib/components/StatusBar.svelte | 12 ++- src/lib/components/Terminal.svelte | 10 +- src/lib/stores/config.ts | 32 ++++++ src/routes/+page.svelte | 7 ++ 7 files changed, 235 insertions(+), 42 deletions(-) diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 69f3e7a..5bf59fd 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -73,6 +73,12 @@ pub struct HikariConfig { #[serde(default)] pub minimize_to_tray: bool, + + #[serde(default)] + pub streamer_mode: bool, + + #[serde(default)] + pub streamer_hide_paths: bool, } impl Default for HikariConfig { @@ -93,6 +99,8 @@ impl Default for HikariConfig { character_panel_width: None, font_size: 14, minimize_to_tray: false, + streamer_mode: false, + streamer_hide_paths: false, } } } @@ -147,6 +155,8 @@ mod tests { assert!(config.character_panel_width.is_none()); assert_eq!(config.font_size, 14); assert!(!config.minimize_to_tray); + assert!(!config.streamer_mode); + assert!(!config.streamer_hide_paths); } #[test] diff --git a/src/lib/components/ConfigSidebar.svelte b/src/lib/components/ConfigSidebar.svelte index 335bad9..6015b62 100644 --- a/src/lib/components/ConfigSidebar.svelte +++ b/src/lib/components/ConfigSidebar.svelte @@ -27,6 +27,8 @@ update_checks_enabled: true, character_panel_width: null, font_size: 14, + streamer_mode: false, + streamer_hide_paths: false, }); let isOpen = $state(false); @@ -187,47 +189,60 @@
- - + {#if config.streamer_mode} + + {:else} + + + {/if}
@@ -519,6 +534,45 @@ + +
+

+ Privacy / Streamer Mode +

+ + +
+ +

+ Hide sensitive information like API keys when streaming (Ctrl+Shift+S to toggle) +

+
+ + + {#if config.streamer_mode} +
+ +

+ Mask directory paths (e.g., /home/user → /home/****) +

+
+ {/if} +
+

diff --git a/src/lib/components/InputBar.svelte b/src/lib/components/InputBar.svelte index b8f82cc..d798985 100644 --- a/src/lib/components/InputBar.svelte +++ b/src/lib/components/InputBar.svelte @@ -25,6 +25,7 @@ isSlashCommand, type SlashCommand, } from "$lib/commands/slashCommands"; + import { configStore, isStreamerMode } from "$lib/stores/config"; import AttachmentPreview from "$lib/components/AttachmentPreview.svelte"; import SnippetLibraryPanel from "$lib/components/SnippetLibraryPanel.svelte"; import QuickActionsPanel from "$lib/components/QuickActionsPanel.svelte"; @@ -46,6 +47,11 @@ let showSnippetLibrary = $state(false); let showQuickActions = $state(false); let showClipboardHistory = $state(false); + let streamerModeActive = $state(false); + + isStreamerMode.subscribe((value) => { + streamerModeActive = value; + }); // Input history state let inputHistory = $state([]); @@ -765,6 +771,34 @@ User: ${formattedMessage}`;
+
+ {#if streamerModeActive} +
+ {/if}
{/each} diff --git a/src/lib/stores/config.ts b/src/lib/stores/config.ts index 86b069c..afdecd0 100644 --- a/src/lib/stores/config.ts +++ b/src/lib/stores/config.ts @@ -19,6 +19,8 @@ export interface HikariConfig { update_checks_enabled: boolean; character_panel_width: number | null; font_size: number; + streamer_mode: boolean; + streamer_hide_paths: boolean; } const defaultConfig: HikariConfig = { @@ -37,6 +39,8 @@ const defaultConfig: HikariConfig = { update_checks_enabled: true, character_panel_width: null, font_size: 14, + streamer_mode: false, + streamer_hide_paths: false, }; function createConfigStore() { @@ -145,6 +149,12 @@ function createConfigStore() { config.subscribe((c) => (currentConfig = c))(); return currentConfig; }, + + toggleStreamerMode: async () => { + let currentConfig: HikariConfig = defaultConfig; + config.subscribe((c) => (currentConfig = c))(); + await updateConfig({ streamer_mode: !currentConfig.streamer_mode }); + }, }; } @@ -174,3 +184,25 @@ export { MIN_FONT_SIZE, MAX_FONT_SIZE, DEFAULT_FONT_SIZE }; export const configStore = createConfigStore(); export const isDarkTheme = derived(configStore.config, ($config) => $config.theme === "dark"); + +export const isStreamerMode = derived(configStore.config, ($config) => $config.streamer_mode); +export const shouldHidePaths = derived( + configStore.config, + ($config) => $config.streamer_mode && $config.streamer_hide_paths +); + +/** + * Masks file paths in text when streamer mode with hide paths is enabled. + * Replaces username portion of paths with asterisks. + */ +export function maskPaths(text: string, hidePaths: boolean): string { + if (!hidePaths) return text; + + // Match Unix paths like /home/username/... or /Users/username/... + // and Windows paths like C:\Users\username\... + return text + .replace(/\/home\/([^\/\s]+)/g, "/home/****") + .replace(/\/Users\/([^\/\s]+)/g, "/Users/****") + .replace(/C:\\Users\\([^\\\s]+)/gi, "C:\\Users\\****") + .replace(/~\//g, "****/"); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3c22695..169c5a1 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -149,6 +149,13 @@ configStore.resetFontSize(); return; } + + // Ctrl+Shift+S - Toggle streamer mode + if (event.ctrlKey && event.shiftKey && event.key === "S") { + event.preventDefault(); + configStore.toggleStreamerMode(); + return; + } } async function handleInterrupt() {