Ctrl K
ComunidadGuías…

native/src/app.rs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
use crate::runtime::{RuntimeController, RuntimeEvent};
use crate::state::{OverlayState, VoiceLevelTracker};
use crate::theme::transparent;
use gpui::{prelude::*, *};
use std::time::Duration;

pub struct OriginSpeakApp {
    _runtime: RuntimeController,
    overlay: OverlayState,
    audio_level: f32,
    voice_level: VoiceLevelTracker,
    status_overlay_window: Option<WindowHandle<crate::overlay::StatusOverlayView>>,
    status_overlay_opening: bool,
    _runtime_bridge: Task<()>,
}

impl OriginSpeakApp {
    pub fn new(runtime: RuntimeController, cx: &mut Context<Self>) -> Self {
        let bridge_events = runtime.events();
        let runtime_bridge = cx.spawn(async move |this, cx| {
            loop {
                cx.background_executor()
                    .timer(Duration::from_millis(20))
                    .await;

                let pending = match bridge_events.lock() {
                    Ok(receiver) => receiver.try_iter().collect::<Vec<_>>(),
                    Err(_) => return,
                };
                let quit_requested = crate::platform_shell::take_quit_request();
                if pending.is_empty() && !quit_requested {
                    continue;
                }

                if this
                    .update(cx, |this, cx| {
                        if quit_requested {
                            cx.quit();
                            return;
                        }
                        for event in pending {
                            let auto_idle = match event {
                                RuntimeEvent::Success => {
                                    this.apply_runtime_state(OverlayState::Success, cx);
                                    Some((OverlayState::Success, Duration::from_millis(900)))
                                }
                                RuntimeEvent::Error => {
                                    this.apply_runtime_state(OverlayState::Error, cx);
                                    Some((OverlayState::Error, Duration::from_millis(1500)))
                                }
                                RuntimeEvent::Idle => {
                                    this.apply_runtime_state(OverlayState::Idle, cx);
                                    None
                                }
                                RuntimeEvent::Listening => {
                                    this.apply_runtime_state(OverlayState::Listening, cx);
                                    None
                                }
                                RuntimeEvent::Processing => {
                                    this.apply_runtime_state(OverlayState::Processing, cx);
                                    None
                                }
                                RuntimeEvent::AudioLevel(level) => {
                                    if this.overlay == OverlayState::Listening {
                                        this.audio_level = this.voice_level.update(level);
                                        cx.notify();
                                    }
                                    None
                                }
                            };

                            if let Some((expected, delay)) = auto_idle {
                                cx.spawn(async move |this, cx| {
                                    cx.background_executor().timer(delay).await;
                                    let _ = this.update(cx, |this, cx| {
                                        if this.overlay == expected {
                                            this.apply_runtime_state(OverlayState::Idle, cx);
                                        }
                                    });
                                })
                                .detach();
                            }
                        }
                    })
                    .is_err()
                {
                    return;
                }
            }
        });

        Self {
            _runtime: runtime,
            overlay: OverlayState::Idle,
            audio_level: 0.0,
            voice_level: VoiceLevelTracker::default(),
            status_overlay_window: None,
            status_overlay_opening: false,
            _runtime_bridge: runtime_bridge,
        }
    }

    pub(crate) fn overlay_snapshot(&self) -> (OverlayState, f32) {
        (self.overlay, self.audio_level)
    }

    fn apply_runtime_state(&mut self, state: OverlayState, cx: &mut Context<Self>) {
        self.overlay = state;
        if state != OverlayState::Listening {
            self.audio_level = 0.0;
        }
        self.reconcile_overlay(cx);
        cx.notify();
    }

    fn reconcile_overlay(&mut self, cx: &mut Context<Self>) {
        let should_be_open = self.overlay != OverlayState::Idle;
        if should_be_open && self.status_overlay_window.is_none() && !self.status_overlay_opening {
            self.status_overlay_opening = true;
            let app = cx.entity();
            cx.defer(move |cx| {
                let opened = crate::overlay::open_status_overlay(app.clone(), cx);
                let mut close_after_open = None;
                app.update(cx, |this, _| {
                    this.status_overlay_opening = false;
                    match opened {
                        Ok(window) if this.overlay != OverlayState::Idle => {
                            this.status_overlay_window = Some(window);
                        }
                        Ok(window) => close_after_open = Some(window),
                        Err(_error) => {
                            #[cfg(debug_assertions)]
                            eprintln!("[Origin Speak] could not open status overlay: {_error}");
                        }
                    }
                });
                if let Some(window) = close_after_open {
                    let _ = window.update(cx, |_, window, _| window.remove_window());
                }
            });
        } else if !should_be_open && let Some(window) = self.status_overlay_window.take() {
            cx.defer(move |cx| {
                let _ = window.update(cx, |_, window, _| window.remove_window());
            });
        }
    }
}

impl Render for OriginSpeakApp {
    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
        // The resident window is deliberately hidden. GPUI remains alive only
        // to own the platform event loop, global shortcut manager, and compact
        // overlay windows.
        div().size_full().bg(transparent())
    }
}