Prediction menu more fleshed out but not quite there yet

Connection status works on second open of menu, api url configurable, model switcher not working yet
This commit is contained in:
Oliver Azevedo Barnes 2025-07-01 19:39:34 -03:00
parent ae6ff310c8
commit e91b0a1f39
No known key found for this signature in database
3 changed files with 735 additions and 30 deletions

6
Cargo.lock generated
View file

@ -8214,16 +8214,22 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
"clock",
"copilot",
"editor",
"feature_flags",
"fs",
"futures 0.3.31",
"gpui",
"http_client",
"indoc",
"inline_completion",
"language",
"language_model",
"language_models",
"log",
"lsp",
"ollama",
"paths",
"project",
"regex",

View file

@ -25,6 +25,7 @@ indoc.workspace = true
inline_completion.workspace = true
language.workspace = true
language_models.workspace = true
log.workspace = true
ollama.workspace = true
paths.workspace = true
regex.workspace = true

View file

@ -13,13 +13,15 @@ use gpui::{
Focusable, IntoElement, ParentElement, Render, Subscription, WeakEntity, actions, div,
pulsating_between,
};
use http_client::HttpClient;
use indoc::indoc;
use language::{
EditPredictionsMode, File, Language,
language_settings::{self, AllLanguageSettings, EditPredictionProvider, all_language_settings},
};
use language_models::AllLanguageModelSettings;
use ollama::get_models;
use paths;
use regex::Regex;
use settings::{Settings, SettingsStore, update_settings_file};
use std::{
@ -56,6 +58,10 @@ pub struct InlineCompletionButton {
fs: Arc<dyn Fs>,
user_store: Entity<UserStore>,
popover_menu_handle: PopoverMenuHandle<ContextMenu>,
http_client: Arc<dyn HttpClient>,
connection_status: Arc<std::sync::Mutex<Option<bool>>>,
connection_checking: Arc<std::sync::Mutex<bool>>,
this_entity: WeakEntity<Self>,
}
enum SupermavenButtonStatus {
@ -406,6 +412,10 @@ impl InlineCompletionButton {
popover_menu_handle: PopoverMenuHandle<ContextMenu>,
cx: &mut Context<Self>,
) -> Self {
let http_client = cx.http_client();
let connection_status = Arc::new(std::sync::Mutex::new(None));
let connection_checking = Arc::new(std::sync::Mutex::new(false));
if let Some(copilot) = Copilot::global(cx) {
cx.observe(&copilot, |_, _, cx| cx.notify()).detach()
}
@ -413,6 +423,8 @@ impl InlineCompletionButton {
cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
.detach();
let this_entity = cx.entity().downgrade();
Self {
editor_subscription: None,
editor_enabled: None,
@ -424,6 +436,10 @@ impl InlineCompletionButton {
popover_menu_handle,
fs,
user_store,
http_client,
connection_status,
connection_checking,
this_entity,
}
}
@ -842,62 +858,366 @@ impl InlineCompletionButton {
})
}
/// Builds a comprehensive context menu for Ollama with the following features:
/// - Connection status display with real-time checking
/// - API URL configuration that opens settings at the correct location
/// - Model selection from available models
/// - Common language settings (buffer/language/global toggles, privacy settings)
/// - Refresh connection functionality
/// - Links to Ollama resources (Web UI, model library, installation)
///
/// This method was enhanced to address the following issues:
/// 1. Connection status now refreshes automatically when menu is opened
/// 2. API URL configuration navigates to the correct setting location
fn build_ollama_context_menu(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ContextMenu> {
let fs = self.fs.clone();
let http_client = self.http_client.clone();
ContextMenu::build(window, cx, |menu, window, cx| {
let settings = AllLanguageModelSettings::get_global(cx);
let ollama_settings = &settings.ollama;
// Check if we have available models (indicates connection)
let is_connected = !ollama_settings.available_models.is_empty();
let connection_status = if is_connected {
"Connected"
} else {
"Disconnected"
};
// Clone needed values to avoid borrowing issues
let api_url = ollama_settings.api_url.clone();
let available_models = ollama_settings.available_models.clone();
let menu =
menu.header("Ollama Status")
.entry(connection_status, None, |_window, _cx| {
// Status display only
});
// Check connection status and trigger immediate refresh when menu opens
let (status_text, status_icon, status_color) = self.get_connection_display_info(cx);
let menu = if !ollama_settings.available_models.is_empty() {
let current_model = ollama_settings
.available_models
.first()
.map(|m| m.display_name.as_ref().unwrap_or(&m.name).clone())
.unwrap_or_else(|| "No model selected".to_string());
let menu = menu.header("Ollama Status").custom_entry(
move |_window, _cx| {
h_flex()
.gap_2()
.child(
Icon::new(status_icon)
.size(IconSize::Small)
.color(status_color),
)
.child(
Label::new(status_text)
.size(LabelSize::Small)
.color(status_color),
)
.into_any_element()
},
|_window, _cx| {
// Status display only
},
);
menu.separator().header("Current Model").entry(
current_model,
None,
|_window, _cx| {
// TODO: Open model selection dialog
// API URL configuration
let menu = menu
.entry("Configure API URL", None, {
let fs = fs.clone();
move |window, cx| {
Self::open_ollama_settings(fs.clone(), window, cx);
}
})
.custom_entry(
{
let display_url = api_url.clone();
move |_window, _cx| {
h_flex()
.gap_2()
.child(
Icon::new(IconName::Link)
.size(IconSize::Small)
.color(Color::Muted),
)
.child(
Label::new(display_url.clone())
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element()
}
},
)
|_window, _cx| {
// URL display only
},
);
// Model selection section
let menu = if !available_models.is_empty() {
let menu = menu.separator().header("Available Models");
// Add each available model as a menu entry
available_models.iter().fold(menu, |menu, model| {
let model_name = model.display_name.as_ref().unwrap_or(&model.name);
let is_current = available_models
.first()
.map(|m| &m.name == &model.name)
.unwrap_or(false);
menu.toggleable_entry(
model_name.clone(),
is_current,
IconPosition::Start,
None,
{
let model_name = model.name.clone();
let fs = fs.clone();
move |_window, cx| {
Self::switch_ollama_model(fs.clone(), model_name.clone(), cx);
}
},
)
})
} else {
menu
menu.separator()
.entry("No Models Available", None, |_window, _cx| {
// Display only
})
};
// Use the common language settings menu
let menu = self.build_language_settings_menu(menu, window, cx);
// Separator and Ollama-specific actions
menu.separator()
.entry("Open Ollama Web UI", None, move |_window, cx| {
cx.open_url(&api_url);
let menu = menu
.separator()
.entry("Refresh Connection", None, {
let http_client = http_client.clone();
let api_url = api_url.clone();
let connection_status = self.connection_status.clone();
move |_window, cx| {
// Clear current status and start fresh check
*connection_status.lock().unwrap() = None;
// Start immediate background check
let connection_status_clone = connection_status.clone();
let http_client_clone = http_client.clone();
let api_url_clone = api_url.clone();
cx.background_executor()
.spawn(async move {
let is_connected =
InlineCompletionButton::check_ollama_connection_async(
http_client_clone,
&api_url_clone,
)
.await;
*connection_status_clone.lock().unwrap() = Some(is_connected);
})
.detach();
}
})
.entry("Open Ollama Web UI", None, {
let web_ui_url = api_url.clone();
move |_window, cx| {
cx.open_url(&web_ui_url);
}
})
.entry("Download More Models", None, |_window, cx| {
cx.open_url("https://ollama.com/library");
});
if self.get_connection_status() != Some(true) {
menu.entry("Check Ollama Installation", None, |_window, cx| {
cx.open_url("https://ollama.com/download");
})
} else {
menu
}
})
}
fn get_connection_status(&self) -> Option<bool> {
*self.connection_status.lock().unwrap()
}
fn is_checking_connection(&self) -> bool {
*self.connection_checking.lock().unwrap()
}
fn get_connection_display_info(&self, cx: &App) -> (&'static str, IconName, Color) {
let settings = AllLanguageModelSettings::get_global(cx);
let api_url = settings.ollama.api_url.clone();
if api_url.trim().is_empty() {
return ("No URL", IconName::Close, Color::Error);
}
// Start background refresh of status
self.start_background_refresh(cx);
// Show cached status immediately
match self.get_connection_status() {
Some(true) => ("Connected", IconName::Check, Color::Success),
Some(false) => ("Disconnected", IconName::Close, Color::Error),
None => {
// No cached status yet, assume disconnected until proven otherwise
("Disconnected", IconName::Close, Color::Error)
}
}
}
fn start_background_refresh(&self, cx: &App) {
// Don't start multiple concurrent checks
if self.is_checking_connection() {
return;
}
let settings = AllLanguageModelSettings::get_global(cx);
let api_url = settings.ollama.api_url.clone();
if api_url.trim().is_empty() {
*self.connection_status.lock().unwrap() = Some(false);
return;
}
let http_client = self.http_client.clone();
let connection_status = self.connection_status.clone();
let connection_checking = self.connection_checking.clone();
// Mark as checking
*connection_checking.lock().unwrap() = true;
cx.background_executor()
.spawn(async move {
let is_connected = Self::check_ollama_connection_async(http_client, &api_url).await;
*connection_status.lock().unwrap() = Some(is_connected);
*connection_checking.lock().unwrap() = false;
log::info!("Ollama connection status updated: {}", is_connected);
})
.detach();
}
async fn check_ollama_connection_async(
http_client: Arc<dyn HttpClient>,
api_url: &str,
) -> bool {
log::info!("Attempting to connect to Ollama at: {}", api_url);
match get_models(
http_client.as_ref(),
api_url,
Some(std::time::Duration::from_secs(5)),
)
.await
{
Ok(models) => {
log::info!(
"Successfully connected to Ollama, found {} models",
models.len()
);
true
}
Err(e) => {
log::warn!("Failed to connect to Ollama: {}", e);
false
}
}
}
/// Opens Zed settings and navigates directly to the Ollama API URL configuration.
/// Uses improved regex patterns to locate the exact setting in the JSON structure.
fn open_ollama_settings(_fs: Arc<dyn Fs>, window: &mut Window, cx: &mut App) {
if let Some(workspace) = window.root::<Workspace>().flatten() {
let workspace = workspace.downgrade();
window
.spawn(cx, async move |cx| {
let settings_editor = workspace
.update_in(cx, |_, window, cx| {
create_and_open_local_file(paths::settings_file(), window, cx, || {
settings::initial_user_settings_content().as_ref().into()
})
})?
.await?
.downcast::<Editor>()
.unwrap();
let _ = settings_editor
.downgrade()
.update_in(cx, |item, window, cx| {
let text = item.buffer().read(cx).snapshot(cx).text();
// Look for language_models.ollama.api_url setting with precise pattern
// This matches the full nested structure to avoid false matches
let api_url_pattern = r#""language_models"\s*:\s*\{[^}]*"ollama"\s*:\s*\{[^}]*"api_url"\s*:\s*"([^"]*)"#;
let regex = regex::Regex::new(api_url_pattern).unwrap();
if let Some(captures) = regex.captures(&text) {
let _full_match = captures.get(0).unwrap();
let value_capture = captures.get(1).unwrap();
// Select the API URL value (excluding quotes)
item.change_selections(
SelectionEffects::scroll(Autoscroll::newest()),
window,
cx,
|selections| {
selections.select_ranges(vec![
value_capture.start()..value_capture.end(),
]);
},
);
return Ok::<(), anyhow::Error>(());
}
// Fallback: look for just the "api_url" key and select its value
let simple_pattern = r#""api_url"\s*:\s*"([^"]*)"#;
let simple_regex = regex::Regex::new(simple_pattern).unwrap();
if let Some(captures) = simple_regex.captures(&text) {
let value_capture = captures.get(1).unwrap();
item.change_selections(
SelectionEffects::scroll(Autoscroll::newest()),
window,
cx,
|selections| {
selections.select_ranges(vec![
value_capture.start()..value_capture.end(),
]);
},
);
return Ok::<(), anyhow::Error>(());
}
// If we can't find the specific setting, ensure language_models section exists
let settings = cx.global::<SettingsStore>();
let edits = settings.edits_for_update::<AllLanguageModelSettings>(
&text,
|file| {
if file.ollama.is_none() {
file.ollama =
Some(language_models::OllamaSettingsContent {
api_url: Some("http://localhost:11434".to_string()),
available_models: None,
});
}
},
);
if !edits.is_empty() {
item.edit(edits, cx);
}
Ok::<(), anyhow::Error>(())
})?;
Ok::<(), anyhow::Error>(())
})
.detach_and_log_err(cx);
}
}
fn switch_ollama_model(fs: Arc<dyn Fs>, model_name: String, cx: &mut App) {
update_settings_file::<AllLanguageModelSettings>(fs, cx, move |settings, _cx| {
// Move the selected model to the front of the list to make it the "current" one
if let Some(ollama_settings) = &mut settings.ollama {
if let Some(models) = &mut ollama_settings.available_models {
if let Some(index) = models.iter().position(|m| m.name == model_name) {
let selected_model = models.remove(index);
models.insert(0, selected_model);
}
}
}
});
}
pub fn update_enabled(&mut self, editor: Entity<Editor>, cx: &mut Context<Self>) {
let editor = editor.read(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
@ -1131,7 +1451,14 @@ mod tests {
// Test that accessing Ollama settings doesn't panic
let settings = AllLanguageModelSettings::get_global(cx);
let _ollama_settings = &settings.ollama;
let ollama_settings = &settings.ollama;
// Verify connection status is properly determined
let is_connected = !ollama_settings.available_models.is_empty();
assert!(!is_connected); // Should be disconnected in test environment
// Verify API URL is accessible
assert!(ollama_settings.api_url.contains("localhost"));
// Verify the button has access to build_language_settings_menu method
// This indirectly tests that Ollama menu can use the common functionality
@ -1156,4 +1483,375 @@ mod tests {
assert!(ollama_settings.available_models.is_empty());
});
}
#[gpui::test]
async fn test_ollama_menu_structure(cx: &mut TestAppContext) {
let fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let popover_menu_handle = PopoverMenuHandle::default();
let button = cx.new(|cx| {
InlineCompletionButton::new(fs.clone(), user_store, popover_menu_handle, cx)
});
// Test that the menu includes expected sections
let settings = AllLanguageModelSettings::get_global(cx);
let ollama_settings = &settings.ollama;
// Verify default connection status (should be disconnected in test)
let is_connected = !ollama_settings.available_models.is_empty();
assert!(!is_connected); // No models available in test environment
// Verify API URL is accessible and has default value
assert!(ollama_settings.api_url.contains("localhost"));
// Verify button can be created and read without panicking
button.read(cx);
});
}
#[gpui::test]
async fn test_ollama_default_settings(cx: &mut TestAppContext) {
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
// Test default Ollama settings structure
let settings = AllLanguageModelSettings::get_global(cx);
let ollama_settings = &settings.ollama;
// Verify default configuration
assert!(ollama_settings.api_url.contains("localhost"));
assert!(ollama_settings.available_models.is_empty());
// Test that menu creation would work with these defaults
// (We don't actually create the menu to avoid UI complexity in tests)
});
}
#[gpui::test]
async fn test_ollama_connection_checking(cx: &mut TestAppContext) {
let fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let popover_menu_handle = PopoverMenuHandle::default();
let button = cx.new(|cx| {
InlineCompletionButton::new(fs.clone(), user_store, popover_menu_handle, cx)
});
// Test connection status checking with default settings
// Note: Connection status will be None initially until async check completes
let is_connected = button.read(cx).get_connection_status();
assert_eq!(is_connected, None); // Should be None initially (no check done yet)
// Verify connection status logic
let settings = AllLanguageModelSettings::get_global(cx);
let ollama_settings = &settings.ollama;
assert!(ollama_settings.available_models.is_empty());
assert!(!ollama_settings.api_url.is_empty()); // Should have default localhost URL
// Test refresh connection status method
// Test connection status checking logic
let is_connected = button.read(cx).get_connection_status();
assert_eq!(is_connected, None); // Should be None initially
});
}
#[gpui::test]
async fn test_ollama_connection_status_refresh(cx: &mut TestAppContext) {
let fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let popover_menu_handle = PopoverMenuHandle::default();
let button = cx.new(|cx| {
InlineCompletionButton::new(fs.clone(), user_store, popover_menu_handle, cx)
});
// Test initial connection status
let initial_status = button.read(cx).get_connection_status();
assert_eq!(initial_status, None); // Should be None initially
// Test that background refresh can be triggered
button.read(cx).start_background_refresh(cx);
let status_after_check = button.read(cx).get_connection_status();
assert_eq!(status_after_check, None); // Should still be None (async check in progress)
// Verify connection status can be manually updated
*button.read(cx).connection_status.lock().unwrap() = Some(true);
let updated_status = button.read(cx).get_connection_status();
assert_eq!(updated_status, Some(true)); // Should now be Some(true)
});
}
#[gpui::test]
async fn test_ollama_connection_checking_state(cx: &mut TestAppContext) {
let fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let popover_menu_handle = PopoverMenuHandle::default();
let button = cx.new(|cx| {
InlineCompletionButton::new(fs.clone(), user_store, popover_menu_handle, cx)
});
// Test initial checking state
let is_checking = button.read(cx).is_checking_connection();
assert!(!is_checking); // Should not be checking initially
// Test that checking state can be updated
*button.read(cx).connection_checking.lock().unwrap() = true;
let is_checking_after = button.read(cx).is_checking_connection();
assert!(is_checking_after); // Should now be checking
});
}
#[gpui::test]
async fn test_ollama_api_url_navigation_regex(cx: &mut TestAppContext) {
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
// Test the regex patterns used for API URL navigation
let test_settings_content = r#"{
"language_models": {
"ollama": {
"api_url": "http://localhost:11434",
"available_models": []
}
}
}"#;
// Test the precise regex pattern
let api_url_pattern =
r#""language_models"\s*:\s*\{[^}]*"ollama"\s*:\s*\{[^}]*"api_url"\s*:\s*"([^"]*)"#;
let regex = regex::Regex::new(api_url_pattern).unwrap();
if let Some(captures) = regex.captures(test_settings_content) {
let value_capture = captures.get(1).unwrap();
assert_eq!(value_capture.as_str(), "http://localhost:11434");
// Verify the capture positions are correct
assert!(value_capture.start() > 0);
assert!(value_capture.end() > value_capture.start());
} else {
panic!("Regex should match the test content");
}
// Test fallback regex
let simple_pattern = r#""api_url"\s*:\s*"([^"]*)"#;
let simple_regex = regex::Regex::new(simple_pattern).unwrap();
if let Some(captures) = simple_regex.captures(test_settings_content) {
let value_capture = captures.get(1).unwrap();
assert_eq!(value_capture.as_str(), "http://localhost:11434");
} else {
panic!("Fallback regex should match the test content");
}
});
}
#[gpui::test]
async fn test_ollama_model_switching_logic(cx: &mut TestAppContext) {
let _fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
// Test the model switching function logic
// This tests the internal logic without actually modifying settings
let test_models = vec![
language_models::provider::ollama::AvailableModel {
name: "llama3.2:3b".to_string(),
display_name: Some("Llama 3.2 3B".to_string()),
max_tokens: 4096,
keep_alive: None,
supports_tools: Some(false),
supports_images: Some(false),
supports_thinking: Some(false),
},
language_models::provider::ollama::AvailableModel {
name: "codellama:7b".to_string(),
display_name: Some("CodeLlama 7B".to_string()),
max_tokens: 8192,
keep_alive: None,
supports_tools: Some(true),
supports_images: Some(false),
supports_thinking: Some(false),
},
];
// Verify we can access the model data
assert_eq!(test_models.len(), 2);
assert_eq!(test_models[0].name, "llama3.2:3b");
assert_eq!(test_models[1].name, "codellama:7b");
// Test model display name logic
let first_model_display = test_models[0]
.display_name
.as_ref()
.unwrap_or(&test_models[0].name);
assert_eq!(first_model_display, "Llama 3.2 3B");
// Verify the switch_ollama_model function exists and can be called
// (We don't actually call it to avoid file system operations in tests)
});
}
#[gpui::test]
async fn test_ollama_refresh_connection_functionality(cx: &mut TestAppContext) {
let fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let popover_menu_handle = PopoverMenuHandle::default();
let button = cx.new(|cx| {
InlineCompletionButton::new(fs.clone(), user_store, popover_menu_handle, cx)
});
// Test that refresh connection function can be called
let settings = AllLanguageModelSettings::get_global(cx);
let api_url = settings.ollama.api_url.clone();
let http_client = button.read(cx).http_client.clone();
// Test that the menu can show connection status
let _http_client = http_client;
let _api_url = api_url;
// Verify button still works after refresh attempt
button.read(cx);
});
}
#[gpui::test]
async fn test_ollama_async_connection_checking(cx: &mut TestAppContext) {
let fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let popover_menu_handle = PopoverMenuHandle::default();
let button = cx.new(|cx| {
InlineCompletionButton::new(fs.clone(), user_store, popover_menu_handle, cx)
});
// Test the async connection checking function directly
let http_client = button.read(cx).http_client.clone();
// Test with invalid URL (should return false)
cx.background_executor()
.spawn(async move {
let result = InlineCompletionButton::check_ollama_connection_async(
http_client,
"http://invalid-url:99999",
)
.await;
assert!(!result); // Should be false for invalid URL
})
.detach();
// Verify button functionality
button.read(cx);
});
}
#[gpui::test]
async fn test_ollama_menu_refresh_functionality(cx: &mut TestAppContext) {
let fs: Arc<dyn Fs> = FakeFs::new(cx.executor());
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
AllLanguageModelSettings::register(cx);
language_model::LanguageModelRegistry::test(cx);
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let popover_menu_handle = PopoverMenuHandle::default();
let button = cx.new(|cx| {
InlineCompletionButton::new(fs.clone(), user_store, popover_menu_handle, cx)
});
// Test that the refresh connection functionality exists
let settings = AllLanguageModelSettings::get_global(cx);
let ollama_settings = &settings.ollama;
// Verify that the refresh connection method works with the current settings
let _api_url = &ollama_settings.api_url;
let _connection_status = button.read(cx).connection_status.clone();
// Test that background refresh can be triggered
button.read(cx).start_background_refresh(cx);
// Verify connection status is properly handled
let status = button.read(cx).get_connection_status();
assert_eq!(status, None); // Should be None initially with fake HTTP client
});
}
}