agent settings refactor: Explicit `ElementId` in `InstructionListItem`

Michael Sloan created

When adding a button with the same label to the codestral settings, the second instance was unclickable

Change summary

crates/language_models/src/provider/anthropic.rs       |  1 
crates/language_models/src/provider/bedrock.rs         |  4 +++
crates/language_models/src/provider/deepseek.rs        |  1 
crates/language_models/src/provider/google.rs          |  1 
crates/language_models/src/provider/mistral.rs         |  1 
crates/language_models/src/provider/ollama.rs          |  1 
crates/language_models/src/provider/open_ai.rs         |  1 
crates/language_models/src/provider/open_router.rs     |  1 
crates/language_models/src/provider/vercel.rs          |  1 
crates/language_models/src/provider/x_ai.rs            |  1 
crates/language_models/src/ui/instruction_list_item.rs | 13 ++++++-----
11 files changed, 20 insertions(+), 6 deletions(-)

Detailed changes

crates/language_models/src/provider/anthropic.rs 🔗

@@ -924,6 +924,7 @@ impl Render for ConfigurationView {
                     List::new()
                         .child(
                             InstructionListItem::new(
+                                "anthropic-console-keys",
                                 "Create one by visiting",
                                 Some("Anthropic's settings"),
                                 Some("https://console.anthropic.com/settings/keys")

crates/language_models/src/provider/bedrock.rs 🔗

@@ -1208,6 +1208,7 @@ impl Render for ConfigurationView {
                 List::new()
                     .child(
                         InstructionListItem::new(
+                            "bedrock-prerequisites",
                             "Grant permissions to the strategy you'll use according to the:",
                             Some("Prerequisites"),
                             Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"),
@@ -1215,6 +1216,7 @@ impl Render for ConfigurationView {
                     )
                     .child(
                         InstructionListItem::new(
+                            "bedrock-model-catalog",
                             "Select the models you would like access to:",
                             Some("Bedrock Model Catalog"),
                             Some("https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess"),
@@ -1260,11 +1262,13 @@ impl ConfigurationView {
             .child(
                 List::new()
                     .child(InstructionListItem::new(
+                        "bedrock-iam-console",
                         "Create an IAM user in the AWS console with programmatic access",
                         Some("IAM Console"),
                         Some("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users"),
                     ))
                     .child(InstructionListItem::new(
+                        "bedrock-user",
                         "Attach the necessary Bedrock permissions to this ",
                         Some("user"),
                         Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"),

crates/language_models/src/provider/deepseek.rs 🔗

@@ -612,6 +612,7 @@ impl Render for ConfigurationView {
                 .child(
                     List::new()
                         .child(InstructionListItem::new(
+                            "deepseek-console",
                             "Get your API key from the",
                             Some("DeepSeek console"),
                             Some("https://platform.deepseek.com/api_keys"),

crates/language_models/src/provider/google.rs 🔗

@@ -849,6 +849,7 @@ impl Render for ConfigurationView {
                 .child(
                     List::new()
                         .child(InstructionListItem::new(
+                            "google-ai-console",
                             "Create one by visiting",
                             Some("Google AI's console"),
                             Some("https://aistudio.google.com/app/apikey"),

crates/language_models/src/provider/mistral.rs 🔗

@@ -782,6 +782,7 @@ impl Render for ConfigurationView {
                 .child(
                     List::new()
                         .child(InstructionListItem::new(
+                            "mistral-console",
                             "Create one by visiting",
                             Some("Mistral's console"),
                             Some("https://console.mistral.ai/api-keys"),

crates/language_models/src/provider/ollama.rs 🔗

@@ -733,6 +733,7 @@ impl ConfigurationView {
             .child(
                 List::new()
                     .child(InstructionListItem::new(
+                        "ollama-console",
                         "Download and install Ollama from",
                         Some("ollama.com"),
                         Some("https://ollama.com/download"),

crates/language_models/src/provider/open_ai.rs 🔗

@@ -768,6 +768,7 @@ impl Render for ConfigurationView {
                 .child(
                     List::new()
                         .child(InstructionListItem::new(
+                            "openai-console",
                             "Create one by visiting",
                             Some("OpenAI's console"),
                             Some("https://platform.openai.com/api-keys"),

crates/language_models/src/provider/open_router.rs 🔗

@@ -788,6 +788,7 @@ impl Render for ConfigurationView {
                 .child(
                     List::new()
                         .child(InstructionListItem::new(
+                            "openrouter-console",
                             "Create an API key by visiting",
                             Some("OpenRouter's console"),
                             Some("https://openrouter.ai/keys"),

crates/language_models/src/provider/vercel.rs 🔗

@@ -456,6 +456,7 @@ impl Render for ConfigurationView {
                 .child(
                     List::new()
                         .child(InstructionListItem::new(
+                            "vercel-console",
                             "Create one by visiting",
                             Some("Vercel v0's console"),
                             Some("https://v0.dev/chat/settings/keys"),

crates/language_models/src/provider/x_ai.rs 🔗

@@ -453,6 +453,7 @@ impl Render for ConfigurationView {
                 .child(
                     List::new()
                         .child(InstructionListItem::new(
+                            "xai-console",
                             "Create one by visiting",
                             Some("xAI console"),
                             Some("https://console.x.ai/team/default/api-keys"),

crates/language_models/src/ui/instruction_list_item.rs 🔗

@@ -3,6 +3,7 @@ use ui::{ListItem, prelude::*};
 
 /// A reusable list item component for adding LLM provider configuration instructions
 pub struct InstructionListItem {
+    id: ElementId,
     label: SharedString,
     button_label: Option<SharedString>,
     button_link: Option<String>,
@@ -10,11 +11,13 @@ pub struct InstructionListItem {
 
 impl InstructionListItem {
     pub fn new(
+        id: impl Into<ElementId>,
         label: impl Into<SharedString>,
         button_label: Option<impl Into<SharedString>>,
         button_link: Option<impl Into<String>>,
     ) -> Self {
         Self {
+            id: id.into(),
             label: label.into(),
             button_label: button_label.map(|l| l.into()),
             button_link: button_link.map(|l| l.into()),
@@ -23,6 +26,7 @@ impl InstructionListItem {
 
     pub fn text_only(label: impl Into<SharedString>) -> Self {
         Self {
+            id: id.into(),
             label: label.into(),
             button_label: None,
             button_link: None,
@@ -37,26 +41,23 @@ impl IntoElement for InstructionListItem {
         let item_content = if let (Some(button_label), Some(button_link)) =
             (self.button_label, self.button_link)
         {
-            let link = button_link;
-            let unique_id = SharedString::from(format!("{}-button", self.label));
-
             h_flex()
                 .flex_wrap()
                 .child(Label::new(self.label))
                 .child(
-                    Button::new(unique_id, button_label)
+                    Button::new("button", button_label)
                         .style(ButtonStyle::Subtle)
                         .icon(IconName::ArrowUpRight)
                         .icon_size(IconSize::Small)
                         .icon_color(Color::Muted)
-                        .on_click(move |_, _window, cx| cx.open_url(&link)),
+                        .on_click(move |_, _window, cx| cx.open_url(&button_link)),
                 )
                 .into_any_element()
         } else {
             Label::new(self.label).into_any_element()
         };
 
-        ListItem::new("list-item")
+        ListItem::new(self.id)
             .selectable(false)
             .start_slot(
                 Icon::new(IconName::Dash)