generated from nhcarrigan/template
feat: add notification sounds (#44)
### Explanation _No response_ ### Issue _No response_ ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Reviewed-on: #44 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #44.
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
theme: "dark",
|
||||
greeting_enabled: true,
|
||||
greeting_custom_prompt: null,
|
||||
notifications_enabled: true,
|
||||
notification_volume: 0.7,
|
||||
});
|
||||
|
||||
let isOpen = $state(false);
|
||||
@@ -394,6 +396,51 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Notifications Section -->
|
||||
<section class="mb-6">
|
||||
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
|
||||
Notifications
|
||||
</h3>
|
||||
|
||||
<!-- Enable/Disable Notifications -->
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={config.notifications_enabled}
|
||||
class="w-4 h-4 text-[var(--accent-primary)] bg-[var(--bg-primary)] border-[var(--border-color)] rounded focus:ring-[var(--accent-primary)] focus:ring-2"
|
||||
/>
|
||||
<span class="text-sm text-gray-300">Enable sound notifications</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Volume Control -->
|
||||
<div class="mb-4">
|
||||
<label for="notification-volume" class="block text-sm text-gray-400 mb-2">
|
||||
Notification Volume
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
id="notification-volume"
|
||||
type="range"
|
||||
bind:value={config.notification_volume}
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
disabled={!config.notifications_enabled}
|
||||
class="flex-1 h-2 bg-[var(--bg-primary)] rounded-lg appearance-none cursor-pointer disabled:opacity-50"
|
||||
/>
|
||||
<span class="text-sm text-gray-300 w-12 text-right">
|
||||
{Math.round(config.notification_volume * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
Sound notifications will play when I complete tasks, encounter errors, or need permissions.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="sticky bottom-0 pt-4 pb-2 bg-[var(--bg-secondary)]">
|
||||
<button
|
||||
@@ -406,3 +453,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
/* Custom range input styling */
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--accent-primary);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--accent-primary);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type="range"]:disabled::-webkit-slider-thumb {
|
||||
background: var(--text-tertiary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input[type="range"]:disabled::-moz-range-thumb {
|
||||
background: var(--text-tertiary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { claudeStore } from "$lib/stores/claude";
|
||||
import { characterState } from "$lib/stores/character";
|
||||
import { handleNewUserMessage } from "$lib/notifications/rules";
|
||||
|
||||
let inputValue = $state("");
|
||||
let isSubmitting = $state(false);
|
||||
@@ -20,6 +21,9 @@
|
||||
isSubmitting = true;
|
||||
inputValue = "";
|
||||
|
||||
// Reset notification state for new user message
|
||||
handleNewUserMessage();
|
||||
|
||||
claudeStore.addLine("user", message);
|
||||
characterState.setState("thinking");
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { notificationManager } from "$lib/notifications/notificationManager";
|
||||
|
||||
let results: { method: string; success: boolean; error?: string }[] = [];
|
||||
let testing = false;
|
||||
|
||||
async function testNotificationMethod(method: string, invokeCommand: string) {
|
||||
try {
|
||||
await invoke(invokeCommand, {
|
||||
title: "Hikari Test",
|
||||
body: `Testing ${method} notification method`,
|
||||
});
|
||||
return { method, success: true };
|
||||
} catch (error) {
|
||||
return { method, success: false, error: String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
async function testAllMethods() {
|
||||
testing = true;
|
||||
results = [];
|
||||
|
||||
const methods = [
|
||||
{ name: "WSL Toast (System Tray)", command: "send_wsl_notification" },
|
||||
{ name: "VBScript (Popup Dialog)", command: "send_vbs_notification" },
|
||||
{ name: "Notify-send (Linux)", command: "send_notify_send" },
|
||||
{ name: "Windows PowerShell", command: "send_windows_notification" },
|
||||
{ name: "Simple Message (Dialog)", command: "send_simple_notification" },
|
||||
];
|
||||
|
||||
for (const method of methods) {
|
||||
const result = await testNotificationMethod(method.name, method.command);
|
||||
results = [...results, result];
|
||||
// Wait a bit between tests
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
testing = false;
|
||||
}
|
||||
|
||||
async function testIntegratedNotification() {
|
||||
await notificationManager.notifySuccess("Integrated notification test!");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="notification-debugger">
|
||||
<h3>Notification Method Debugger</h3>
|
||||
|
||||
<div class="test-buttons">
|
||||
<button on:click={testAllMethods} disabled={testing}>
|
||||
{testing ? "Testing..." : "Test All Methods"}
|
||||
</button>
|
||||
|
||||
<button on:click={testIntegratedNotification}> Test Integrated Notification </button>
|
||||
</div>
|
||||
|
||||
{#if results.length > 0}
|
||||
<div class="results">
|
||||
<h4>Test Results:</h4>
|
||||
{#each results as result (result.method)}
|
||||
<div class="result" class:success={result.success} class:failed={!result.success}>
|
||||
<span class="method">{result.method}:</span>
|
||||
<span class="status">{result.success ? "✓ Success" : "✗ Failed"}</span>
|
||||
{#if result.error}
|
||||
<div class="error">{result.error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.notification-debugger {
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.test-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.results {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.result {
|
||||
padding: 0.5rem;
|
||||
margin: 0.25rem 0;
|
||||
border-radius: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.result.success {
|
||||
background: rgba(0, 255, 0, 0.1);
|
||||
border: 1px solid rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.result.failed {
|
||||
background: rgba(255, 0, 0, 0.1);
|
||||
border: 1px solid rgba(255, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.method {
|
||||
font-weight: bold;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.status {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.error {
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--error-color);
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
@@ -25,6 +25,8 @@
|
||||
theme: "dark",
|
||||
greeting_enabled: true,
|
||||
greeting_custom_prompt: null,
|
||||
notifications_enabled: true,
|
||||
notification_volume: 0.5,
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
|
||||
Reference in New Issue
Block a user