Apply recent changes
This commit is contained in:
@@ -155,6 +155,7 @@ pub struct ChatApp {
|
||||
available_themes: Vec<String>, // Cached list of theme names
|
||||
selected_theme_index: usize, // Index of selected theme in browser
|
||||
pending_consent: Option<ConsentDialogState>, // Pending consent request
|
||||
system_status: String, // System/status messages (tool execution, status, etc)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -173,15 +174,16 @@ impl ChatApp {
|
||||
let mut textarea = TextArea::default();
|
||||
configure_textarea_defaults(&mut textarea);
|
||||
|
||||
// Load theme based on config
|
||||
let theme_name = &controller.config().ui.theme;
|
||||
let theme = owlen_core::theme::get_theme(theme_name).unwrap_or_else(|| {
|
||||
// Load theme and provider based on config before moving `controller`.
|
||||
let config_guard = controller.config_async().await;
|
||||
let theme_name = config_guard.ui.theme.clone();
|
||||
let current_provider = config_guard.general.default_provider.clone();
|
||||
drop(config_guard);
|
||||
let theme = owlen_core::theme::get_theme(&theme_name).unwrap_or_else(|| {
|
||||
eprintln!("Warning: Theme '{}' not found, using default", theme_name);
|
||||
Theme::default()
|
||||
});
|
||||
|
||||
let current_provider = controller.config().general.default_provider.clone();
|
||||
|
||||
let app = Self {
|
||||
controller,
|
||||
mode: InputMode::Normal,
|
||||
@@ -225,6 +227,7 @@ impl ChatApp {
|
||||
available_themes: Vec::new(),
|
||||
selected_theme_index: 0,
|
||||
pending_consent: None,
|
||||
system_status: String::new(),
|
||||
};
|
||||
|
||||
Ok((app, session_rx))
|
||||
@@ -260,10 +263,16 @@ impl ChatApp {
|
||||
self.controller.selected_model()
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &owlen_core::config::Config {
|
||||
// Synchronous access for UI rendering and other callers that expect an immediate Config.
|
||||
pub fn config(&self) -> tokio::sync::MutexGuard<'_, owlen_core::config::Config> {
|
||||
self.controller.config()
|
||||
}
|
||||
|
||||
// Asynchronous version retained for places that already await the config.
|
||||
pub async fn config_async(&self) -> tokio::sync::MutexGuard<'_, owlen_core::config::Config> {
|
||||
self.controller.config_async().await
|
||||
}
|
||||
|
||||
pub(crate) fn model_selector_items(&self) -> &[ModelSelectorItem] {
|
||||
&self.model_selector_items
|
||||
}
|
||||
@@ -328,6 +337,25 @@ impl ChatApp {
|
||||
&mut self.textarea
|
||||
}
|
||||
|
||||
pub fn system_status(&self) -> &str {
|
||||
&self.system_status
|
||||
}
|
||||
|
||||
pub fn set_system_status(&mut self, status: String) {
|
||||
self.system_status = status;
|
||||
}
|
||||
|
||||
pub fn append_system_status(&mut self, status: &str) {
|
||||
if !self.system_status.is_empty() {
|
||||
self.system_status.push_str(" | ");
|
||||
}
|
||||
self.system_status.push_str(status);
|
||||
}
|
||||
|
||||
pub fn clear_system_status(&mut self) {
|
||||
self.system_status.clear();
|
||||
}
|
||||
|
||||
pub fn command_buffer(&self) -> &str {
|
||||
&self.command_buffer
|
||||
}
|
||||
@@ -463,7 +491,7 @@ impl ChatApp {
|
||||
self.theme = theme;
|
||||
// Save theme to config
|
||||
self.controller.config_mut().ui.theme = theme_name.to_string();
|
||||
if let Err(err) = config::save_config(self.controller.config()) {
|
||||
if let Err(err) = config::save_config(&self.controller.config()) {
|
||||
self.error = Some(format!("Failed to save theme config: {}", err));
|
||||
} else {
|
||||
self.status = format!("Switched to theme: {}", theme_name);
|
||||
@@ -538,10 +566,10 @@ impl ChatApp {
|
||||
|
||||
self.expanded_provider = Some(self.selected_provider.clone());
|
||||
self.update_selected_provider_index();
|
||||
self.sync_selected_model_index();
|
||||
self.sync_selected_model_index().await;
|
||||
|
||||
// Ensure the default model is set in the controller and config
|
||||
self.controller.ensure_default_model(&self.models);
|
||||
// Ensure the default model is set in the controller and config (async)
|
||||
self.controller.ensure_default_model(&self.models).await;
|
||||
|
||||
let current_model_name = self.controller.selected_model().to_string();
|
||||
let current_model_provider = self.controller.config().general.default_provider.clone();
|
||||
@@ -549,7 +577,7 @@ impl ChatApp {
|
||||
if config_model_name.as_deref() != Some(¤t_model_name)
|
||||
|| config_model_provider != current_model_provider
|
||||
{
|
||||
if let Err(err) = config::save_config(self.controller.config()) {
|
||||
if let Err(err) = config::save_config(&self.controller.config()) {
|
||||
self.error = Some(format!("Failed to save config: {err}"));
|
||||
} else {
|
||||
self.error = None;
|
||||
@@ -592,24 +620,74 @@ impl ChatApp {
|
||||
// Handle consent dialog first (highest priority)
|
||||
if let Some(consent_state) = &self.pending_consent {
|
||||
match key.code {
|
||||
KeyCode::Char('y') | KeyCode::Char('Y') => {
|
||||
// Grant consent
|
||||
KeyCode::Char('1') => {
|
||||
// Allow once
|
||||
let tool_name = consent_state.tool_name.clone();
|
||||
let data_types = consent_state.data_types.clone();
|
||||
let endpoints = consent_state.endpoints.clone();
|
||||
|
||||
self.controller
|
||||
.grant_consent(&tool_name, data_types, endpoints);
|
||||
self.controller.grant_consent_with_scope(
|
||||
&tool_name,
|
||||
data_types,
|
||||
endpoints,
|
||||
owlen_core::consent::ConsentScope::Once,
|
||||
);
|
||||
self.pending_consent = None;
|
||||
self.status = format!("✓ Consent granted for {}", tool_name);
|
||||
self.status = format!("✓ Consent granted (once) for {}", tool_name);
|
||||
self.set_system_status(format!(
|
||||
"✓ Consent granted (once): {}",
|
||||
tool_name
|
||||
));
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
|
||||
KeyCode::Char('2') => {
|
||||
// Allow session
|
||||
let tool_name = consent_state.tool_name.clone();
|
||||
let data_types = consent_state.data_types.clone();
|
||||
let endpoints = consent_state.endpoints.clone();
|
||||
|
||||
self.controller.grant_consent_with_scope(
|
||||
&tool_name,
|
||||
data_types,
|
||||
endpoints,
|
||||
owlen_core::consent::ConsentScope::Session,
|
||||
);
|
||||
self.pending_consent = None;
|
||||
self.status = format!("✓ Consent granted (session) for {}", tool_name);
|
||||
self.set_system_status(format!(
|
||||
"✓ Consent granted (session): {}",
|
||||
tool_name
|
||||
));
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
KeyCode::Char('3') => {
|
||||
// Allow always (permanent)
|
||||
let tool_name = consent_state.tool_name.clone();
|
||||
let data_types = consent_state.data_types.clone();
|
||||
let endpoints = consent_state.endpoints.clone();
|
||||
|
||||
self.controller.grant_consent_with_scope(
|
||||
&tool_name,
|
||||
data_types,
|
||||
endpoints,
|
||||
owlen_core::consent::ConsentScope::Permanent,
|
||||
);
|
||||
self.pending_consent = None;
|
||||
self.status =
|
||||
format!("✓ Consent granted (permanent) for {}", tool_name);
|
||||
self.set_system_status(format!(
|
||||
"✓ Consent granted (permanent): {}",
|
||||
tool_name
|
||||
));
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
KeyCode::Char('4') | KeyCode::Esc => {
|
||||
// Deny consent - clear both consent and pending tool execution to prevent retry
|
||||
let tool_name = consent_state.tool_name.clone();
|
||||
self.pending_consent = None;
|
||||
self.pending_tool_execution = None; // Clear to prevent infinite retry
|
||||
self.status = format!("✗ Consent denied for {}", tool_name);
|
||||
self.set_system_status(format!("✗ Consent denied: {}", tool_name));
|
||||
self.error = Some(format!("Tool {} was blocked by user", tool_name));
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
@@ -1532,7 +1610,7 @@ impl ChatApp {
|
||||
match self.controller.set_tool_enabled(tool, true).await {
|
||||
Ok(_) => {
|
||||
if let Err(err) =
|
||||
config::save_config(self.controller.config())
|
||||
config::save_config(&self.controller.config())
|
||||
{
|
||||
self.error = Some(format!(
|
||||
"Enabled {tool}, but failed to save config: {err}"
|
||||
@@ -1557,7 +1635,7 @@ impl ChatApp {
|
||||
match self.controller.set_tool_enabled(tool, false).await {
|
||||
Ok(_) => {
|
||||
if let Err(err) =
|
||||
config::save_config(self.controller.config())
|
||||
config::save_config(&self.controller.config())
|
||||
{
|
||||
self.error = Some(format!(
|
||||
"Disabled {tool}, but failed to save config: {err}"
|
||||
@@ -1619,7 +1697,8 @@ impl ChatApp {
|
||||
self.available_providers.get(self.selected_provider_index)
|
||||
{
|
||||
self.selected_provider = provider.clone();
|
||||
self.sync_selected_model_index(); // Update model selection based on new provider
|
||||
// Update model selection based on new provider (await async)
|
||||
self.sync_selected_model_index().await; // Update model selection based on new provider
|
||||
self.mode = InputMode::ModelSelection;
|
||||
}
|
||||
}
|
||||
@@ -1679,7 +1758,8 @@ impl ChatApp {
|
||||
self.selected_provider = model.provider.clone();
|
||||
self.update_selected_provider_index();
|
||||
|
||||
self.controller.set_model(model_id.clone());
|
||||
// Set the selected model asynchronously
|
||||
self.controller.set_model(model_id.clone()).await;
|
||||
self.status = format!(
|
||||
"Using model: {} (provider: {})",
|
||||
model_label, self.selected_provider
|
||||
@@ -1689,7 +1769,7 @@ impl ChatApp {
|
||||
Some(model_id.clone());
|
||||
self.controller.config_mut().general.default_provider =
|
||||
self.selected_provider.clone();
|
||||
match config::save_config(self.controller.config()) {
|
||||
match config::save_config(&self.controller.config()) {
|
||||
Ok(_) => self.error = None,
|
||||
Err(err) => {
|
||||
self.error = Some(format!(
|
||||
@@ -2351,7 +2431,9 @@ impl ChatApp {
|
||||
let provider_cfg = if let Some(cfg) = self.controller.config().provider(provider_name) {
|
||||
cfg.clone()
|
||||
} else {
|
||||
let cfg = config::ensure_provider_config(self.controller.config_mut(), provider_name);
|
||||
let mut guard = self.controller.config_mut();
|
||||
// Pass a mutable reference directly; avoid unnecessary deref
|
||||
let cfg = config::ensure_provider_config(&mut guard, provider_name);
|
||||
cfg.clone()
|
||||
};
|
||||
|
||||
@@ -2403,8 +2485,9 @@ impl ChatApp {
|
||||
|
||||
self.expanded_provider = Some(self.selected_provider.clone());
|
||||
self.update_selected_provider_index();
|
||||
self.controller.ensure_default_model(&self.models);
|
||||
self.sync_selected_model_index();
|
||||
// Ensure the default model is set after refreshing models (async)
|
||||
self.controller.ensure_default_model(&self.models).await;
|
||||
self.sync_selected_model_index().await;
|
||||
|
||||
let current_model_name = self.controller.selected_model().to_string();
|
||||
let current_model_provider = self.controller.config().general.default_provider.clone();
|
||||
@@ -2412,7 +2495,7 @@ impl ChatApp {
|
||||
if config_model_name.as_deref() != Some(¤t_model_name)
|
||||
|| config_model_provider != current_model_provider
|
||||
{
|
||||
if let Err(err) = config::save_config(self.controller.config()) {
|
||||
if let Err(err) = config::save_config(&self.controller.config()) {
|
||||
self.error = Some(format!("Failed to save config: {err}"));
|
||||
} else {
|
||||
self.error = None;
|
||||
@@ -2537,6 +2620,13 @@ impl ChatApp {
|
||||
let consent_needed = self.controller.check_tools_consent_needed(&tool_calls);
|
||||
|
||||
if !consent_needed.is_empty() {
|
||||
// If a consent dialog is already being shown, don't send another request
|
||||
// Just re-queue the tool execution and wait for user response
|
||||
if self.pending_consent.is_some() {
|
||||
self.pending_tool_execution = Some((message_id, tool_calls));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Show consent for the first tool that needs it
|
||||
// After consent is granted, the next iteration will check remaining tools
|
||||
let (tool_name, data_types, endpoints) = consent_needed.into_iter().next().unwrap();
|
||||
@@ -2555,6 +2645,11 @@ impl ChatApp {
|
||||
|
||||
// Show tool execution status
|
||||
self.status = format!("🔧 Executing {} tool(s)...", tool_calls.len());
|
||||
|
||||
// Show tool names in system output
|
||||
let tool_names: Vec<String> = tool_calls.iter().map(|tc| tc.name.clone()).collect();
|
||||
self.set_system_status(format!("🔧 Executing tools: {}", tool_names.join(", ")));
|
||||
|
||||
self.start_loading_animation();
|
||||
|
||||
// Execute tools and get the result
|
||||
@@ -2569,6 +2664,7 @@ impl ChatApp {
|
||||
}) => {
|
||||
// Tool execution succeeded, spawn stream handler for continuation
|
||||
self.status = "Tool results sent. Generating response...".to_string();
|
||||
self.set_system_status("✓ Tools executed successfully".to_string());
|
||||
self.spawn_stream(response_id, stream);
|
||||
match self.controller.mark_stream_placeholder(response_id, "▌") {
|
||||
Ok(_) => self.error = None,
|
||||
@@ -2582,19 +2678,22 @@ impl ChatApp {
|
||||
// Tool execution complete without streaming (shouldn't happen in streaming mode)
|
||||
self.stop_loading_animation();
|
||||
self.status = "✓ Tool execution complete".to_string();
|
||||
self.set_system_status("✓ Tool execution complete".to_string());
|
||||
self.error = None;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
self.stop_loading_animation();
|
||||
self.status = "Tool execution failed".to_string();
|
||||
self.set_system_status(format!("❌ Tool execution failed: {}", err));
|
||||
self.error = Some(format!("Tool execution failed: {}", err));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_selected_model_index(&mut self) {
|
||||
// Updated to async to allow awaiting async controller calls
|
||||
async fn sync_selected_model_index(&mut self) {
|
||||
self.expanded_provider = Some(self.selected_provider.clone());
|
||||
self.rebuild_model_selector_items();
|
||||
|
||||
@@ -2616,7 +2715,8 @@ impl ChatApp {
|
||||
|
||||
if let Some(model) = self.selected_model_info().cloned() {
|
||||
self.selected_provider = model.provider.clone();
|
||||
self.controller.set_model(model.id.clone());
|
||||
// Set the selected model asynchronously
|
||||
self.controller.set_model(model.id.clone()).await;
|
||||
self.controller.config_mut().general.default_model = Some(model.id.clone());
|
||||
self.controller.config_mut().general.default_provider =
|
||||
self.selected_provider.clone();
|
||||
@@ -2627,7 +2727,7 @@ impl ChatApp {
|
||||
self.update_selected_provider_index();
|
||||
|
||||
if config_updated {
|
||||
if let Err(err) = config::save_config(self.controller.config()) {
|
||||
if let Err(err) = config::save_config(&self.controller.config()) {
|
||||
self.error = Some(format!("Failed to save config: {err}"));
|
||||
} else {
|
||||
self.error = None;
|
||||
|
||||
Reference in New Issue
Block a user