diff options
| author | Michelle Tilley <michelle@michelletilley.net> | 2026-04-14 16:03:08 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-15 00:03:08 +0100 |
| commit | fd188da879d977ca847f10708c39dd4801a204c4 (patch) | |
| tree | 592bfe2644f8bd9be3563f176eabf29e55fa9a9b /crates/atuin-ai/src/tui/slash.rs | |
| parent | fix: dependency fix (#3414) (diff) | |
| download | atuin-fd188da879d977ca847f10708c39dd4801a204c4.zip | |
feat: Allow resuming previous AI sessions (#3407)
This PR introduces session continuation to Atuin AI.
* Conversations with Atuin AI are stored in a local SQLite database
* Upon startup, Atuin AI tries to find a session to resume based on its
directory/workspace and the time since the last event
* If found, Atuin AI will show a note that the session has been resumed,
and an event is added to help the LLM know where the invocation
boundaries are
* If not, Atuin AI will create a new conversation
* The user can create a new conversation with `/new`
* The new setting `ai.session_continue_minutes`, which defaults to `60`,
controls how old the last event in a session can be before it's no
longer considered for automatic resuming.
<img width="1055" height="593" alt="image"
src="https://github.com/user-attachments/assets/3f9ff01a-ef64-44a9-b0e2-3a4252c5746f"
/>
## Architecture
A new `SessionService` trait defines an API contract for a service that
can manage session data. `LocalSessionService` implements this, with
`DaemonSessionService` a possible future extension point.
`SessionManager` owns a `dyn SessionService` and delegates as
appropriate.
Diffstat (limited to 'crates/atuin-ai/src/tui/slash.rs')
| -rw-r--r-- | crates/atuin-ai/src/tui/slash.rs | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/crates/atuin-ai/src/tui/slash.rs b/crates/atuin-ai/src/tui/slash.rs new file mode 100644 index 00000000..7d5e6fa8 --- /dev/null +++ b/crates/atuin-ai/src/tui/slash.rs @@ -0,0 +1,79 @@ +#[derive(Debug, Clone)] +pub(crate) struct SlashCommand { + pub name: String, + pub description: String, +} + +impl SlashCommand { + pub fn new(name: &str, description: &str) -> Self { + Self { + name: name.to_string(), + description: description.to_string(), + } + } +} + +#[derive(Debug)] +pub(crate) struct SlashCommandRegistry { + commands: Vec<SlashCommand>, +} + +#[derive(Debug, Clone)] +pub(crate) struct SlashCommandSearchResult { + pub command: SlashCommand, + pub relevance: f32, + pub span: (usize, usize), +} + +impl SlashCommandRegistry { + pub fn new() -> Self { + Self { + commands: Vec::new(), + } + } + + pub fn register(&mut self, command: SlashCommand) { + self.commands.push(command); + } + + pub fn get_commands(&self) -> &[SlashCommand] { + &self.commands + } + + pub fn search_fuzzy(&self, query: &str) -> Vec<SlashCommandSearchResult> { + let query_lower = query.to_lowercase(); + + self.commands + .iter() + .filter_map(|command| { + let name_lower = command.name.to_lowercase(); + if let Some(start) = name_lower.find(&query_lower as &str) { + let end = start + query_lower.len(); + Some((command, start, end)) + } else { + None + } + }) + .map(|(command, start, end)| { + SlashCommandSearchResult { + command: command.clone(), + relevance: 1.0, // Simple relevance score for now + span: (start, end), + } + }) + .collect() + } +} + +impl Default for SlashCommandRegistry { + fn default() -> Self { + let mut registry = Self::new(); + registry.register(SlashCommand::new("help", "Show help information")); + registry.register(SlashCommand::new( + "new", + "Start a new conversation, archiving the current one", + )); + + registry + } +} |
