Skip to main content

ragrig_gpui/model/
component.rs

1//! Agent pipeline components and their status.
2
3use gpui_kit::assets::IconName;
4
5/// The role a component plays in the RAG pipeline.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub enum ComponentKind {
8    /// The chat model.
9    Chat,
10    /// The embedding model.
11    Embedder,
12    /// The vector or document store.
13    Store,
14    /// The document parser.
15    Parser,
16    /// The chunker.
17    Chunker,
18    /// The retrieval strategy.
19    Retriever,
20    /// A vision or OCR component.
21    Vision,
22    /// Bibliographic reference extraction.
23    References,
24    /// Anything else.
25    Other,
26}
27
28impl ComponentKind {
29    /// Short human-readable name.
30    pub fn label(self) -> &'static str {
31        match self {
32            Self::Chat => "Chat model",
33            Self::Embedder => "Embedder",
34            Self::Store => "Store",
35            Self::Parser => "Parser",
36            Self::Chunker => "Chunker",
37            Self::Retriever => "Retriever",
38            Self::Vision => "Vision",
39            Self::References => "References",
40            Self::Other => "Component",
41        }
42    }
43
44    /// Icon used in the agent bar.
45    pub fn icon(self) -> IconName {
46        match self {
47            Self::Chat => IconName::Bot,
48            Self::Embedder => IconName::Cpu,
49            Self::Store => IconName::Database,
50            Self::Parser => IconName::FileText,
51            Self::Chunker => IconName::Scissors,
52            Self::Retriever => IconName::Search,
53            Self::Vision => IconName::Eye,
54            Self::References => IconName::BookOpen,
55            Self::Other => IconName::Settings,
56        }
57    }
58}
59
60/// Status of one agent component.
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
62pub enum ComponentStatus {
63    /// Not configured or switched off.
64    #[default]
65    Inactive,
66    /// Configured and available.
67    Ready,
68    /// Currently working.
69    Busy,
70    /// Failed; the detail line explains why.
71    Error,
72}
73
74impl ComponentStatus {
75    /// Short human-readable name.
76    pub fn label(self) -> &'static str {
77        match self {
78            Self::Inactive => "inactive",
79            Self::Ready => "ready",
80            Self::Busy => "busy",
81            Self::Error => "error",
82        }
83    }
84
85    /// Returns whether the component deserves a pulsing dot.
86    pub fn is_busy(self) -> bool {
87        matches!(self, Self::Busy)
88    }
89}
90
91/// One component shown in the agent bar.
92#[derive(Clone, Debug, PartialEq)]
93pub struct AgentComponent {
94    /// Stable identifier reported when the component is configured.
95    pub id: String,
96    /// Role in the pipeline.
97    pub kind: ComponentKind,
98    /// Display label, e.g. `DeepSeek Chat`.
99    pub label: String,
100    /// Current status.
101    pub status: ComponentStatus,
102    /// Optional detail line: backend, model, last error.
103    pub detail: Option<String>,
104}
105
106impl AgentComponent {
107    /// A component with an ID, kind, and label.
108    pub fn new(id: impl Into<String>, kind: ComponentKind, label: impl Into<String>) -> Self {
109        Self {
110            id: id.into(),
111            kind,
112            label: label.into(),
113            status: ComponentStatus::default(),
114            detail: None,
115        }
116    }
117
118    /// Set the status.
119    pub fn status(mut self, status: ComponentStatus) -> Self {
120        self.status = status;
121        self
122    }
123
124    /// Set the detail line.
125    pub fn detail(mut self, detail: impl Into<String>) -> Self {
126        self.detail = Some(detail.into());
127        self
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn kinds_have_labels_and_icons() {
137        assert_eq!(ComponentKind::Chat.label(), "Chat model");
138        assert_eq!(ComponentKind::Store.icon(), IconName::Database);
139        assert_eq!(ComponentKind::References.label(), "References");
140        assert_eq!(ComponentKind::References.icon(), IconName::BookOpen);
141    }
142
143    #[test]
144    fn components_default_to_inactive() {
145        let component = AgentComponent::new("chat", ComponentKind::Chat, "DeepSeek");
146        assert_eq!(component.status, ComponentStatus::Inactive);
147        assert!(!component.status.is_busy());
148    }
149}