From 947e56ef4132fc7e377b2c3eff2179095159f38a Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Wed, 21 Jan 2026 17:38:36 -0800 Subject: [PATCH] feat: naomi did too much at once (#53) - feat: add slash commands - feat: toggle window always on top - fix: save settings button closes settings panel - feat: input history (both text and commands) - feat: add keyboard shortcuts - feat: add confirmation modal when closing connected tabs - fix: better text colours in light mode - fix: handle multiple tabs requesting permission Closes #6 Closes #13 Closes #21 Closes #28 Reviewed-on: https://git.nhcarrigan.com/nhcarrigan/hikari-desktop/pulls/53 Co-authored-by: Naomi Carrigan Co-committed-by: Naomi Carrigan --- src-tauri/src/config.rs | 6 + src/lib/commands/slashCommands.ts | 139 ++++++++++++++ src/lib/components/AboutPanel.svelte | 28 +-- .../components/CloseTabConfirmModal.svelte | 107 +++++++++++ src/lib/components/ConfigSidebar.svelte | 77 +++++--- src/lib/components/ConversationTabs.svelte | 133 +++++++------ src/lib/components/HelpPanel.svelte | 21 +-- src/lib/components/InputBar.svelte | 178 +++++++++++++++++- .../components/KeyboardShortcutsModal.svelte | 177 +++++++++++++++++ src/lib/components/PermissionModal.svelte | 30 ++- src/lib/components/SlashCommandMenu.svelte | 86 +++++++++ src/lib/components/StatusBar.svelte | 27 +++ src/lib/stores/claude.ts | 6 +- src/lib/stores/config.ts | 2 + src/lib/stores/conversations.ts | 55 +++++- src/lib/tauri.ts | 28 +-- src/routes/+page.svelte | 77 +++++++- 17 files changed, 1040 insertions(+), 137 deletions(-) create mode 100644 src/lib/commands/slashCommands.ts create mode 100644 src/lib/components/CloseTabConfirmModal.svelte create mode 100644 src/lib/components/KeyboardShortcutsModal.svelte create mode 100644 src/lib/components/SlashCommandMenu.svelte diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index a801550..56b79e1 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -55,6 +55,9 @@ pub struct HikariConfig { #[serde(default = "default_notification_volume")] pub notification_volume: f32, + + #[serde(default)] + pub always_on_top: bool, } impl Default for HikariConfig { @@ -70,6 +73,7 @@ impl Default for HikariConfig { greeting_custom_prompt: None, notifications_enabled: true, notification_volume: 0.7, + always_on_top: false, } } } @@ -109,6 +113,7 @@ mod tests { assert_eq!(config.theme, Theme::Dark); assert!(config.greeting_enabled); assert!(config.greeting_custom_prompt.is_none()); + assert!(!config.always_on_top); } #[test] @@ -124,6 +129,7 @@ mod tests { greeting_custom_prompt: Some("Hello!".to_string()), notifications_enabled: true, notification_volume: 0.7, + always_on_top: true, }; let json = serde_json::to_string(&config).unwrap(); diff --git a/src/lib/commands/slashCommands.ts b/src/lib/commands/slashCommands.ts new file mode 100644 index 0000000..cfab588 --- /dev/null +++ b/src/lib/commands/slashCommands.ts @@ -0,0 +1,139 @@ +import { get } from "svelte/store"; +import { invoke } from "@tauri-apps/api/core"; +import { claudeStore } from "$lib/stores/claude"; +import { characterState } from "$lib/stores/character"; +import { setSkipNextGreeting } from "$lib/tauri"; + +export interface SlashCommand { + name: string; + description: string; + usage: string; + execute: (args: string) => Promise | void; +} + +async function startNewConversation(): Promise { + const conversationId = get(claudeStore.activeConversationId); + if (!conversationId) { + claudeStore.addLine("error", "No active conversation"); + return; + } + + try { + const workingDir = await invoke("get_working_directory", { + conversationId, + }); + + claudeStore.addLine("system", "Starting new conversation..."); + characterState.setState("thinking"); + + await invoke("interrupt_claude", { conversationId }); + + claudeStore.clearTerminal(); + + setSkipNextGreeting(true); + + await invoke("start_claude", { + conversationId, + options: { + working_dir: workingDir, + }, + }); + + claudeStore.addLine("system", "New conversation started!"); + characterState.setState("idle"); + } catch (error) { + claudeStore.addLine("error", `Failed to start new conversation: ${error}`); + characterState.setTemporaryState("error", 3000); + } +} + +export const slashCommands: SlashCommand[] = [ + { + name: "clear", + description: "Clear the terminal display (keeps conversation context)", + usage: "/clear", + execute: () => { + claudeStore.clearTerminal(); + claudeStore.addLine("system", "Terminal cleared"); + }, + }, + { + name: "new", + description: "Start a fresh conversation (resets context)", + usage: "/new", + execute: startNewConversation, + }, + { + name: "help", + description: "Show available slash commands", + usage: "/help", + execute: () => { + const helpText = slashCommands + .map((cmd) => ` ${cmd.usage.padEnd(12)} - ${cmd.description}`) + .join("\n"); + claudeStore.addLine("system", `Available commands:\n${helpText}`); + }, + }, + { + name: "summarise", + description: "Get a summary of the entire conversation", + usage: "/summarise", + execute: async () => { + const conversationId = get(claudeStore.activeConversationId); + if (!conversationId) { + claudeStore.addLine("error", "No active conversation"); + return; + } + + try { + claudeStore.addLine("system", "Requesting conversation summary..."); + await invoke("send_prompt", { + conversationId, + message: + "Please provide a comprehensive summary of our entire conversation so far, including the key topics we've discussed, decisions made, and any important context.", + }); + } catch (error) { + claudeStore.addLine("error", `Failed to request summary: ${error}`); + } + }, + }, +]; + +export function parseSlashCommand(input: string): { + command: SlashCommand | null; + args: string; +} { + const trimmed = input.trim(); + + if (!trimmed.startsWith("/")) { + return { command: null, args: "" }; + } + + const parts = trimmed.slice(1).split(/\s+/); + const commandName = parts[0]?.toLowerCase(); + const args = parts.slice(1).join(" "); + + const command = slashCommands.find((cmd) => cmd.name.toLowerCase() === commandName); + + return { command: command || null, args }; +} + +export function getMatchingCommands(input: string): SlashCommand[] { + const trimmed = input.trim(); + + if (!trimmed.startsWith("/")) { + return []; + } + + const partial = trimmed.slice(1).toLowerCase(); + + if (partial === "") { + return slashCommands; + } + + return slashCommands.filter((cmd) => cmd.name.toLowerCase().startsWith(partial)); +} + +export function isSlashCommand(input: string): boolean { + return input.trim().startsWith("/"); +} diff --git a/src/lib/components/AboutPanel.svelte b/src/lib/components/AboutPanel.svelte index 4f240b6..7cec772 100644 --- a/src/lib/components/AboutPanel.svelte +++ b/src/lib/components/AboutPanel.svelte @@ -40,10 +40,12 @@ tabindex="-1" >
-

About Hikari Desktop

+

+ About Hikari Desktop +

-

Support & Community

-

Found a bug or have a suggestion?

+

Support & Community

+

Found a bug or have a suggestion?

-

Built with 💕 by

+

Built with 💕 by

-

License

-

+

License

+

This project is open source and available under our license terms.

+ +
+ + + +{/if} + + diff --git a/src/lib/components/ConfigSidebar.svelte b/src/lib/components/ConfigSidebar.svelte index 69366aa..2ae9fc5 100644 --- a/src/lib/components/ConfigSidebar.svelte +++ b/src/lib/components/ConfigSidebar.svelte @@ -1,6 +1,7 @@ @@ -121,7 +131,7 @@

Settings