@@ -1,4 +1,3 @@
-use crate::AgentPanel;
use crate::context::{AgentContextHandle, RULES_ICON};
use crate::context_picker::{ContextPicker, MentionLink};
use crate::context_store::ContextStore;
@@ -13,6 +12,7 @@ use crate::tool_use::{PendingToolUseStatus, ToolUse};
use crate::ui::{
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
};
+use crate::{AgentPanel, ModelUsageContext};
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
use anyhow::Context as _;
use assistant_tool::ToolUseStatus;
@@ -1348,6 +1348,7 @@ impl ActiveThread {
Some(self.text_thread_store.downgrade()),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
+ ModelUsageContext::Thread(self.thread.clone()),
window,
cx,
)
@@ -1826,9 +1827,10 @@ impl ActiveThread {
// Get all the data we need from thread before we start using it in closures
let checkpoint = thread.checkpoint_for_message(message_id);
+ let configured_model = thread.configured_model().map(|m| m.model);
let added_context = thread
.context_for_message(message_id)
- .map(|context| AddedContext::new_attached(context, cx))
+ .map(|context| AddedContext::new_attached(context, configured_model.as_ref(), cx))
.collect::<Vec<_>>();
let tool_uses = thread.tool_uses_for_message(message_id, cx);
@@ -23,7 +23,7 @@ use crate::thread_store::{TextThreadStore, ThreadStore};
use crate::ui::{AddedContext, ContextPill};
use crate::{
AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
- RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
+ ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
};
pub struct ContextStrip {
@@ -37,6 +37,7 @@ pub struct ContextStrip {
_subscriptions: Vec<Subscription>,
focused_index: Option<usize>,
children_bounds: Option<Vec<Bounds<Pixels>>>,
+ model_usage_context: ModelUsageContext,
}
impl ContextStrip {
@@ -47,6 +48,7 @@ impl ContextStrip {
text_thread_store: Option<WeakEntity<TextThreadStore>>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
suggest_context_kind: SuggestContextKind,
+ model_usage_context: ModelUsageContext,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -81,6 +83,7 @@ impl ContextStrip {
_subscriptions: subscriptions,
focused_index: None,
children_bounds: None,
+ model_usage_context,
}
}
@@ -98,11 +101,20 @@ impl ContextStrip {
.as_ref()
.and_then(|thread_store| thread_store.upgrade())
.and_then(|thread_store| thread_store.read(cx).prompt_store().as_ref());
+
+ let current_model = self.model_usage_context.language_model(cx);
+
self.context_store
.read(cx)
.context()
.flat_map(|context| {
- AddedContext::new_pending(context.clone(), prompt_store, project, cx)
+ AddedContext::new_pending(
+ context.clone(),
+ prompt_store,
+ project,
+ current_model.as_ref(),
+ cx,
+ )
})
.collect::<Vec<_>>()
} else {
@@ -93,20 +93,9 @@ impl ContextPill {
Self::Suggested {
icon_path: Some(icon_path),
..
- }
- | Self::Added {
- context:
- AddedContext {
- icon_path: Some(icon_path),
- ..
- },
- ..
} => Icon::from_path(icon_path),
- Self::Suggested { kind, .. }
- | Self::Added {
- context: AddedContext { kind, .. },
- ..
- } => Icon::new(kind.icon()),
+ Self::Suggested { kind, .. } => Icon::new(kind.icon()),
+ Self::Added { context, .. } => context.icon(),
}
}
}
@@ -133,6 +122,7 @@ impl RenderOnce for ContextPill {
on_click,
} => {
let status_is_error = matches!(context.status, ContextStatus::Error { .. });
+ let status_is_warning = matches!(context.status, ContextStatus::Warning { .. });
base_pill
.pr(if on_remove.is_some() { px(2.) } else { px(4.) })
@@ -140,6 +130,9 @@ impl RenderOnce for ContextPill {
if status_is_error {
pill.bg(cx.theme().status().error_background)
.border_color(cx.theme().status().error_border)
+ } else if status_is_warning {
+ pill.bg(cx.theme().status().warning_background)
+ .border_color(cx.theme().status().warning_border)
} else if *focused {
pill.bg(color.element_background)
.border_color(color.border_focused)
@@ -195,7 +188,8 @@ impl RenderOnce for ContextPill {
|label, delta| label.opacity(delta),
)
.into_any_element(),
- ContextStatus::Error { message } => element
+ ContextStatus::Warning { message }
+ | ContextStatus::Error { message } => element
.tooltip(ui::Tooltip::text(message.clone()))
.into_any_element(),
}),
@@ -270,6 +264,7 @@ pub enum ContextStatus {
Ready,
Loading { message: SharedString },
Error { message: SharedString },
+ Warning { message: SharedString },
}
#[derive(RegisterComponent)]
@@ -285,6 +280,19 @@ pub struct AddedContext {
}
impl AddedContext {
+ pub fn icon(&self) -> Icon {
+ match &self.status {
+ ContextStatus::Warning { .. } => Icon::new(IconName::Warning).color(Color::Warning),
+ ContextStatus::Error { .. } => Icon::new(IconName::XCircle).color(Color::Error),
+ _ => {
+ if let Some(icon_path) = &self.icon_path {
+ Icon::from_path(icon_path)
+ } else {
+ Icon::new(self.kind.icon())
+ }
+ }
+ }
+ }
/// Creates an `AddedContext` by retrieving relevant details of `AgentContext`. This returns a
/// `None` if `DirectoryContext` or `RulesContext` no longer exist.
///
@@ -293,6 +301,7 @@ impl AddedContext {
handle: AgentContextHandle,
prompt_store: Option<&Entity<PromptStore>>,
project: &Project,
+ model: Option<&Arc<dyn language_model::LanguageModel>>,
cx: &App,
) -> Option<AddedContext> {
match handle {
@@ -304,11 +313,15 @@ impl AddedContext {
AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),
AgentContextHandle::TextThread(handle) => Some(Self::pending_text_thread(handle, cx)),
AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),
- AgentContextHandle::Image(handle) => Some(Self::image(handle, cx)),
+ AgentContextHandle::Image(handle) => Some(Self::image(handle, model, cx)),
}
}
- pub fn new_attached(context: &AgentContext, cx: &App) -> AddedContext {
+ pub fn new_attached(
+ context: &AgentContext,
+ model: Option<&Arc<dyn language_model::LanguageModel>>,
+ cx: &App,
+ ) -> AddedContext {
match context {
AgentContext::File(context) => Self::attached_file(context, cx),
AgentContext::Directory(context) => Self::attached_directory(context),
@@ -318,7 +331,7 @@ impl AddedContext {
AgentContext::Thread(context) => Self::attached_thread(context),
AgentContext::TextThread(context) => Self::attached_text_thread(context),
AgentContext::Rules(context) => Self::attached_rules(context),
- AgentContext::Image(context) => Self::image(context.clone(), cx),
+ AgentContext::Image(context) => Self::image(context.clone(), model, cx),
}
}
@@ -593,7 +606,11 @@ impl AddedContext {
}
}
- fn image(context: ImageContext, cx: &App) -> AddedContext {
+ fn image(
+ context: ImageContext,
+ model: Option<&Arc<dyn language_model::LanguageModel>>,
+ cx: &App,
+ ) -> AddedContext {
let (name, parent, icon_path) = if let Some(full_path) = context.full_path.as_ref() {
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
let (name, parent) =
@@ -604,21 +621,30 @@ impl AddedContext {
("Image".into(), None, None)
};
+ let status = match context.status(model) {
+ ImageStatus::Loading => ContextStatus::Loading {
+ message: "Loadingβ¦".into(),
+ },
+ ImageStatus::Error => ContextStatus::Error {
+ message: "Failed to load Image".into(),
+ },
+ ImageStatus::Warning => ContextStatus::Warning {
+ message: format!(
+ "{} doesn't support attaching Images as Context",
+ model.map(|m| m.name().0).unwrap_or_else(|| "Model".into())
+ )
+ .into(),
+ },
+ ImageStatus::Ready => ContextStatus::Ready,
+ };
+
AddedContext {
kind: ContextKind::Image,
name,
parent,
tooltip: None,
icon_path,
- status: match context.status() {
- ImageStatus::Loading => ContextStatus::Loading {
- message: "Loadingβ¦".into(),
- },
- ImageStatus::Error => ContextStatus::Error {
- message: "Failed to load image".into(),
- },
- ImageStatus::Ready => ContextStatus::Ready,
- },
+ status,
render_hover: Some(Rc::new({
let image = context.original_image.clone();
move |_, cx| {
@@ -787,6 +813,7 @@ impl Component for AddedContext {
original_image: Arc::new(Image::empty()),
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
},
+ None,
cx,
),
);
@@ -806,6 +833,7 @@ impl Component for AddedContext {
})
.shared(),
},
+ None,
cx,
),
);
@@ -820,6 +848,7 @@ impl Component for AddedContext {
original_image: Arc::new(Image::empty()),
image_task: Task::ready(None).shared(),
},
+ None,
cx,
),
);
@@ -841,3 +870,60 @@ impl Component for AddedContext {
)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use gpui::App;
+ use language_model::{LanguageModel, fake_provider::FakeLanguageModel};
+ use std::sync::Arc;
+
+ #[gpui::test]
+ fn test_image_context_warning_for_unsupported_model(cx: &mut App) {
+ let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel::default());
+ assert!(!model.supports_images());
+
+ let image_context = ImageContext {
+ context_id: ContextId::zero(),
+ project_path: None,
+ original_image: Arc::new(Image::empty()),
+ image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
+ full_path: None,
+ };
+
+ let added_context = AddedContext::image(image_context, Some(&model), cx);
+
+ assert!(matches!(
+ added_context.status,
+ ContextStatus::Warning { .. }
+ ));
+
+ assert!(matches!(added_context.kind, ContextKind::Image));
+ assert_eq!(added_context.name.as_ref(), "Image");
+ assert!(added_context.parent.is_none());
+ assert!(added_context.icon_path.is_none());
+ }
+
+ #[gpui::test]
+ fn test_image_context_ready_for_no_model(cx: &mut App) {
+ let image_context = ImageContext {
+ context_id: ContextId::zero(),
+ project_path: None,
+ original_image: Arc::new(Image::empty()),
+ image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
+ full_path: None,
+ };
+
+ let added_context = AddedContext::image(image_context, None, cx);
+
+ assert!(
+ matches!(added_context.status, ContextStatus::Ready),
+ "Expected ready status when no model provided"
+ );
+
+ assert!(matches!(added_context.kind, ContextKind::Image));
+ assert_eq!(added_context.name.as_ref(), "Image");
+ assert!(added_context.parent.is_none());
+ assert!(added_context.icon_path.is_none());
+ }
+}