- Renamed `execute_compact_prompt` to `compact_prompt_for_bots` and simplified logic - Removed redundant comments and empty lines in test files - Consolidated prompt compaction threshold handling - Cleaned up UI logging implementation by removing unnecessary whitespace - Improved code organization in ui_tree module The changes focus on code quality improvements, removing clutter, and making the prompt compaction logic more straightforward. Test files were cleaned up to be more concise.
227 lines
5.8 KiB
Rust
227 lines
5.8 KiB
Rust
use color_eyre::Result;
|
|
use std::sync::Arc;
|
|
use crate::shared::state::AppState;
|
|
#[derive(Debug, Clone)]
|
|
pub enum TreeNode {
|
|
Bucket { name: String },
|
|
Folder { bucket: String, path: String },
|
|
File { bucket: String, path: String },
|
|
}
|
|
pub struct FileTree {
|
|
app_state: Arc<AppState>,
|
|
items: Vec<(String, TreeNode)>,
|
|
selected: usize,
|
|
current_bucket: Option<String>,
|
|
current_path: Vec<String>,
|
|
}
|
|
impl FileTree {
|
|
pub fn new(app_state: Arc<AppState>) -> Self {
|
|
Self {
|
|
app_state,
|
|
items: Vec::new(),
|
|
selected: 0,
|
|
current_bucket: None,
|
|
current_path: Vec::new(),
|
|
}
|
|
}
|
|
pub async fn load_root(&mut self) -> Result<()> {
|
|
self.items.clear();
|
|
self.current_bucket = None;
|
|
self.current_path.clear();
|
|
if let Some(drive) = &self.app_state.drive {
|
|
let result = drive.list_buckets().send().await;
|
|
match result {
|
|
Ok(response) => {
|
|
let buckets = response.buckets();
|
|
for bucket in buckets {
|
|
if let Some(name) = bucket.name() {
|
|
let icon = if name.ends_with(".gbai") { "🤖" } else { "📦" };
|
|
let display = format!("{} {}", icon, name);
|
|
self.items.push((display, TreeNode::Bucket { name: name.to_string() }));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
self.items.push((format!("✗ Error: {}", e), TreeNode::Bucket { name: String::new() }));
|
|
}
|
|
}
|
|
} else {
|
|
self.items.push(("✗ Drive not connected".to_string(), TreeNode::Bucket { name: String::new() }));
|
|
}
|
|
if self.items.is_empty() {
|
|
self.items.push(("(no buckets found)".to_string(), TreeNode::Bucket { name: String::new() }));
|
|
}
|
|
self.selected = 0;
|
|
Ok(())
|
|
}
|
|
pub async fn enter_bucket(&mut self, bucket: String) -> Result<()> {
|
|
self.current_bucket = Some(bucket.clone());
|
|
self.current_path.clear();
|
|
self.load_bucket_contents(&bucket, "").await
|
|
}
|
|
pub async fn enter_folder(&mut self, bucket: String, path: String) -> Result<()> {
|
|
self.current_bucket = Some(bucket.clone());
|
|
let parts: Vec<&str> = path.trim_matches('/').split('/').filter(|s| !s.is_empty()).collect();
|
|
self.current_path = parts.iter().map(|s| s.to_string()).collect();
|
|
self.load_bucket_contents(&bucket, &path).await
|
|
}
|
|
pub fn go_up(&mut self) -> bool {
|
|
if self.current_path.is_empty() {
|
|
if self.current_bucket.is_some() {
|
|
self.current_bucket = None;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
self.current_path.pop();
|
|
true
|
|
}
|
|
pub async fn refresh_current(&mut self) -> Result<()> {
|
|
if let Some(bucket) = &self.current_bucket.clone() {
|
|
let path = self.current_path.join("/");
|
|
self.load_bucket_contents(bucket, &path).await
|
|
} else {
|
|
self.load_root().await
|
|
}
|
|
}
|
|
async fn load_bucket_contents(&mut self, bucket: &str, prefix: &str) -> Result<()> {
|
|
self.items.clear();
|
|
self.items.push(("⬆️ .. (go back)".to_string(), TreeNode::Folder {
|
|
bucket: bucket.to_string(),
|
|
path: "..".to_string(),
|
|
}));
|
|
if let Some(drive) = &self.app_state.drive {
|
|
let normalized_prefix = if prefix.is_empty() {
|
|
String::new()
|
|
} else if prefix.ends_with('/') {
|
|
prefix.to_string()
|
|
} else {
|
|
format!("{}/", prefix)
|
|
};
|
|
let mut continuation_token = None;
|
|
let mut all_keys = Vec::new();
|
|
loop {
|
|
let mut request = drive.list_objects_v2().bucket(bucket);
|
|
if !normalized_prefix.is_empty() {
|
|
request = request.prefix(&normalized_prefix);
|
|
}
|
|
if let Some(token) = continuation_token {
|
|
request = request.continuation_token(token);
|
|
}
|
|
let result = request.send().await?;
|
|
for obj in result.contents() {
|
|
if let Some(key) = obj.key() {
|
|
all_keys.push(key.to_string());
|
|
}
|
|
}
|
|
if !result.is_truncated.unwrap_or(false) {
|
|
break;
|
|
}
|
|
continuation_token = result.next_continuation_token;
|
|
}
|
|
let mut folders = std::collections::HashSet::new();
|
|
let mut files = Vec::new();
|
|
for key in all_keys {
|
|
if key == normalized_prefix {
|
|
continue;
|
|
}
|
|
let relative = if !normalized_prefix.is_empty() && key.starts_with(&normalized_prefix) {
|
|
&key[normalized_prefix.len()..]
|
|
} else {
|
|
&key
|
|
};
|
|
if relative.is_empty() {
|
|
continue;
|
|
}
|
|
if let Some(slash_pos) = relative.find('/') {
|
|
let folder_name = &relative[..slash_pos];
|
|
if !folder_name.is_empty() {
|
|
folders.insert(folder_name.to_string());
|
|
}
|
|
} else {
|
|
files.push((relative.to_string(), key.clone()));
|
|
}
|
|
}
|
|
let mut folder_vec: Vec<String> = folders.into_iter().collect();
|
|
folder_vec.sort();
|
|
for folder_name in folder_vec {
|
|
let full_path = if normalized_prefix.is_empty() {
|
|
folder_name.clone()
|
|
} else {
|
|
format!("{}{}", normalized_prefix, folder_name)
|
|
};
|
|
let display = format!("📁 {}/", folder_name);
|
|
self.items.push((display, TreeNode::Folder {
|
|
bucket: bucket.to_string(),
|
|
path: full_path,
|
|
}));
|
|
}
|
|
files.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
for (name, full_path) in files {
|
|
let icon = if name.ends_with(".bas") {
|
|
"⚙️"
|
|
} else if name.ends_with(".ast") {
|
|
"🔧"
|
|
} else if name.ends_with(".csv") {
|
|
"📊"
|
|
} else if name.ends_with(".gbkb") {
|
|
"📚"
|
|
} else if name.ends_with(".json") {
|
|
"🔖"
|
|
} else {
|
|
"📄"
|
|
};
|
|
let display = format!("{} {}", icon, name);
|
|
self.items.push((display, TreeNode::File {
|
|
bucket: bucket.to_string(),
|
|
path: full_path,
|
|
}));
|
|
}
|
|
}
|
|
if self.items.len() == 1 {
|
|
self.items.push(("(empty folder)".to_string(), TreeNode::Folder {
|
|
bucket: bucket.to_string(),
|
|
path: String::new(),
|
|
}));
|
|
}
|
|
self.selected = 0;
|
|
Ok(())
|
|
}
|
|
pub fn render_items(&self) -> &[(String, TreeNode)] {
|
|
&self.items
|
|
}
|
|
pub fn selected_index(&self) -> usize {
|
|
self.selected
|
|
}
|
|
pub fn get_selected_node(&self) -> Option<&TreeNode> {
|
|
self.items.get(self.selected).map(|(_, node)| node)
|
|
}
|
|
pub fn get_selected_bot(&self) -> Option<String> {
|
|
if let Some(bucket) = &self.current_bucket {
|
|
if bucket.ends_with(".gbai") {
|
|
return Some(bucket.trim_end_matches(".gbai").to_string());
|
|
}
|
|
}
|
|
if let Some((_, node)) = self.items.get(self.selected) {
|
|
match node {
|
|
TreeNode::Bucket { name } => {
|
|
if name.ends_with(".gbai") {
|
|
return Some(name.trim_end_matches(".gbai").to_string());
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
pub fn move_up(&mut self) {
|
|
if self.selected > 0 {
|
|
self.selected -= 1;
|
|
}
|
|
}
|
|
pub fn move_down(&mut self) {
|
|
if self.selected < self.items.len().saturating_sub(1) {
|
|
self.selected += 1;
|
|
}
|
|
}
|
|
}
|