Compare commits
6 Commits
v0.2.0
..
e4d2d47e61
| Author | SHA1 | Date | |
|---|---|---|---|
|
e4d2d47e61
|
|||
|
511caa3a8e
|
|||
|
51ecf47508
|
|||
|
861b44797e
|
|||
|
6a811d2016
|
|||
|
2f4bfd4183
|
@@ -1,51 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Function to run a command and check its status
|
|
||||||
run_check() {
|
|
||||||
local desc=$1
|
|
||||||
local cmd=$2
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}Running: ${desc}${NC}"
|
|
||||||
echo -e "${YELLOW}Command: ${cmd}${NC}"
|
|
||||||
|
|
||||||
if eval "$cmd"; then
|
|
||||||
echo -e "${GREEN}✓ ${desc} passed${NC}"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ ${desc} failed${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Track if any checks fail
|
|
||||||
failed=0
|
|
||||||
|
|
||||||
echo -e "${YELLOW}🔍 Running all checks for Hikari Desktop...${NC}"
|
|
||||||
|
|
||||||
# Frontend checks
|
|
||||||
run_check "Frontend lint" "pnpm lint" || failed=1
|
|
||||||
run_check "Frontend format check" "pnpm format:check" || failed=1
|
|
||||||
run_check "Frontend type check" "pnpm check" || failed=1
|
|
||||||
run_check "Frontend tests" "pnpm test" || failed=1
|
|
||||||
|
|
||||||
# Backend checks
|
|
||||||
run_check "Backend clippy (strict)" "cd src-tauri && cargo clippy --all-targets --all-features -- -D warnings" || failed=1
|
|
||||||
run_check "Backend tests" "cargo test" || failed=1
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
echo -e "\n${YELLOW}========================================${NC}"
|
|
||||||
if [ $failed -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✨ All checks passed! The code is looking great!${NC}"
|
|
||||||
echo -e "${GREEN} Naomi would be so proud of us! 💖${NC}"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ Some checks failed. Let's fix them together!${NC}"
|
|
||||||
echo -e "${RED} Don't worry, we'll get through this! 💪${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hikari-desktop",
|
"name": "hikari-desktop",
|
||||||
"version": "0.2.0",
|
"version": "0.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1411,7 +1411,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hikari-desktop"
|
name = "hikari-desktop"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hikari-desktop"
|
name = "hikari-desktop"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
description = "Hikari - Claude Code Visual Assistant"
|
description = "Hikari - Claude Code Visual Assistant"
|
||||||
authors = ["Naomi Carrigan"]
|
authors = ["Naomi Carrigan"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -97,13 +97,11 @@ impl BridgeManager {
|
|||||||
.ok_or_else(|| "No Claude instance found for this conversation".to_string())
|
.ok_or_else(|| "No Claude instance found for this conversation".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn cleanup_stopped_bridges(&mut self) {
|
pub fn cleanup_stopped_bridges(&mut self) {
|
||||||
// Remove bridges that are no longer running
|
// Remove bridges that are no longer running
|
||||||
self.bridges.retain(|_, bridge| bridge.is_running());
|
self.bridges.retain(|_, bridge| bridge.is_running());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn stop_all(&mut self) {
|
pub fn stop_all(&mut self) {
|
||||||
if let Some(app) = &self.app_handle {
|
if let Some(app) = &self.app_handle {
|
||||||
for (_, bridge) in self.bridges.iter_mut() {
|
for (_, bridge) in self.bridges.iter_mut() {
|
||||||
@@ -113,7 +111,6 @@ impl BridgeManager {
|
|||||||
self.bridges.clear();
|
self.bridges.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get_active_conversations(&self) -> Vec<String> {
|
pub fn get_active_conversations(&self) -> Vec<String> {
|
||||||
self.bridges.keys()
|
self.bridges.keys()
|
||||||
.filter(|id| self.bridges.get(*id).map(|b| b.is_running()).unwrap_or(false))
|
.filter(|id| self.bridges.get(*id).map(|b| b.is_running()).unwrap_or(false))
|
||||||
|
|||||||
@@ -321,7 +321,6 @@ mod tests {
|
|||||||
let event = StateChangeEvent {
|
let event = StateChangeEvent {
|
||||||
state: CharacterState::Coding,
|
state: CharacterState::Coding,
|
||||||
tool_name: Some("Edit".to_string()),
|
tool_name: Some("Edit".to_string()),
|
||||||
conversation_id: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&event).unwrap();
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
@@ -335,7 +334,6 @@ mod tests {
|
|||||||
line_type: "assistant".to_string(),
|
line_type: "assistant".to_string(),
|
||||||
content: "Test output".to_string(),
|
content: "Test output".to_string(),
|
||||||
tool_name: None,
|
tool_name: None,
|
||||||
conversation_id: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&event).unwrap();
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
|||||||
@@ -103,6 +103,17 @@ impl WslBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn new_with_loaded_achievements(app: &tauri::AppHandle) -> Self {
|
||||||
|
let bridge = Self::new();
|
||||||
|
|
||||||
|
// Load saved achievements into the stats
|
||||||
|
let achievements = crate::achievements::load_achievements(app).await;
|
||||||
|
println!("Loaded achievements into bridge: {} unlocked", achievements.unlocked.len());
|
||||||
|
bridge.stats.write().achievements = achievements;
|
||||||
|
|
||||||
|
bridge
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, app: AppHandle, options: ClaudeStartOptions) -> Result<(), String> {
|
pub fn start(&mut self, app: AppHandle, options: ClaudeStartOptions) -> Result<(), String> {
|
||||||
if self.process.is_some() {
|
if self.process.is_some() {
|
||||||
@@ -383,6 +394,15 @@ impl WslBridge {
|
|||||||
self.stats.read().clone()
|
self.stats.read().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn update_stats(&mut self, input_tokens: u64, output_tokens: u64, model: &str) {
|
||||||
|
self.stats.write().add_usage(input_tokens, output_tokens, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn reset_session_stats(&mut self) {
|
||||||
|
self.stats.write().reset_session();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WslBridge {
|
impl Default for WslBridge {
|
||||||
@@ -894,10 +914,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_shared_bridge_manager() {
|
fn test_create_shared_bridge() {
|
||||||
use crate::bridge_manager::create_shared_bridge_manager;
|
let shared = create_shared_bridge();
|
||||||
let shared = create_shared_bridge_manager();
|
let bridge = shared.lock();
|
||||||
let manager = shared.lock();
|
assert!(!bridge.is_running());
|
||||||
assert!(manager.get_active_conversations().is_empty());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "hikari-desktop",
|
"productName": "hikari-desktop",
|
||||||
"version": "0.2.0",
|
"version": "0.1.0",
|
||||||
"identifier": "com.naomi.hikari-desktop",
|
"identifier": "com.naomi.hikari-desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
|
||||||
import { getVersion } from "@tauri-apps/api/app";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { onClose }: Props = $props();
|
|
||||||
|
|
||||||
let appVersion = $state("");
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
appVersion = await getVersion();
|
|
||||||
});
|
|
||||||
|
|
||||||
const links = {
|
|
||||||
source: "https://git.nhcarrigan.com/nhcarrigan/hikari-desktop",
|
|
||||||
discord: "https://chat.nhcarrigan.com",
|
|
||||||
website: "https://nhcarrigan.com",
|
|
||||||
license: "https://docs.nhcarrigan.com/legal/license/",
|
|
||||||
changelog: "https://git.nhcarrigan.com/nhcarrigan/hikari-desktop/releases",
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
|
||||||
onclick={onClose}
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
onkeydown={(e) => e.key === "Escape" && onClose()}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg shadow-xl max-w-md w-full p-6"
|
|
||||||
onclick={(e) => e.stopPropagation()}
|
|
||||||
onkeydown={(e) => e.stopPropagation()}
|
|
||||||
role="dialog"
|
|
||||||
aria-labelledby="about-title"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h2 id="about-title" class="text-xl font-semibold text-gray-100">About Hikari Desktop</h2>
|
|
||||||
<button
|
|
||||||
onclick={onClose}
|
|
||||||
class="p-1 text-gray-500 hover:text-gray-300 transition-colors"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4 text-sm">
|
|
||||||
<div>
|
|
||||||
<h3 class="font-medium text-gray-200 mb-2">What is Hikari Desktop?</h3>
|
|
||||||
<p class="text-gray-400">
|
|
||||||
Hikari Desktop is an AI-powered desktop assistant that brings Claude directly to your
|
|
||||||
desktop. Built with love using Tauri, Svelte, and Rust for a fast, native experience.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="font-medium text-gray-200 mb-2">Version</h3>
|
|
||||||
<p class="text-gray-400 mb-1">
|
|
||||||
{appVersion || "Loading..."}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onclick={() => openUrl(links.changelog)}
|
|
||||||
class="text-[var(--accent-primary)] hover:text-[var(--accent-primary-hover)] transition-colors underline"
|
|
||||||
>
|
|
||||||
View Changelog
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="font-medium text-gray-200 mb-2">Source Code</h3>
|
|
||||||
<button
|
|
||||||
onclick={() => openUrl(links.source)}
|
|
||||||
class="text-[var(--accent-primary)] hover:text-[var(--accent-primary-hover)] transition-colors underline"
|
|
||||||
>
|
|
||||||
View on Git
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="font-medium text-gray-200 mb-2">Support & Community</h3>
|
|
||||||
<p class="text-gray-400 mb-1">Found a bug or have a suggestion?</p>
|
|
||||||
<button
|
|
||||||
onclick={() => openUrl(links.discord)}
|
|
||||||
class="text-[var(--accent-primary)] hover:text-[var(--accent-primary-hover)] transition-colors underline"
|
|
||||||
>
|
|
||||||
Join our Discord
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="font-medium text-gray-200 mb-2">Built with 💕 by</h3>
|
|
||||||
<button
|
|
||||||
onclick={() => openUrl(links.website)}
|
|
||||||
class="text-[var(--accent-primary)] hover:text-[var(--accent-primary-hover)] transition-colors underline"
|
|
||||||
>
|
|
||||||
Naomi Carrigan
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="font-medium text-gray-200 mb-2">License</h3>
|
|
||||||
<p class="text-gray-400 mb-1">
|
|
||||||
This project is open source and available under our license terms.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onclick={() => openUrl(links.license)}
|
|
||||||
class="text-[var(--accent-primary)] hover:text-[var(--accent-primary-hover)] transition-colors underline"
|
|
||||||
>
|
|
||||||
View License
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pt-4 mt-4 border-t border-[var(--border-color)]">
|
|
||||||
<p class="text-xs text-gray-500 text-center">
|
|
||||||
Copyright © {new Date().getFullYear()} Naomi Carrigan. All rights reserved.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Ensure the panel appears above other content */
|
|
||||||
[role="dialog"] {
|
|
||||||
animation: slideIn 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
interface Props {
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { onClose }: Props = $props();
|
|
||||||
|
|
||||||
const sections = [
|
|
||||||
{
|
|
||||||
title: "Getting Started",
|
|
||||||
items: [
|
|
||||||
"Enter your Claude API key in Settings (gear icon)",
|
|
||||||
"Set your working directory and click Connect",
|
|
||||||
"Start chatting with Hikari - your AI assistant!",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Key Features",
|
|
||||||
items: [
|
|
||||||
"🗂️ File Management: Hikari can read, write, and edit files in your project",
|
|
||||||
"💻 Terminal Access: Execute commands and run scripts",
|
|
||||||
"🔍 Code Search: Find files and search through code",
|
|
||||||
"🌐 Web Access: Fetch information from the web",
|
|
||||||
"📊 MCP Servers: Connect to external tools via Model Context Protocol",
|
|
||||||
"📁 Multi-tab Support: Work on multiple conversations simultaneously",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Available Commands",
|
|
||||||
items: [
|
|
||||||
"Type naturally - Hikari understands context!",
|
|
||||||
"Ask to read, create, or modify files",
|
|
||||||
"Request code explanations or debugging help",
|
|
||||||
"Have Hikari run tests or build commands",
|
|
||||||
"Search for specific functions or patterns",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Tips & Tricks",
|
|
||||||
items: [
|
|
||||||
"💡 Use the stats panel to track your usage",
|
|
||||||
"🎯 Be specific about file paths and requirements",
|
|
||||||
"🔒 Grant tool permissions as needed for security",
|
|
||||||
"📌 Pin important conversations for quick access",
|
|
||||||
"🎨 Customize your theme and preferences in Settings",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Keyboard Shortcuts",
|
|
||||||
items: [
|
|
||||||
"Ctrl/Cmd + Enter: Send message",
|
|
||||||
"Ctrl/Cmd + K: Clear chat (when supported)",
|
|
||||||
"Escape: Close modals and panels",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
|
||||||
onclick={onClose}
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
onkeydown={(e) => e.key === "Escape" && onClose()}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] overflow-hidden flex flex-col"
|
|
||||||
onclick={(e) => e.stopPropagation()}
|
|
||||||
onkeydown={(e) => e.stopPropagation()}
|
|
||||||
role="dialog"
|
|
||||||
aria-labelledby="help-title"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between p-6 pb-4 border-b border-[var(--border-color)]">
|
|
||||||
<h2 id="help-title" class="text-xl font-semibold text-gray-100">How to Use Hikari Desktop</h2>
|
|
||||||
<button
|
|
||||||
onclick={onClose}
|
|
||||||
class="p-1 text-gray-500 hover:text-gray-300 transition-colors"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-y-auto flex-1 p-6 space-y-6">
|
|
||||||
{#each sections as section (section.title)}
|
|
||||||
<div>
|
|
||||||
<h3 class="font-medium text-gray-200 mb-3">{section.title}</h3>
|
|
||||||
<ul class="space-y-2 text-sm text-gray-400">
|
|
||||||
{#each section.items as item (item)}
|
|
||||||
<li class="flex items-start">
|
|
||||||
<span class="text-[var(--accent-primary)] mr-2 mt-0.5">•</span>
|
|
||||||
<span>{item}</span>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<div class="pt-4 border-t border-[var(--border-color)]">
|
|
||||||
<p class="text-sm text-gray-500">
|
|
||||||
<strong>Need more help?</strong> Join our Discord community for support and updates!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Ensure the panel appears above other content */
|
|
||||||
[role="dialog"] {
|
|
||||||
animation: slideIn 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.95) translateY(10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1) translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom scrollbar styling */
|
|
||||||
.overflow-y-auto {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: var(--border-color) transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-y-auto::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-y-auto::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-y-auto::-webkit-scrollbar-thumb {
|
|
||||||
background-color: var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
|
|
||||||
background-color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -15,12 +15,9 @@
|
|||||||
import type { ConnectionStatus } from "$lib/types/messages";
|
import type { ConnectionStatus } from "$lib/types/messages";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import StatsDisplay from "./StatsDisplay.svelte";
|
import StatsDisplay from "./StatsDisplay.svelte";
|
||||||
import AboutPanel from "./AboutPanel.svelte";
|
|
||||||
import HelpPanel from "./HelpPanel.svelte";
|
|
||||||
import { achievementProgress } from "$lib/stores/achievements";
|
import { achievementProgress } from "$lib/stores/achievements";
|
||||||
|
|
||||||
const DISCORD_URL = "https://chat.nhcarrigan.com";
|
const DISCORD_URL = "https://chat.nhcarrigan.com";
|
||||||
const DONATE_URL = "https://donate.nhcarrigan.com";
|
|
||||||
|
|
||||||
let connectionStatus: ConnectionStatus = $state("disconnected");
|
let connectionStatus: ConnectionStatus = $state("disconnected");
|
||||||
let workingDirectory = $state("");
|
let workingDirectory = $state("");
|
||||||
@@ -29,8 +26,6 @@
|
|||||||
let grantedToolsList: string[] = $state([]);
|
let grantedToolsList: string[] = $state([]);
|
||||||
let appVersion = $state("");
|
let appVersion = $state("");
|
||||||
let showStats = $state(false);
|
let showStats = $state(false);
|
||||||
let showAbout = $state(false);
|
|
||||||
let showHelp = $state(false);
|
|
||||||
const progress = $derived($achievementProgress);
|
const progress = $derived($achievementProgress);
|
||||||
let currentConfig: HikariConfig = $state({
|
let currentConfig: HikariConfig = $state({
|
||||||
model: null,
|
model: null,
|
||||||
@@ -246,45 +241,6 @@
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onclick={() => openUrl(DONATE_URL)}
|
|
||||||
class="p-1 text-gray-500 hover:text-[var(--accent-primary)] transition-colors"
|
|
||||||
title="Support our work"
|
|
||||||
>
|
|
||||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onclick={() => (showAbout = true)}
|
|
||||||
class="p-1 text-gray-500 hover:text-[var(--accent-primary)] transition-colors"
|
|
||||||
title="About Hikari Desktop"
|
|
||||||
>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onclick={() => (showHelp = true)}
|
|
||||||
class="p-1 text-gray-500 hover:text-[var(--accent-primary)] transition-colors"
|
|
||||||
title="Help"
|
|
||||||
>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onclick={() => openUrl(DISCORD_URL)}
|
onclick={() => openUrl(DISCORD_URL)}
|
||||||
class="p-1 text-gray-500 hover:text-[var(--accent-primary)] transition-colors"
|
class="p-1 text-gray-500 hover:text-[var(--accent-primary)] transition-colors"
|
||||||
@@ -332,11 +288,3 @@
|
|||||||
<StatsDisplay />
|
<StatsDisplay />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showAbout}
|
|
||||||
<AboutPanel onClose={() => (showAbout = false)} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if showHelp}
|
|
||||||
<HelpPanel onClose={() => (showHelp = false)} />
|
|
||||||
{/if}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { NOTIFICATION_SOUNDS, type NotificationType } from "./types";
|
|||||||
|
|
||||||
class SoundPlayer {
|
class SoundPlayer {
|
||||||
private audioCache: Map<NotificationType, HTMLAudioElement> = new Map();
|
private audioCache: Map<NotificationType, HTMLAudioElement> = new Map();
|
||||||
private enabled: boolean = false; // Start disabled until config loads
|
private enabled: boolean = true;
|
||||||
private globalVolume: number = 1.0;
|
private globalVolume: number = 1.0;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
|
import { derived } from "svelte/store";
|
||||||
import { configStore } from "./config";
|
import { configStore } from "./config";
|
||||||
import { soundPlayer } from "$lib/notifications";
|
import { soundPlayer } from "$lib/notifications";
|
||||||
|
|
||||||
let unsubscribe: (() => void) | null = null;
|
// Sync notification settings with config
|
||||||
|
export const notificationSettings = derived(configStore.config, ($config) => {
|
||||||
// Initialize notification settings sync - call this once on app startup
|
|
||||||
export function initNotificationSync() {
|
|
||||||
// Prevent duplicate subscriptions
|
|
||||||
if (unsubscribe) return;
|
|
||||||
|
|
||||||
unsubscribe = configStore.config.subscribe(($config) => {
|
|
||||||
soundPlayer.setEnabled($config.notifications_enabled);
|
soundPlayer.setEnabled($config.notifications_enabled);
|
||||||
soundPlayer.setGlobalVolume($config.notification_volume);
|
soundPlayer.setGlobalVolume($config.notification_volume);
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: $config.notifications_enabled,
|
||||||
|
volume: $config.notification_volume,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper to update notification settings
|
||||||
|
export async function updateNotificationSettings(enabled: boolean, volume: number) {
|
||||||
|
await configStore.updateConfig({
|
||||||
|
notifications_enabled: enabled,
|
||||||
|
notification_volume: volume,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup function if needed
|
|
||||||
export function cleanupNotificationSync() {
|
|
||||||
if (unsubscribe) {
|
|
||||||
unsubscribe();
|
|
||||||
unsubscribe = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
import { initializeTauriListeners, cleanupTauriListeners } from "$lib/tauri";
|
import { initializeTauriListeners, cleanupTauriListeners } from "$lib/tauri";
|
||||||
import { configStore, applyTheme } from "$lib/stores/config";
|
import { configStore, applyTheme } from "$lib/stores/config";
|
||||||
import { initNotificationSync, cleanupNotificationSync } from "$lib/stores/notifications";
|
|
||||||
import { conversationsStore } from "$lib/stores/conversations";
|
import { conversationsStore } from "$lib/stores/conversations";
|
||||||
import "$lib/notifications/testNotifications";
|
import "$lib/notifications/testNotifications";
|
||||||
import Terminal from "$lib/components/Terminal.svelte";
|
import Terminal from "$lib/components/Terminal.svelte";
|
||||||
@@ -30,16 +29,12 @@
|
|||||||
// Apply saved theme on startup
|
// Apply saved theme on startup
|
||||||
const config = configStore.getConfig();
|
const config = configStore.getConfig();
|
||||||
applyTheme(config.theme);
|
applyTheme(config.theme);
|
||||||
|
|
||||||
// Initialize notification settings sync
|
|
||||||
initNotificationSync();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
cleanupTauriListeners();
|
cleanupTauriListeners();
|
||||||
cleanupNotificationSync();
|
|
||||||
initialized = false;
|
initialized = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 680 KiB |
|
Before Width: | Height: | Size: 442 KiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 414 KiB |
|
Before Width: | Height: | Size: 777 KiB After Width: | Height: | Size: 778 KiB |