248 lines · 6.5 KB · Rust
RawDownload
//! AI Chat functionality with browser tool integration use anyhow::Result;use chrono::{DateTime, Utc};use serde::{Deserialize, Serialize}; use crate::browser::Page;use crate::storage::{Bookmark, HistoryEntry}; /// A chat message#[derive(Debug, Clone, Serialize, Deserialize)]pub struct ChatMessage {    pub role: Role,    pub content: String,    pub timestamp: DateTime<Utc>,    #[serde(skip_serializing_if = "Option::is_none")]    pub tool_calls: Option<Vec<ToolCall>>,} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]#[serde(rename_all = "lowercase")]pub enum Role {    System,    User,    Assistant,    Tool,} /// Tool call from AI#[derive(Debug, Clone, Serialize, Deserialize)]pub struct ToolCall {    pub name: String,    pub arguments: serde_json::Value,} /// Available browser tools for AI#[derive(Debug, Clone, Serialize, Deserialize)]#[serde(tag = "type")]pub enum BrowserTool {    GetCurrentPage,    GetLinks,    GetBookmarks,    GetHistory,    Navigate { url: String },    Search { query: String },    AddBookmark { url: String, title: String },} impl ChatMessage {    pub fn new_user(content: String) -> Self {        Self {            role: Role::User,            content,            timestamp: Utc::now(),            tool_calls: None,        }    }     pub fn new_assistant(content: String) -> Self {        Self {            role: Role::Assistant,            content,            timestamp: Utc::now(),            tool_calls: None,        }    }     pub fn new_system(content: String) -> Self {        Self {            role: Role::System,            content,            timestamp: Utc::now(),            tool_calls: None,        }    }} /// Chat sessionpub struct ChatSession {    pub messages: Vec<ChatMessage>,    pub model: String,    pub available_models: Vec<String>,} impl ChatSession {    pub fn new(model: String, available_models: Vec<String>) -> Self {        let mut session = Self {            messages: Vec::new(),            model,            available_models,        };         // Add system message with browser tools        session.add_system_message();         // Add welcome message from assistant        session.messages.push(ChatMessage::new_assistant(            "Hey! I'm your browsing assistant. I can help you:\n\n\             • Search the web (I have Brave Search!)\n\             • Summarize pages you're reading\n\             • Find links and navigate\n\             • Answer questions about content\n\n\             What would you like to explore today?".to_string()        ));         session    }     fn add_system_message(&mut self) {        let system_prompt = r#"You are Azul Assistant, an AI helper integrated into the Azul terminal web browser. You have access to the following browser tools:- get_current_page: Get the currently displayed page content and metadata- get_links: Get all links from the current page- get_bookmarks: Get user's bookmarks- get_history: Get browsing history- navigate: Navigate to a URL- search: Perform a web search- add_bookmark: Bookmark the current page When the user asks about the current page, links, or wants to navigate, use these tools.Be helpful, concise, and focused on assisting with web browsing tasks."#;         self.messages.push(ChatMessage::new_system(system_prompt.to_string()));    }     pub fn add_user_message(&mut self, content: String) {        self.messages.push(ChatMessage::new_user(content));    }     pub fn add_assistant_message(&mut self, content: String) {        self.messages.push(ChatMessage::new_assistant(content));    }     pub fn set_model(&mut self, model: String) {        self.model = model;    }     pub fn clear(&mut self) {        self.messages.clear();        self.add_system_message();    }} /// Browser context for AI tools#[derive(Debug, Clone)]pub struct BrowserContext {    pub current_page: Option<Page>,    pub bookmarks: Vec<Bookmark>,    pub history: Vec<HistoryEntry>,} impl BrowserContext {    pub fn new() -> Self {        Self {            current_page: None,            bookmarks: Vec::new(),            history: Vec::new(),        }    }     /// Convert to a JSON string for AI context    pub fn to_json(&self) -> Result<String> {        #[derive(Serialize)]        struct Context {            current_page: Option<PageSummary>,            bookmarks_count: usize,            history_count: usize,        }         #[derive(Serialize)]        struct PageSummary {            title: String,            url: String,            links_count: usize,            content_preview: String,        }         let current_page = self.current_page.as_ref().map(|p| PageSummary {            title: p.title.clone(),            url: p.url.clone(),            links_count: p.links.len(),            content_preview: p.content_lines                .iter()                .take(10)                .cloned()                .collect::<Vec<_>>()                .join("\n"),        });         let ctx = Context {            current_page,            bookmarks_count: self.bookmarks.len(),            history_count: self.history.len(),        };         Ok(serde_json::to_string_pretty(&ctx)?)    }     /// Get links as formatted text    pub fn get_links_text(&self) -> String {        if let Some(page) = &self.current_page {            page.links                .iter()                .take(20)                .enumerate()                .map(|(i, link)| format!("{}. {} -> {}", i + 1, link.text, link.url))                .collect::<Vec<_>>()                .join("\n")        } else {            "No page loaded".to_string()        }    }     /// Get bookmarks as formatted text    pub fn get_bookmarks_text(&self) -> String {        if self.bookmarks.is_empty() {            "No bookmarks".to_string()        } else {            self.bookmarks                .iter()                .take(20)                .map(|b| format!("{} - {}", b.title, b.url))                .collect::<Vec<_>>()                .join("\n")        }    }     /// Get history as formatted text    pub fn get_history_text(&self) -> String {        if self.history.is_empty() {            "No history".to_string()        } else {            self.history                .iter()                .take(20)                .map(|h| format!("{} - {}", h.title, h.url))                .collect::<Vec<_>>()                .join("\n")        }    }} impl Default for BrowserContext {    fn default() -> Self {        Self::new()    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t