Fix cursor focus and selection in input box, add AWS env var warning box, and remove AWS Bedrock login banner
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use chrono::{DateTime, Local};
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use ordered_float::OrderedFloat;
|
||||
use galaxy_core::command::ExitCode;
|
||||
use ordered_float::OrderedFloat;
|
||||
use warpui::elements::{
|
||||
ConstrainedBox, Container, CrossAxisAlignment, Flex, Highlight, Icon, ParentElement, Text,
|
||||
};
|
||||
|
||||
@@ -75,7 +75,7 @@ impl SyncDataSource for ConversationDataSource {
|
||||
// assign higher values to more recently updated conversations. This ensures
|
||||
// recency acts as a tiebreaker when fuzzy scores are similar.
|
||||
let mut all_conversations = all_conversations;
|
||||
all_conversations.sort_by(|a, b| a.last_updated.cmp(&b.last_updated));
|
||||
all_conversations.sort_by_key(|a| a.last_updated);
|
||||
let total_conversations = all_conversations.len();
|
||||
|
||||
let mut results: Vec<QueryResult<Self::Action>> = if query_text.is_empty() {
|
||||
|
||||
@@ -189,11 +189,7 @@ async fn fuzzy_match_files_zero_state(
|
||||
|
||||
if is_git_changed || is_recently_opened {
|
||||
let rank = recency_index.get(&item.path).copied().unwrap_or(0);
|
||||
let recency_bonus = if max_recency > 0 {
|
||||
(30 * rank / max_recency) as i64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let recency_bonus = (30 * rank).checked_div(max_recency).unwrap_or(0) as i64;
|
||||
let base_score = if is_git_changed { 10000 } else { 0 };
|
||||
let match_result = FuzzyMatchResult {
|
||||
score: base_score + recency_bonus,
|
||||
@@ -255,11 +251,7 @@ async fn fuzzy_match_files_query(
|
||||
|
||||
// Add a recency bonus, capped at 30.
|
||||
let rank = recency_index.get(&item.path).copied().unwrap_or(0);
|
||||
let recency_bonus = if max_recency > 0 {
|
||||
(30 * rank / max_recency) as i64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let recency_bonus = (30 * rank).checked_div(max_recency).unwrap_or(0) as i64;
|
||||
|
||||
match_result.score += recency_bonus;
|
||||
|
||||
|
||||
@@ -3,18 +3,20 @@ use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use repo_metadata::repositories::DetectedRepositories;
|
||||
use repo_metadata::RepoMetadataModel;
|
||||
use tempfile::tempdir;
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use galaxyui::elements::Empty;
|
||||
use galaxyui::platform::WindowStyle;
|
||||
use galaxyui::r#async::block_on;
|
||||
use galaxyui::windowing::WindowManager;
|
||||
use galaxyui::{App, AppContext, Element, Entity, SingletonEntity, TypedActionView, View};
|
||||
use repo_metadata::repositories::DetectedRepositories;
|
||||
use repo_metadata::RepoMetadataModel;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::search::ai_context_menu::files::data_source::{
|
||||
file_data_source_for_pwd, fuzzy_match_files, FileSnapshot,
|
||||
};
|
||||
use crate::search::ai_context_menu::files::search_item::FileSearchItem;
|
||||
use crate::search::ai_context_menu::mixer::AIContextMenuSearchableAction;
|
||||
use crate::search::data_source::Query;
|
||||
use crate::search::files::model::FileSearchModel;
|
||||
@@ -436,8 +438,6 @@ fn test_directory_search_support() {
|
||||
|
||||
#[test]
|
||||
fn test_directory_action_type() {
|
||||
|
||||
|
||||
let directory_item = FileSearchItem {
|
||||
path: PathBuf::from("src/components/"),
|
||||
match_result: FuzzyMatchResult::no_match(),
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use galaxyui::{App, SingletonEntity};
|
||||
use settings::manager::SettingsManager;
|
||||
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::cloud_object::model::view::CloudViewModel;
|
||||
use crate::cloud_object::{Owner, Revision, ServerMetadata, ServerNotebook, ServerPermissions};
|
||||
use crate::notebooks::manager::NotebookManager;
|
||||
use crate::notebooks::CloudNotebookModel;
|
||||
use crate::search::ai_context_menu::notebooks::data_source::NotebookDataSource;
|
||||
use crate::search::data_source::Query;
|
||||
use crate::search::mixer::SyncDataSource;
|
||||
use crate::server::cloud_objects::update_manager::UpdateManager;
|
||||
use crate::server::ids::{ServerId, SyncId};
|
||||
use crate::server::server_api::ServerApiProvider;
|
||||
use crate::server::sync_queue::SyncQueue;
|
||||
use crate::settings::AISettings;
|
||||
use crate::system::SystemStats;
|
||||
use crate::workspaces::team_tester::TeamTesterStatus;
|
||||
use crate::workspaces::user_profiles::UserProfiles;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use crate::NetworkStatus;
|
||||
|
||||
use crate::server::server_api::object::MockObjectClient;
|
||||
use crate::server::server_api::team::MockTeamClient;
|
||||
use crate::server::server_api::workspace::MockWorkspaceClient;
|
||||
|
||||
fn mock_server_notebook_with_revision(
|
||||
id: i64,
|
||||
title: &str,
|
||||
revision: Revision,
|
||||
) -> ServerNotebook {
|
||||
ServerNotebook {
|
||||
id: SyncId::ServerId(id.into()),
|
||||
metadata: ServerMetadata {
|
||||
uid: ServerId::default(),
|
||||
revision,
|
||||
metadata_last_updated_ts: Utc::now().into(),
|
||||
trashed_ts: None,
|
||||
folder_id: None,
|
||||
is_welcome_object: false,
|
||||
creator_uid: None,
|
||||
last_editor_uid: None,
|
||||
current_editor_uid: None,
|
||||
},
|
||||
permissions: ServerPermissions {
|
||||
space: Owner::mock_current_user(),
|
||||
guests: Vec::new(),
|
||||
anyone_link_sharing: None,
|
||||
permissions_last_updated_ts: Utc::now().into(),
|
||||
},
|
||||
model: CloudNotebookModel {
|
||||
title: title.to_string(),
|
||||
data: format!("{title} content"),
|
||||
ai_document_id: None,
|
||||
conversation_id: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_app(app: &mut App) {
|
||||
app.add_singleton_model(|_| NetworkStatus::new());
|
||||
app.add_singleton_model(|_| SystemStats::new());
|
||||
let mock_team_client = Arc::new(MockTeamClient::new());
|
||||
let mock_workspace_client = Arc::new(MockWorkspaceClient::new());
|
||||
app.add_singleton_model(|ctx| {
|
||||
UserWorkspaces::mock(
|
||||
mock_team_client.clone(),
|
||||
mock_workspace_client.clone(),
|
||||
vec![],
|
||||
ctx,
|
||||
)
|
||||
});
|
||||
app.add_singleton_model(TeamTesterStatus::new);
|
||||
app.add_singleton_model(SyncQueue::mock);
|
||||
app.add_singleton_model(CloudModel::mock);
|
||||
app.add_singleton_model(|ctx| {
|
||||
UpdateManager::new(None, Arc::new(MockObjectClient::new()), ctx)
|
||||
});
|
||||
app.add_singleton_model(|_| UserProfiles::new(Vec::new()));
|
||||
app.add_singleton_model(CloudViewModel::new);
|
||||
app.add_singleton_model(NotebookManager::mock);
|
||||
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
|
||||
app.add_singleton_model(|_| SettingsManager::default());
|
||||
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
|
||||
app.update(crate::settings::init_and_register_user_preferences);
|
||||
app.update(AISettings::register_and_subscribe_to_events);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_state_scores_reflect_recency() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_app(&mut app);
|
||||
|
||||
let now = Utc::now();
|
||||
CloudModel::handle(&app).update(&mut app, |model, ctx| {
|
||||
model.upsert_from_server_notebook(
|
||||
mock_server_notebook_with_revision(
|
||||
1,
|
||||
"oldest",
|
||||
(now - Duration::minutes(3)).into(),
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
model.upsert_from_server_notebook(
|
||||
mock_server_notebook_with_revision(
|
||||
2,
|
||||
"middle",
|
||||
(now - Duration::minutes(2)).into(),
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
model.upsert_from_server_notebook(
|
||||
mock_server_notebook_with_revision(
|
||||
3,
|
||||
"newest",
|
||||
(now - Duration::minutes(1)).into(),
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
|
||||
let data_source = NotebookDataSource::new(false);
|
||||
let results = app.read(|app| data_source.run_query(&Query::from(""), app).unwrap());
|
||||
|
||||
assert_eq!(results.len(), 3);
|
||||
// run_query sorts descending by score, so first result should be newest
|
||||
let scores: Vec<_> = results.iter().map(|r| r.score()).collect();
|
||||
assert!(
|
||||
scores[0] > scores[1] && scores[1] > scores[2],
|
||||
"Expected scores in strictly descending order (newest first), got {scores:?}"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filtered_state_adds_recency_bonus_to_equal_matches() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_app(&mut app);
|
||||
|
||||
let now = Utc::now();
|
||||
// All titles contain "plan" so fuzzy scores should be similar
|
||||
CloudModel::handle(&app).update(&mut app, |model, ctx| {
|
||||
model.upsert_from_server_notebook(
|
||||
mock_server_notebook_with_revision(
|
||||
1,
|
||||
"my first plan",
|
||||
(now - Duration::minutes(3)).into(),
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
model.upsert_from_server_notebook(
|
||||
mock_server_notebook_with_revision(
|
||||
2,
|
||||
"my second plan",
|
||||
(now - Duration::minutes(2)).into(),
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
model.upsert_from_server_notebook(
|
||||
mock_server_notebook_with_revision(
|
||||
3,
|
||||
"my third plan",
|
||||
(now - Duration::minutes(1)).into(),
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
|
||||
let data_source = NotebookDataSource::new(false);
|
||||
let results = app.read(|app| data_source.run_query(&Query::from("plan"), app).unwrap());
|
||||
|
||||
assert_eq!(results.len(), 3);
|
||||
// All match "plan" similarly; recency bonus should make newer items score higher
|
||||
let scores: Vec<_> = results.iter().map(|r| r.score()).collect();
|
||||
assert!(
|
||||
scores[0] > scores[1] && scores[1] > scores[2],
|
||||
"Expected scores in strictly descending order (newest first), got {scores:?}"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multibyte_character_truncation() {
|
||||
// Test string with multibyte characters (emojis, accented chars)
|
||||
let test_content = "This is a test with emojis 🚀 and accented chars like café and naïve that should be truncated properly without panicking. This string is intentionally long to test the 200 character limit and ensure we don't slice in the middle of multibyte characters like 你好世界";
|
||||
|
||||
let truncated = if test_content.len() > 200 {
|
||||
let result = test_content
|
||||
.char_indices()
|
||||
.take_while(|(i, _)| *i <= 197)
|
||||
.last()
|
||||
.map(|(i, c)| &test_content[..i + c.len_utf8()])
|
||||
.unwrap_or("");
|
||||
format!("{result}...")
|
||||
} else {
|
||||
test_content.to_string()
|
||||
};
|
||||
|
||||
// Should not panic and should produce a valid string
|
||||
assert!(!truncated.is_empty());
|
||||
assert!(truncated.ends_with("..."));
|
||||
// The truncated string should be valid UTF-8
|
||||
assert!(std::str::from_utf8(truncated.as_bytes()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncation_with_boundary_at_multibyte_char() {
|
||||
// Create a string where byte 197 falls exactly in the middle of a multibyte character
|
||||
let mut test_content = "a".repeat(195); // 195 single-byte chars
|
||||
test_content.push('🚀'); // 4-byte emoji at positions 195-198
|
||||
test_content.push_str("more text after emoji");
|
||||
|
||||
// This should not panic even though byte 197 is in the middle of the emoji
|
||||
let truncated = if test_content.len() > 200 {
|
||||
let result = test_content
|
||||
.char_indices()
|
||||
.take_while(|(i, _)| *i <= 197)
|
||||
.last()
|
||||
.map(|(i, c)| &test_content[..i + c.len_utf8()])
|
||||
.unwrap_or("");
|
||||
format!("{result}...")
|
||||
} else {
|
||||
test_content.to_string()
|
||||
};
|
||||
|
||||
// Should not panic and should produce a valid string
|
||||
assert!(!truncated.is_empty());
|
||||
// The truncated string should be valid UTF-8
|
||||
assert!(std::str::from_utf8(truncated.as_bytes()).is_ok());
|
||||
// Should either include the full emoji or stop before it
|
||||
assert!(!truncated.contains("🚀") || truncated.contains("🚀..."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_content_not_truncated() {
|
||||
let short_content = "This is a short string with emoji 🚀";
|
||||
|
||||
let result = if short_content.len() > 200 {
|
||||
let truncated = short_content
|
||||
.char_indices()
|
||||
.take_while(|(i, _)| *i <= 197)
|
||||
.last()
|
||||
.map(|(i, c)| &short_content[..i + c.len_utf8()])
|
||||
.unwrap_or("");
|
||||
format!("{truncated}...")
|
||||
} else {
|
||||
short_content.to_string()
|
||||
};
|
||||
|
||||
// Short content should not be truncated
|
||||
assert_eq!(result, short_content);
|
||||
assert!(!result.ends_with("..."));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use ordered_float::OrderedFloat;
|
||||
use galaxyui::elements::{
|
||||
ConstrainedBox, Container, CrossAxisAlignment, Flex, Highlight, Icon, ParentElement, Text,
|
||||
};
|
||||
use galaxyui::fonts::{Properties, Weight};
|
||||
use galaxyui::{AppContext, Element, SingletonEntity};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::cloud_object::ObjectType;
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use cloud_object_client::MockObjectClient;
|
||||
use galaxyui::{App, SingletonEntity};
|
||||
use settings::manager::SettingsManager;
|
||||
|
||||
use crate::ai::facts::{AIFact, AIMemory, CloudAIFactModel};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use ordered_float::OrderedFloat;
|
||||
use galaxyui::elements::{
|
||||
ConstrainedBox, Container, CrossAxisAlignment, Flex, Highlight, Icon, ParentElement, Text,
|
||||
};
|
||||
use galaxyui::fonts::{Properties, Weight};
|
||||
use galaxyui::{AppContext, Element, SingletonEntity};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::cloud_object::{GenericStringObjectFormat, JsonObjectType, ObjectType};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use ai::skills::SkillProvider;
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use ordered_float::OrderedFloat;
|
||||
use galaxy_core::ui::icons::Icon;
|
||||
use galaxyui::elements::{
|
||||
ConstrainedBox, Container, CrossAxisAlignment, Flex, Highlight, ParentElement, Shrinkable, Text,
|
||||
};
|
||||
use galaxyui::fonts::{Properties, Weight};
|
||||
use galaxyui::{AppContext, Element, SingletonEntity};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::search::ai_context_menu::mixer::AIContextMenuSearchableAction;
|
||||
|
||||
@@ -3,11 +3,11 @@ use std::ops::Range;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_channel::Sender;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use itertools::Itertools;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use repo_metadata::repositories::DetectedRepositories;
|
||||
use settings::Setting as _;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use warpui::elements::{
|
||||
AnchorPair, Border, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
|
||||
Dismiss, Empty, Fill, Flex, Hoverable, Icon, MouseStateHandle, OffsetPositioning, OffsetType,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use ordered_float::OrderedFloat;
|
||||
use galaxyui::elements::{
|
||||
ConstrainedBox, Container, CrossAxisAlignment, Flex, Highlight, Icon, ParentElement, Text,
|
||||
};
|
||||
use galaxyui::fonts::{Properties, Weight};
|
||||
use galaxyui::{AppContext, Element, SingletonEntity};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::cloud_object::ObjectType;
|
||||
|
||||
Reference in New Issue
Block a user