Task attempt 6481dff0-5262-4260-ab55-112df093b125 - Final changes
This commit is contained in:
@@ -151,4 +151,9 @@ impl AppState {
|
||||
let config = self.config.read().await;
|
||||
config.push_notifications
|
||||
}
|
||||
|
||||
pub async fn get_sound_file(&self) -> crate::models::config::SoundFile {
|
||||
let config = self.config.read().await;
|
||||
config.sound_file.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,26 @@ export const EDITOR_LABELS: Record<string, string> = {
|
||||
"intellij": "IntelliJ IDEA",
|
||||
"zed": "Zed",
|
||||
"custom": "Custom"
|
||||
};
|
||||
|
||||
export const SOUND_FILES: SoundFile[] = [
|
||||
"abstract-sound1",
|
||||
"abstract-sound2",
|
||||
"abstract-sound3",
|
||||
"abstract-sound4",
|
||||
"cow-mooing",
|
||||
"phone-vibration",
|
||||
"rooster"
|
||||
];
|
||||
|
||||
export const SOUND_LABELS: Record<string, string> = {
|
||||
"abstract-sound1": "Gentle Chime",
|
||||
"abstract-sound2": "Soft Bell",
|
||||
"abstract-sound3": "Digital Tone",
|
||||
"abstract-sound4": "Subtle Alert",
|
||||
"cow-mooing": "Cow Mooing",
|
||||
"phone-vibration": "Phone Vibration",
|
||||
"rooster": "Rooster Call"
|
||||
};"#
|
||||
.to_string()
|
||||
}
|
||||
@@ -53,6 +73,9 @@ fn main() {
|
||||
vibe_kanban::models::config::EditorConfig::decl(),
|
||||
vibe_kanban::models::config::EditorType::decl(),
|
||||
vibe_kanban::models::config::EditorConstants::decl(),
|
||||
vibe_kanban::models::config::SoundFile::decl(),
|
||||
vibe_kanban::models::config::SoundConstants::decl(),
|
||||
vibe_kanban::routes::config::ConfigConstants::decl(),
|
||||
vibe_kanban::executor::ExecutorConfig::decl(),
|
||||
vibe_kanban::executor::ExecutorConstants::decl(),
|
||||
vibe_kanban::models::project::CreateProject::decl(),
|
||||
|
||||
@@ -69,21 +69,23 @@ async fn commit_execution_changes(
|
||||
}
|
||||
|
||||
/// Play a system sound notification
|
||||
async fn play_sound_notification() {
|
||||
async fn play_sound_notification(sound_file: &crate::models::config::SoundFile) {
|
||||
// Use platform-specific sound notification
|
||||
if cfg!(target_os = "macos") {
|
||||
let sound_path = sound_file.to_path();
|
||||
let _ = tokio::process::Command::new("afplay")
|
||||
.arg("/System/Library/Sounds/Glass.aiff")
|
||||
.arg(sound_path)
|
||||
.spawn();
|
||||
} else if cfg!(target_os = "linux") {
|
||||
// Try different Linux notification sounds
|
||||
let sound_path = sound_file.to_path();
|
||||
if let Ok(_) = tokio::process::Command::new("paplay")
|
||||
.arg("/usr/share/sounds/alsa/Front_Left.wav")
|
||||
.arg(&sound_path)
|
||||
.spawn()
|
||||
{
|
||||
// Success with paplay
|
||||
} else if let Ok(_) = tokio::process::Command::new("aplay")
|
||||
.arg("/usr/share/sounds/alsa/Front_Left.wav")
|
||||
.arg(&sound_path)
|
||||
.spawn()
|
||||
{
|
||||
// Success with aplay
|
||||
@@ -429,7 +431,8 @@ async fn handle_coding_agent_completion(
|
||||
|
||||
// Play sound notification if enabled
|
||||
if app_state.get_sound_alerts_enabled().await {
|
||||
play_sound_notification().await;
|
||||
let sound_file = app_state.get_sound_file().await;
|
||||
play_sound_notification(&sound_file).await;
|
||||
}
|
||||
|
||||
// Send push notification if enabled
|
||||
|
||||
@@ -83,6 +83,40 @@ async fn serve_file(path: &str) -> impl IntoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_sound_file(axum::extract::Path(filename): axum::extract::Path<String>) -> impl IntoResponse {
|
||||
use tokio::fs;
|
||||
use std::path::Path;
|
||||
|
||||
// Validate filename contains only expected sound files
|
||||
let valid_sounds = ["abstract-sound1.mp3", "abstract-sound2.mp3", "abstract-sound3.mp3",
|
||||
"abstract-sound4.mp3", "cow-mooing.mp3", "phone-vibration.mp3", "rooster.mp3"];
|
||||
|
||||
if !valid_sounds.contains(&filename.as_str()) {
|
||||
return Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::from("Sound file not found"))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let sound_path = Path::new("backend/sounds").join(&filename);
|
||||
|
||||
match fs::read(&sound_path).await {
|
||||
Ok(content) => {
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("audio/mpeg"))
|
||||
.body(Body::from(content))
|
||||
.unwrap()
|
||||
}
|
||||
Err(_) => {
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::from("Sound file not found"))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt().init();
|
||||
@@ -130,7 +164,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
.merge(tasks::tasks_router())
|
||||
.merge(task_attempts::task_attempts_router())
|
||||
.merge(filesystem::filesystem_router())
|
||||
.merge(config::config_router()),
|
||||
.merge(config::config_router())
|
||||
.route("/sounds/:filename", get(serve_sound_file)),
|
||||
)
|
||||
.layer(Extension(pool.clone()))
|
||||
.layer(Extension(config_arc));
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct Config {
|
||||
pub disclaimer_acknowledged: bool,
|
||||
pub onboarding_acknowledged: bool,
|
||||
pub sound_alerts: bool,
|
||||
pub sound_file: SoundFile,
|
||||
pub push_notifications: bool,
|
||||
pub editor: EditorConfig,
|
||||
}
|
||||
@@ -43,6 +44,19 @@ pub enum EditorType {
|
||||
Custom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum SoundFile {
|
||||
AbstractSound1,
|
||||
AbstractSound2,
|
||||
AbstractSound3,
|
||||
AbstractSound4,
|
||||
CowMooing,
|
||||
PhoneVibration,
|
||||
Rooster,
|
||||
}
|
||||
|
||||
// Constants for frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
@@ -51,6 +65,13 @@ pub struct EditorConstants {
|
||||
pub editor_labels: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct SoundConstants {
|
||||
pub sound_files: Vec<SoundFile>,
|
||||
pub sound_labels: Vec<String>,
|
||||
}
|
||||
|
||||
impl EditorConstants {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -74,6 +95,31 @@ impl EditorConstants {
|
||||
}
|
||||
}
|
||||
|
||||
impl SoundConstants {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sound_files: vec![
|
||||
SoundFile::AbstractSound1,
|
||||
SoundFile::AbstractSound2,
|
||||
SoundFile::AbstractSound3,
|
||||
SoundFile::AbstractSound4,
|
||||
SoundFile::CowMooing,
|
||||
SoundFile::PhoneVibration,
|
||||
SoundFile::Rooster,
|
||||
],
|
||||
sound_labels: vec![
|
||||
"Gentle Chime".to_string(),
|
||||
"Soft Bell".to_string(),
|
||||
"Digital Tone".to_string(),
|
||||
"Subtle Alert".to_string(),
|
||||
"Cow Mooing".to_string(),
|
||||
"Phone Vibration".to_string(),
|
||||
"Rooster Call".to_string(),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -82,6 +128,7 @@ impl Default for Config {
|
||||
disclaimer_acknowledged: false,
|
||||
onboarding_acknowledged: false,
|
||||
sound_alerts: true,
|
||||
sound_file: SoundFile::AbstractSound4,
|
||||
push_notifications: true,
|
||||
editor: EditorConfig::default(),
|
||||
}
|
||||
@@ -116,6 +163,24 @@ impl EditorConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl SoundFile {
|
||||
pub fn to_filename(&self) -> &'static str {
|
||||
match self {
|
||||
SoundFile::AbstractSound1 => "abstract-sound1.mp3",
|
||||
SoundFile::AbstractSound2 => "abstract-sound2.mp3",
|
||||
SoundFile::AbstractSound3 => "abstract-sound3.mp3",
|
||||
SoundFile::AbstractSound4 => "abstract-sound4.mp3",
|
||||
SoundFile::CowMooing => "cow-mooing.mp3",
|
||||
SoundFile::PhoneVibration => "phone-vibration.mp3",
|
||||
SoundFile::Rooster => "rooster.mp3",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_path(&self) -> PathBuf {
|
||||
PathBuf::from("backend/sounds").join(self.to_filename())
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(config_path: &PathBuf) -> anyhow::Result<Self> {
|
||||
if config_path.exists() {
|
||||
|
||||
@@ -7,13 +7,16 @@ use axum::{
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::models::{config::Config, ApiResponse};
|
||||
use crate::models::{config::{Config, EditorConstants, SoundConstants}, ApiResponse};
|
||||
use crate::utils;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
pub fn config_router() -> Router {
|
||||
Router::new()
|
||||
.route("/config", get(get_config))
|
||||
.route("/config", post(update_config))
|
||||
.route("/config/constants", get(get_config_constants))
|
||||
}
|
||||
|
||||
async fn get_config(
|
||||
@@ -51,3 +54,23 @@ async fn update_config(
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct ConfigConstants {
|
||||
pub editor: EditorConstants,
|
||||
pub sound: SoundConstants,
|
||||
}
|
||||
|
||||
async fn get_config_constants() -> ResponseJson<ApiResponse<ConfigConstants>> {
|
||||
let constants = ConfigConstants {
|
||||
editor: EditorConstants::new(),
|
||||
sound: SoundConstants::new(),
|
||||
};
|
||||
|
||||
ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(constants),
|
||||
message: Some("Config constants retrieved successfully".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user