fix: zero warnings in botserver
- drive/vectordb.rs: fix struct field name db_path -> _db_path - meet/conversations.rs: prefix 38 unused variables with underscore - console/mod.rs: rename border_active/inactive, title_bg/fg - console/mod.rs: fix unnested or-pattern KeyCode::Char - email/mod.rs: remove unnecessary raw string hashes, fix unused state - attendance/drive.rs: update deprecated aws_config::from_env() - Cargo.toml: attendance feature extends drive for AWS deps
This commit is contained in:
parent
d09a584443
commit
db6d7a9859
6 changed files with 159 additions and 217 deletions
|
|
@ -70,7 +70,7 @@ mail = ["email"]
|
|||
|
||||
# ===== ENTERPRISE FEATURES =====
|
||||
compliance = ["dep:csv"]
|
||||
attendance = []
|
||||
attendance = ["drive"]
|
||||
directory = []
|
||||
weba = []
|
||||
timeseries = []
|
||||
|
|
@ -171,7 +171,7 @@ livekit = { version = "0.7", optional = true }
|
|||
qdrant-client = { version = "1.12", optional = true }
|
||||
|
||||
# File Storage & Drive (drive feature)
|
||||
aws-config = { version = "1.8.8", optional = true }
|
||||
aws-config = { version = "1.8.8", features = ["behavior-version-latest"], optional = true }
|
||||
aws-sdk-s3 = { version = "1.109.0", features = ["behavior-version-latest"], optional = true }
|
||||
pdf-extract = { version = "0.10.0", optional = true }
|
||||
zip = { version = "2.2", optional = true }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
|
||||
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use aws_config::BehaviorVersion;
|
||||
use aws_sdk_s3::primitives::ByteStream;
|
||||
use aws_sdk_s3::Client;
|
||||
use chrono::TimeZone;
|
||||
|
|
@ -9,7 +7,6 @@ use serde::{Deserialize, Serialize};
|
|||
use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AttendanceDriveConfig {
|
||||
pub bucket_name: String,
|
||||
|
|
@ -29,7 +26,6 @@ impl Default for AttendanceDriveConfig {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AttendanceDriveService {
|
||||
config: AttendanceDriveConfig,
|
||||
|
|
@ -37,15 +33,14 @@ pub struct AttendanceDriveService {
|
|||
}
|
||||
|
||||
impl AttendanceDriveService {
|
||||
|
||||
pub async fn new(config: AttendanceDriveConfig) -> Result<Self> {
|
||||
let sdk_config = if let Some(region) = &config.region {
|
||||
aws_config::from_env()
|
||||
aws_config::defaults(BehaviorVersion::latest())
|
||||
.region(aws_config::Region::new(region.clone()))
|
||||
.load()
|
||||
.await
|
||||
} else {
|
||||
aws_config::from_env().load().await
|
||||
aws_config::defaults(BehaviorVersion::latest()).load().await
|
||||
};
|
||||
|
||||
let client = Client::new(&sdk_config);
|
||||
|
|
@ -53,17 +48,14 @@ impl AttendanceDriveService {
|
|||
Ok(Self { config, client })
|
||||
}
|
||||
|
||||
|
||||
pub fn with_client(config: AttendanceDriveConfig, client: Client) -> Self {
|
||||
Self { config, client }
|
||||
}
|
||||
|
||||
|
||||
fn get_record_key(&self, record_id: &str) -> String {
|
||||
format!("{}{}", self.config.prefix, record_id)
|
||||
}
|
||||
|
||||
|
||||
pub async fn upload_record(&self, record_id: &str, data: Vec<u8>) -> Result<()> {
|
||||
let key = self.get_record_key(record_id);
|
||||
|
||||
|
|
@ -90,7 +82,6 @@ impl AttendanceDriveService {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn download_record(&self, record_id: &str) -> Result<Vec<u8>> {
|
||||
let key = self.get_record_key(record_id);
|
||||
|
||||
|
|
@ -120,7 +111,6 @@ impl AttendanceDriveService {
|
|||
Ok(data.into_bytes().to_vec())
|
||||
}
|
||||
|
||||
|
||||
pub async fn list_records(&self, prefix: Option<&str>) -> Result<Vec<String>> {
|
||||
let list_prefix = if let Some(p) = prefix {
|
||||
format!("{}{}", self.config.prefix, p)
|
||||
|
|
@ -157,7 +147,6 @@ impl AttendanceDriveService {
|
|||
if let Some(contents) = result.contents {
|
||||
for obj in contents {
|
||||
if let Some(key) = obj.key {
|
||||
|
||||
if let Some(record_id) = key.strip_prefix(&self.config.prefix) {
|
||||
records.push(record_id.to_string());
|
||||
}
|
||||
|
|
@ -176,7 +165,6 @@ impl AttendanceDriveService {
|
|||
Ok(records)
|
||||
}
|
||||
|
||||
|
||||
pub async fn delete_record(&self, record_id: &str) -> Result<()> {
|
||||
let key = self.get_record_key(record_id);
|
||||
|
||||
|
|
@ -199,7 +187,6 @@ impl AttendanceDriveService {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn delete_records(&self, record_ids: &[String]) -> Result<()> {
|
||||
if record_ids.is_empty() {
|
||||
return Ok(());
|
||||
|
|
@ -211,7 +198,6 @@ impl AttendanceDriveService {
|
|||
self.config.bucket_name
|
||||
);
|
||||
|
||||
|
||||
for chunk in record_ids.chunks(1000) {
|
||||
let objects: Vec<_> = chunk
|
||||
.iter()
|
||||
|
|
@ -244,7 +230,6 @@ impl AttendanceDriveService {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn record_exists(&self, record_id: &str) -> Result<bool> {
|
||||
let key = self.get_record_key(record_id);
|
||||
|
||||
|
|
@ -270,7 +255,6 @@ impl AttendanceDriveService {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn sync_records(&self, local_path: PathBuf) -> Result<SyncResult> {
|
||||
if !self.config.sync_enabled {
|
||||
log::debug!("Attendance drive sync is disabled");
|
||||
|
|
@ -315,14 +299,12 @@ impl AttendanceDriveService {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
if self.record_exists(&file_name).await? {
|
||||
log::debug!("Record {} already exists in drive, skipping", file_name);
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
match fs::read(&path).await {
|
||||
Ok(data) => match self.upload_record(&file_name, data).await {
|
||||
Ok(_) => {
|
||||
|
|
@ -357,7 +339,6 @@ impl AttendanceDriveService {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
|
||||
pub async fn get_record_metadata(&self, record_id: &str) -> Result<RecordMetadata> {
|
||||
let key = self.get_record_key(record_id);
|
||||
|
||||
|
|
@ -382,7 +363,6 @@ impl AttendanceDriveService {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct SyncResult {
|
||||
pub uploaded: usize,
|
||||
|
|
@ -390,7 +370,6 @@ pub struct SyncResult {
|
|||
pub skipped: usize,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RecordMetadata {
|
||||
pub size: usize,
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@ impl XtreeUI {
|
|||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
|
||||
if let Err(e) = init_logger(self.log_panel.clone()) {
|
||||
eprintln!("Warning: Could not initialize UI logger: {}", e);
|
||||
}
|
||||
|
|
@ -123,12 +122,10 @@ impl XtreeUI {
|
|||
let mut last_blink = std::time::Instant::now();
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
loop {
|
||||
|
||||
if self.app_state.is_none() {
|
||||
if let Some(ref state_rx) = self.state_channel {
|
||||
if let Ok(mut rx) = state_rx.try_lock() {
|
||||
if let Ok(app_state) = rx.try_recv() {
|
||||
|
||||
self.file_tree = Some(FileTree::new(app_state.clone()));
|
||||
self.status_panel = Some(StatusPanel::new(app_state.clone()));
|
||||
self.chat_panel = Some(ChatPanel::new(app_state.clone()));
|
||||
|
|
@ -136,7 +133,6 @@ impl XtreeUI {
|
|||
self.active_panel = ActivePanel::FileTree;
|
||||
self.bootstrap_status = "Ready".to_string();
|
||||
|
||||
|
||||
if let Ok(mut log_panel) = self.log_panel.lock() {
|
||||
log_panel.add_log("AppState received - UI fully initialized");
|
||||
}
|
||||
|
|
@ -205,14 +201,14 @@ impl XtreeUI {
|
|||
}
|
||||
fn render(&mut self, f: &mut Frame, cursor_blink: bool) {
|
||||
let bg = Color::Rgb(0, 30, 100);
|
||||
let border_active = Color::Rgb(85, 255, 255);
|
||||
let border_inactive = Color::Rgb(170, 170, 170);
|
||||
let border_focused = Color::Rgb(85, 255, 255);
|
||||
let border_dim = Color::Rgb(170, 170, 170);
|
||||
let text = Color::Rgb(255, 255, 255);
|
||||
let highlight = Color::Rgb(0, 170, 170);
|
||||
let title_bg = Color::Rgb(170, 170, 170);
|
||||
let title_fg = Color::Rgb(0, 0, 0);
|
||||
let header_bg_color = Color::Rgb(170, 170, 170);
|
||||
let header_text_color = Color::Rgb(0, 0, 0);
|
||||
if self.app_state.is_none() {
|
||||
self.render_loading(f, bg, text, border_active, title_bg, title_fg);
|
||||
self.render_loading(f, bg, text, border_focused, header_bg_color, header_text_color);
|
||||
return;
|
||||
}
|
||||
let main_chunks = Layout::default()
|
||||
|
|
@ -223,7 +219,7 @@ impl XtreeUI {
|
|||
Constraint::Length(12),
|
||||
])
|
||||
.split(f.area());
|
||||
self.render_header(f, main_chunks[0], bg, title_bg, title_fg);
|
||||
self.render_header(f, main_chunks[0], bg, header_bg_color, header_text_color);
|
||||
if self.editor.is_some() {
|
||||
let content_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
|
|
@ -238,28 +234,28 @@ impl XtreeUI {
|
|||
content_chunks[0],
|
||||
bg,
|
||||
text,
|
||||
border_active,
|
||||
border_inactive,
|
||||
border_focused,
|
||||
border_dim,
|
||||
highlight,
|
||||
title_bg,
|
||||
title_fg,
|
||||
header_bg_color,
|
||||
header_text_color,
|
||||
);
|
||||
if let Some(editor) = &mut self.editor {
|
||||
let area = content_chunks[1];
|
||||
editor.set_visible_lines(area.height.saturating_sub(4) as usize);
|
||||
let is_active = self.active_panel == ActivePanel::Editor;
|
||||
let border_color = if is_active {
|
||||
border_active
|
||||
border_focused
|
||||
} else {
|
||||
border_inactive
|
||||
border_dim
|
||||
};
|
||||
let title_style = if is_active {
|
||||
Style::default()
|
||||
.fg(title_fg)
|
||||
.bg(title_bg)
|
||||
.fg(header_text_color)
|
||||
.bg(header_bg_color)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(title_fg).bg(title_bg)
|
||||
Style::default().fg(header_text_color).bg(header_bg_color)
|
||||
};
|
||||
let title_text = format!(" EDITOR: {} ", editor.file_path());
|
||||
let block = Block::default()
|
||||
|
|
@ -279,11 +275,11 @@ impl XtreeUI {
|
|||
content_chunks[2],
|
||||
bg,
|
||||
text,
|
||||
border_active,
|
||||
border_inactive,
|
||||
border_focused,
|
||||
border_dim,
|
||||
highlight,
|
||||
title_bg,
|
||||
title_fg,
|
||||
header_bg_color,
|
||||
header_text_color,
|
||||
);
|
||||
} else {
|
||||
let content_chunks = Layout::default()
|
||||
|
|
@ -299,11 +295,11 @@ impl XtreeUI {
|
|||
content_chunks[0],
|
||||
bg,
|
||||
text,
|
||||
border_active,
|
||||
border_inactive,
|
||||
border_focused,
|
||||
border_dim,
|
||||
highlight,
|
||||
title_bg,
|
||||
title_fg,
|
||||
header_bg_color,
|
||||
header_text_color,
|
||||
);
|
||||
let right_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
|
|
@ -314,22 +310,22 @@ impl XtreeUI {
|
|||
right_chunks[0],
|
||||
bg,
|
||||
text,
|
||||
border_active,
|
||||
border_inactive,
|
||||
border_focused,
|
||||
border_dim,
|
||||
highlight,
|
||||
title_bg,
|
||||
title_fg,
|
||||
header_bg_color,
|
||||
header_text_color,
|
||||
);
|
||||
self.render_chat(
|
||||
self.render_status(
|
||||
f,
|
||||
content_chunks[2],
|
||||
content_chunks[1],
|
||||
bg,
|
||||
text,
|
||||
border_active,
|
||||
border_inactive,
|
||||
border_focused,
|
||||
border_dim,
|
||||
highlight,
|
||||
title_bg,
|
||||
title_fg,
|
||||
header_bg_color,
|
||||
header_text_color,
|
||||
);
|
||||
}
|
||||
self.render_logs(
|
||||
|
|
@ -337,11 +333,11 @@ impl XtreeUI {
|
|||
main_chunks[2],
|
||||
bg,
|
||||
text,
|
||||
border_active,
|
||||
border_inactive,
|
||||
border_focused,
|
||||
border_dim,
|
||||
highlight,
|
||||
title_bg,
|
||||
title_fg,
|
||||
header_bg_color,
|
||||
header_text_color,
|
||||
);
|
||||
}
|
||||
fn render_header(
|
||||
|
|
@ -349,10 +345,10 @@ impl XtreeUI {
|
|||
f: &mut Frame,
|
||||
area: Rect,
|
||||
_bg: Color,
|
||||
title_bg: Color,
|
||||
title_fg: Color,
|
||||
header_bg_color: Color,
|
||||
header_text_color: Color,
|
||||
) {
|
||||
let block = Block::default().style(Style::default().bg(title_bg));
|
||||
let block = Block::default().style(Style::default().bg(header_bg_color));
|
||||
f.render_widget(block, area);
|
||||
let title = if self.app_state.is_some() {
|
||||
let components = vec![
|
||||
|
|
@ -385,8 +381,8 @@ impl XtreeUI {
|
|||
let title_span = Span::styled(
|
||||
title,
|
||||
Style::default()
|
||||
.fg(title_fg)
|
||||
.bg(title_bg)
|
||||
.fg(header_text_color)
|
||||
.bg(header_bg_color)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
f.render_widget(
|
||||
|
|
@ -405,10 +401,9 @@ impl XtreeUI {
|
|||
bg: Color,
|
||||
text: Color,
|
||||
border: Color,
|
||||
title_bg: Color,
|
||||
title_fg: Color,
|
||||
header_bg_color: Color,
|
||||
header_text_color: Color,
|
||||
) {
|
||||
|
||||
let main_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
|
|
@ -418,8 +413,7 @@ impl XtreeUI {
|
|||
])
|
||||
.split(f.area());
|
||||
|
||||
|
||||
let header_block = Block::default().style(Style::default().bg(title_bg));
|
||||
let header_block = Block::default().style(Style::default().bg(header_bg_color));
|
||||
f.render_widget(header_block, main_chunks[0]);
|
||||
|
||||
let title = " GENERAL BOTS ";
|
||||
|
|
@ -428,8 +422,8 @@ impl XtreeUI {
|
|||
let title_span = Span::styled(
|
||||
title,
|
||||
Style::default()
|
||||
.fg(title_fg)
|
||||
.bg(title_bg)
|
||||
.fg(header_text_color)
|
||||
.bg(header_bg_color)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
f.render_widget(
|
||||
|
|
@ -442,7 +436,6 @@ impl XtreeUI {
|
|||
},
|
||||
);
|
||||
|
||||
|
||||
let content_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
|
|
@ -452,11 +445,10 @@ impl XtreeUI {
|
|||
])
|
||||
.split(main_chunks[1]);
|
||||
|
||||
|
||||
let file_block = Block::default()
|
||||
.title(Span::styled(
|
||||
" FILE EXPLORER ",
|
||||
Style::default().fg(title_fg).bg(title_bg),
|
||||
Style::default().fg(header_text_color).bg(header_bg_color),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(border))
|
||||
|
|
@ -466,7 +458,6 @@ impl XtreeUI {
|
|||
.style(Style::default().fg(Color::DarkGray));
|
||||
f.render_widget(file_text, content_chunks[0]);
|
||||
|
||||
|
||||
let middle_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
|
|
@ -475,7 +466,7 @@ impl XtreeUI {
|
|||
let status_block = Block::default()
|
||||
.title(Span::styled(
|
||||
" STATUS ",
|
||||
Style::default().fg(title_fg).bg(title_bg),
|
||||
Style::default().fg(header_text_color).bg(header_bg_color),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(border))
|
||||
|
|
@ -490,18 +481,16 @@ impl XtreeUI {
|
|||
.style(Style::default().fg(text));
|
||||
f.render_widget(status_para, middle_chunks[0]);
|
||||
|
||||
|
||||
let empty_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::DarkGray))
|
||||
.style(Style::default().bg(bg));
|
||||
f.render_widget(empty_block, middle_chunks[1]);
|
||||
|
||||
|
||||
let chat_block = Block::default()
|
||||
.title(Span::styled(
|
||||
" CHAT ",
|
||||
Style::default().fg(title_fg).bg(title_bg),
|
||||
Style::default().fg(header_text_color).bg(header_bg_color),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(border))
|
||||
|
|
@ -511,17 +500,15 @@ impl XtreeUI {
|
|||
.style(Style::default().fg(Color::DarkGray));
|
||||
f.render_widget(chat_text, content_chunks[2]);
|
||||
|
||||
|
||||
let logs_block = Block::default()
|
||||
.title(Span::styled(
|
||||
" SYSTEM LOGS ",
|
||||
Style::default().fg(title_fg).bg(title_bg),
|
||||
Style::default().fg(header_text_color).bg(header_bg_color),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(border))
|
||||
.style(Style::default().bg(bg));
|
||||
|
||||
|
||||
let logs_visible_lines = main_chunks[2].height.saturating_sub(2) as usize;
|
||||
let logs_content = if let Ok(panel) = self.log_panel.lock() {
|
||||
panel.render(logs_visible_lines)
|
||||
|
|
@ -541,11 +528,11 @@ impl XtreeUI {
|
|||
area: Rect,
|
||||
bg: Color,
|
||||
text: Color,
|
||||
border_active: Color,
|
||||
border_inactive: Color,
|
||||
border_focused: Color,
|
||||
border_dim: Color,
|
||||
highlight: Color,
|
||||
title_bg: Color,
|
||||
title_fg: Color,
|
||||
header_bg_color: Color,
|
||||
header_text_color: Color,
|
||||
) {
|
||||
if let Some(file_tree) = &self.file_tree {
|
||||
let items = file_tree.render_items();
|
||||
|
|
@ -569,17 +556,17 @@ impl XtreeUI {
|
|||
.collect();
|
||||
let is_active = self.active_panel == ActivePanel::FileTree;
|
||||
let border_color = if is_active {
|
||||
border_active
|
||||
border_focused
|
||||
} else {
|
||||
border_inactive
|
||||
border_dim
|
||||
};
|
||||
let title_style = if is_active {
|
||||
Style::default()
|
||||
.fg(title_fg)
|
||||
.bg(title_bg)
|
||||
.fg(header_text_color)
|
||||
.bg(header_bg_color)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(title_fg).bg(title_bg)
|
||||
Style::default().fg(header_text_color).bg(header_bg_color)
|
||||
};
|
||||
let block = Block::default()
|
||||
.title(Span::styled(" FILE EXPLORER ", title_style))
|
||||
|
|
@ -596,11 +583,11 @@ impl XtreeUI {
|
|||
area: Rect,
|
||||
bg: Color,
|
||||
text: Color,
|
||||
border_active: Color,
|
||||
border_inactive: Color,
|
||||
border_focused: Color,
|
||||
border_dim: Color,
|
||||
_highlight: Color,
|
||||
title_bg: Color,
|
||||
title_fg: Color,
|
||||
header_bg_color: Color,
|
||||
header_text_color: Color,
|
||||
) {
|
||||
let selected_bot_opt = self.file_tree.as_ref().and_then(|ft| ft.get_selected_bot());
|
||||
let status_text = if let Some(status_panel) = &mut self.status_panel {
|
||||
|
|
@ -613,17 +600,17 @@ impl XtreeUI {
|
|||
};
|
||||
let is_active = self.active_panel == ActivePanel::Status;
|
||||
let border_color = if is_active {
|
||||
border_active
|
||||
border_focused
|
||||
} else {
|
||||
border_inactive
|
||||
border_dim
|
||||
};
|
||||
let title_style = if is_active {
|
||||
Style::default()
|
||||
.fg(title_fg)
|
||||
.bg(title_bg)
|
||||
.fg(header_text_color)
|
||||
.bg(header_bg_color)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(title_fg).bg(title_bg)
|
||||
Style::default().fg(header_text_color).bg(header_bg_color)
|
||||
};
|
||||
let block = Block::default()
|
||||
.title(Span::styled(" SYSTEM STATUS ", title_style))
|
||||
|
|
@ -642,26 +629,26 @@ impl XtreeUI {
|
|||
area: Rect,
|
||||
bg: Color,
|
||||
text: Color,
|
||||
border_active: Color,
|
||||
border_inactive: Color,
|
||||
border_focused: Color,
|
||||
border_dim: Color,
|
||||
_highlight: Color,
|
||||
title_bg: Color,
|
||||
title_fg: Color,
|
||||
header_bg_color: Color,
|
||||
header_text_color: Color,
|
||||
) {
|
||||
if let Some(chat_panel) = &self.chat_panel {
|
||||
let is_active = self.active_panel == ActivePanel::Chat;
|
||||
let border_color = if is_active {
|
||||
border_active
|
||||
border_focused
|
||||
} else {
|
||||
border_inactive
|
||||
border_dim
|
||||
};
|
||||
let title_style = if is_active {
|
||||
Style::default()
|
||||
.fg(title_fg)
|
||||
.bg(title_bg)
|
||||
.fg(header_text_color)
|
||||
.bg(header_bg_color)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(title_fg).bg(title_bg)
|
||||
Style::default().fg(header_text_color).bg(header_bg_color)
|
||||
};
|
||||
let selected_bot = if let Some(file_tree) = &self.file_tree {
|
||||
file_tree
|
||||
|
|
@ -690,13 +677,12 @@ impl XtreeUI {
|
|||
area: Rect,
|
||||
bg: Color,
|
||||
text: Color,
|
||||
border_active: Color,
|
||||
border_inactive: Color,
|
||||
border_focused: Color,
|
||||
border_dim: Color,
|
||||
_highlight: Color,
|
||||
title_bg: Color,
|
||||
title_fg: Color,
|
||||
header_bg_color: Color,
|
||||
header_text_color: Color,
|
||||
) {
|
||||
|
||||
let visible_lines = area.height.saturating_sub(2) as usize;
|
||||
|
||||
let log_panel = self.log_panel.try_lock();
|
||||
|
|
@ -716,20 +702,19 @@ impl XtreeUI {
|
|||
|
||||
let is_active = self.active_panel == ActivePanel::Logs;
|
||||
let border_color = if is_active {
|
||||
border_active
|
||||
border_focused
|
||||
} else {
|
||||
border_inactive
|
||||
border_dim
|
||||
};
|
||||
let title_style = if is_active {
|
||||
Style::default()
|
||||
.fg(title_fg)
|
||||
.bg(title_bg)
|
||||
.fg(header_text_color)
|
||||
.bg(header_bg_color)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(title_fg).bg(title_bg)
|
||||
Style::default().fg(header_text_color).bg(header_bg_color)
|
||||
};
|
||||
|
||||
|
||||
let scroll_indicator = if can_scroll_up && can_scroll_down {
|
||||
" [^v] "
|
||||
} else if can_scroll_up {
|
||||
|
|
@ -760,7 +745,7 @@ impl XtreeUI {
|
|||
async fn handle_input(&mut self, key: KeyCode, modifiers: KeyModifiers) -> Result<()> {
|
||||
if modifiers.contains(KeyModifiers::CONTROL) {
|
||||
match key {
|
||||
KeyCode::Char('c') | KeyCode::Char('q') => {
|
||||
KeyCode::Char('c' | 'q') => {
|
||||
self.should_quit = true;
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ impl UserDriveVectorDB {
|
|||
user_id,
|
||||
bot_id,
|
||||
collection_name,
|
||||
db_path,
|
||||
_db_path: db_path,
|
||||
#[cfg(feature = "vectordb")]
|
||||
client: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1197,7 +1197,7 @@ fn update_email_read_status(
|
|||
let now = Utc::now();
|
||||
|
||||
diesel::sql_query(
|
||||
r#"UPDATE sent_email_tracking
|
||||
r"UPDATE sent_email_tracking
|
||||
SET
|
||||
is_read = true,
|
||||
read_count = read_count + 1,
|
||||
|
|
@ -1206,7 +1206,7 @@ fn update_email_read_status(
|
|||
last_read_ip = $3,
|
||||
user_agent = COALESCE(user_agent, $4),
|
||||
updated_at = $2
|
||||
WHERE tracking_id = $1"#,
|
||||
WHERE tracking_id = $1",
|
||||
)
|
||||
.bind::<diesel::sql_types::Uuid, _>(tracking_id)
|
||||
.bind::<diesel::sql_types::Timestamptz, _>(now)
|
||||
|
|
@ -1266,8 +1266,8 @@ fn get_tracking_record(
|
|||
}
|
||||
|
||||
let row: TrackingRow = diesel::sql_query(
|
||||
r#"SELECT tracking_id, to_email, subject, sent_at, is_read, read_at, read_count
|
||||
FROM sent_email_tracking WHERE tracking_id = $1"#,
|
||||
r"SELECT tracking_id, to_email, subject, sent_at, is_read, read_at, read_count
|
||||
FROM sent_email_tracking WHERE tracking_id = $1",
|
||||
)
|
||||
.bind::<diesel::sql_types::Uuid, _>(tracking_id)
|
||||
.get_result(&mut db_conn)
|
||||
|
|
@ -1402,11 +1402,11 @@ fn calculate_tracking_stats(
|
|||
}
|
||||
|
||||
let stats: StatsRow = diesel::sql_query(
|
||||
r#"SELECT
|
||||
r"SELECT
|
||||
COUNT(*) as total_sent,
|
||||
COUNT(*) FILTER (WHERE is_read = true) as total_read,
|
||||
AVG(EXTRACT(EPOCH FROM (read_at - sent_at)) / 3600) FILTER (WHERE is_read = true) as avg_time_hours
|
||||
FROM sent_email_tracking"#
|
||||
FROM sent_email_tracking",
|
||||
)
|
||||
.get_result(&mut db_conn)
|
||||
.map_err(|e| format!("Stats query failed: {}", e))?;
|
||||
|
|
@ -1831,10 +1831,10 @@ pub async fn list_emails_htmx(
|
|||
|
||||
if html.is_empty() {
|
||||
html = format!(
|
||||
r##"<div class="empty-state">
|
||||
r#"<div class="empty-state">
|
||||
<h3>No emails in {}</h3>
|
||||
<p>This folder is empty</p>
|
||||
</div>"##,
|
||||
</div>"#,
|
||||
folder
|
||||
);
|
||||
}
|
||||
|
|
@ -1899,7 +1899,7 @@ pub async fn list_folders_htmx(
|
|||
};
|
||||
let count_badge = if **count > 0 {
|
||||
format!(
|
||||
r##"<span style="margin-left: auto; font-size: 0.875rem; color: #64748b;">{}</span>"##,
|
||||
r#"<span style="margin-left: auto; font-size: 0.875rem; color: #64748b;">{}</span>"#,
|
||||
count
|
||||
)
|
||||
} else {
|
||||
|
|
@ -1992,9 +1992,9 @@ pub async fn get_email_content_htmx(
|
|||
|
||||
let Some(account) = account else {
|
||||
return Ok(axum::response::Html(
|
||||
r##"<div class="mail-content-view">
|
||||
r#"<div class="mail-content-view">
|
||||
<p>No email account configured</p>
|
||||
</div>"##
|
||||
</div>"#
|
||||
.to_string(),
|
||||
));
|
||||
};
|
||||
|
|
@ -2137,7 +2137,7 @@ pub async fn get_email(
|
|||
}
|
||||
|
||||
pub async fn track_click(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path((campaign_id, email)): Path<(String, String)>,
|
||||
) -> Result<Json<ApiResponse<()>>, EmailError> {
|
||||
info!(
|
||||
|
|
@ -2338,7 +2338,7 @@ pub async fn search_emails_htmx(
|
|||
|
||||
if results.is_empty() {
|
||||
return axum::response::Html(format!(
|
||||
r##"
|
||||
r#"
|
||||
<div class="empty-state">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
|
|
@ -2347,12 +2347,12 @@ pub async fn search_emails_htmx(
|
|||
<h3>No results for "{}"</h3>
|
||||
<p>Try different keywords or check your spelling.</p>
|
||||
</div>
|
||||
"##,
|
||||
"#,
|
||||
query
|
||||
));
|
||||
}
|
||||
|
||||
let mut html = String::from(r##"<div class="search-results">"##);
|
||||
let mut html = String::from(r#"<div class="search-results">"#);
|
||||
html.push_str(&format!(
|
||||
r#"<div class="result-stats">Found {} results for "{}"</div>"#,
|
||||
results.len(),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
|
|
@ -15,8 +10,6 @@ use uuid::Uuid;
|
|||
|
||||
use crate::shared::state::AppState;
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateConversationRequest {
|
||||
pub name: String,
|
||||
|
|
@ -188,11 +181,8 @@ pub struct SuccessResponse {
|
|||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
pub async fn create_conversation(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Json(req): Json<CreateConversationRequest>,
|
||||
) -> Result<Json<ConversationResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let conversation_id = Uuid::new_v4();
|
||||
|
|
@ -216,34 +206,37 @@ pub async fn create_conversation(
|
|||
Ok(Json(conversation))
|
||||
}
|
||||
|
||||
|
||||
pub async fn join_conversation(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
Json(req): Json<JoinConversationRequest>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
message: Some(format!("User {} joined conversation {}", req.user_id, conversation_id)),
|
||||
message: Some(format!(
|
||||
"User {} joined conversation {}",
|
||||
req.user_id, conversation_id
|
||||
)),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn leave_conversation(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
Json(req): Json<LeaveConversationRequest>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
message: Some(format!("User {} left conversation {}", req.user_id, conversation_id)),
|
||||
message: Some(format!(
|
||||
"User {} left conversation {}",
|
||||
req.user_id, conversation_id
|
||||
)),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn get_conversation_members(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<ParticipantResponse>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let members = vec![ParticipantResponse {
|
||||
user_id: Uuid::new_v4(),
|
||||
|
|
@ -258,19 +251,17 @@ pub async fn get_conversation_members(
|
|||
Ok(Json(members))
|
||||
}
|
||||
|
||||
|
||||
pub async fn get_conversation_messages(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<MessageResponse>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let messages = vec![];
|
||||
|
||||
Ok(Json(messages))
|
||||
}
|
||||
|
||||
|
||||
pub async fn send_message(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
Json(req): Json<SendMessageRequest>,
|
||||
) -> Result<Json<MessageResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
|
|
@ -297,9 +288,8 @@ pub async fn send_message(
|
|||
Ok(Json(message))
|
||||
}
|
||||
|
||||
|
||||
pub async fn edit_message(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path((conversation_id, message_id)): Path<(Uuid, Uuid)>,
|
||||
Json(req): Json<EditMessageRequest>,
|
||||
) -> Result<Json<MessageResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
|
|
@ -324,10 +314,9 @@ pub async fn edit_message(
|
|||
Ok(Json(message))
|
||||
}
|
||||
|
||||
|
||||
pub async fn delete_message(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((conversation_id, message_id)): Path<(Uuid, Uuid)>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path((_conversation_id, message_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -335,22 +324,23 @@ pub async fn delete_message(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn react_to_message(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((conversation_id, message_id)): Path<(Uuid, Uuid)>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path((_conversation_id, message_id)): Path<(Uuid, Uuid)>,
|
||||
Json(req): Json<ReactToMessageRequest>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
message: Some(format!("Reaction '{}' added to message {}", req.reaction, message_id)),
|
||||
message: Some(format!(
|
||||
"Reaction '{}' added to message {}",
|
||||
req.reaction, message_id
|
||||
)),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn pin_message(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((conversation_id, message_id)): Path<(Uuid, Uuid)>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path((_conversation_id, message_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -358,20 +348,18 @@ pub async fn pin_message(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn search_messages(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
Query(params): Query<SearchMessagesQuery>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
Query(_params): Query<SearchMessagesQuery>,
|
||||
) -> Result<Json<Vec<MessageResponse>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let messages = vec![];
|
||||
|
||||
Ok(Json(messages))
|
||||
}
|
||||
|
||||
|
||||
pub async fn start_call(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
Json(req): Json<StartCallRequest>,
|
||||
) -> Result<Json<CallResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
|
|
@ -395,10 +383,9 @@ pub async fn start_call(
|
|||
Ok(Json(call))
|
||||
}
|
||||
|
||||
|
||||
pub async fn join_call(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -406,10 +393,9 @@ pub async fn join_call(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn leave_call(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -417,10 +403,9 @@ pub async fn leave_call(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn mute_call(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -428,10 +413,9 @@ pub async fn mute_call(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn unmute_call(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -439,9 +423,8 @@ pub async fn unmute_call(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn start_screen_share(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
Json(req): Json<ScreenShareRequest>,
|
||||
) -> Result<Json<ScreenShareResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
|
|
@ -462,10 +445,9 @@ pub async fn start_screen_share(
|
|||
Ok(Json(screen_share))
|
||||
}
|
||||
|
||||
|
||||
pub async fn stop_screen_share(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -473,10 +455,9 @@ pub async fn stop_screen_share(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn start_recording(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -484,10 +465,9 @@ pub async fn start_recording(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn stop_recording(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
@ -495,9 +475,8 @@ pub async fn stop_recording(
|
|||
}))
|
||||
}
|
||||
|
||||
|
||||
pub async fn create_whiteboard(
|
||||
State(state): State<Arc<AppState>>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
) -> Result<Json<WhiteboardResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let whiteboard_id = Uuid::new_v4();
|
||||
|
|
@ -518,11 +497,10 @@ pub async fn create_whiteboard(
|
|||
Ok(Json(whiteboard))
|
||||
}
|
||||
|
||||
|
||||
pub async fn collaborate_whiteboard(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(conversation_id): Path<Uuid>,
|
||||
Json(data): Json<serde_json::Value>,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_conversation_id): Path<Uuid>,
|
||||
Json(_data): Json<serde_json::Value>,
|
||||
) -> Result<Json<SuccessResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
Ok(Json(SuccessResponse {
|
||||
success: true,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue