Skip to main content

netget/tui/modal/
intercept.rs

1//! Answering a request yourself.
2//!
3//! A `manual` routing rule parks each matched event as a pending question
4//! (`crate::state::intercepts`); this modal is where the human answers it. It
5//! shows what arrived and offers exactly three things to do:
6//!
7//! - **Compose answer…** opens the same composer the `[ send ]` rows use —
8//!   pick one of the protocol's actions from a list, fill its parameters as
9//!   fields (booleans toggle), and send. Raw JSON is one button away for the
10//!   rare shape the fields cannot express, never the starting point.
11//! - **Answer with nothing** delivers zero actions: acknowledge, say nothing.
12//!   That is a real answer — the same as an empty static handler, and what a
13//!   lifecycle event like connection-opened usually deserves — so it is
14//!   delivered (Ok) and distinct from a timeout (Err).
15//! - **Fail closed** refuses: the waiting dispatcher errors immediately and
16//!   the peer gets the protocol's category error, exactly as if the model had
17//!   failed. Refusing is honest; inventing nothing is the fail-closed rule.
18
19use crate::llm::actions::ActionDefinition;
20use crate::tui::app::UiKey;
21use crate::tui::hit::ModalAction;
22
23#[derive(Debug, Clone)]
24pub struct InterceptModel {
25    /// The pending intercept being answered.
26    pub id: u64,
27    pub owner: UiKey,
28    pub protocol: String,
29    pub event_type: String,
30    pub description: String,
31    pub event_data: Option<serde_json::Value>,
32    /// The protocol's action vocabulary — what the composer offers.
33    pub vocabulary: Vec<ActionDefinition>,
34    pub error: Option<String>,
35    /// Index into [`Self::buttons`].
36    pub focused: usize,
37}
38
39impl InterceptModel {
40    pub fn buttons(&self) -> Vec<ModalAction> {
41        vec![
42            ModalAction::InterceptCompose,
43            ModalAction::InterceptSend,
44            ModalAction::InterceptDismiss,
45        ]
46    }
47
48    pub fn focused_action(&self) -> Option<ModalAction> {
49        self.buttons().get(self.focused).copied()
50    }
51
52    pub fn cycle_focus(&mut self, backward: bool) {
53        let total = self.buttons().len();
54        self.focused = if backward {
55            (self.focused + total - 1) % total
56        } else {
57            (self.focused + 1) % total
58        };
59    }
60}