diff --git a/crates/agent_ui/src/completion_provider.rs b/crates/agent_ui/src/completion_provider.rs index efade13bfd..37074aa35a 100644 --- a/crates/agent_ui/src/completion_provider.rs +++ b/crates/agent_ui/src/completion_provider.rs @@ -22,7 +22,7 @@ use project::{ Completion, CompletionDisplayOptions, CompletionGroup, CompletionIntent, CompletionResponse, DiagnosticSummary, PathMatchCandidateSet, Project, ProjectPath, Symbol, WorktreeId, }; -use prompt_store::{PromptStore, UserPromptId}; + use rope::Point; use settings::Settings; use terminal::terminal_settings::TerminalSettings; @@ -158,7 +158,7 @@ pub(crate) enum PromptContextType { Symbol, Fetch, Thread, - Rules, + Skill, Diagnostics, BranchDiff, } @@ -197,7 +197,7 @@ impl TryFrom<&str> for PromptContextType { "symbol" => Ok(Self::Symbol), "fetch" => Ok(Self::Fetch), "thread" => Ok(Self::Thread), - "rule" => Ok(Self::Rules), + "skill" => Ok(Self::Skill), "diagnostics" => Ok(Self::Diagnostics), "diff" => Ok(Self::BranchDiff), _ => Err(format!("Invalid context picker mode: {}", value)), @@ -212,7 +212,7 @@ impl PromptContextType { Self::Symbol => "symbol", Self::Fetch => "fetch", Self::Thread => "thread", - Self::Rules => "rule", + Self::Skill => "skill", Self::Diagnostics => "diagnostics", Self::BranchDiff => "branch diff", } @@ -224,7 +224,7 @@ impl PromptContextType { Self::Symbol => "Symbols", Self::Fetch => "Fetch", Self::Thread => "Threads", - Self::Rules => "Rules", + Self::Skill => "Skills", Self::Diagnostics => "Diagnostics", Self::BranchDiff => "Branch Diff", } @@ -236,7 +236,7 @@ impl PromptContextType { Self::Symbol => IconName::Code, Self::Fetch => IconName::ToolWeb, Self::Thread => IconName::Thread, - Self::Rules => IconName::Reader, + Self::Skill => IconName::Sparkle, Self::Diagnostics => IconName::Warning, Self::BranchDiff => IconName::GitBranch, } @@ -249,7 +249,7 @@ pub(crate) enum Match { Thread(SessionMatch), RecentThread(SessionMatch), Fetch(SharedString), - Rules(RulesContextEntry), + Skill(AvailableSkill), Entry(EntryMatch), BranchDiff(BranchDiffMatch), } @@ -267,7 +267,7 @@ impl Match { Match::Thread(_) => 1., Match::RecentThread(_) => 1., Match::Symbol(_) => 1., - Match::Rules(_) => 1., + Match::Skill(_) => 1., Match::Fetch(_) => 1., Match::BranchDiff(_) => 1., } @@ -291,12 +291,6 @@ fn session_title(title: Option) -> SharedString { .unwrap_or_else(|| SharedString::new_static(DEFAULT_THREAD_TITLE)) } -#[derive(Debug, Clone)] -pub struct RulesContextEntry { - pub prompt_id: UserPromptId, - pub title: SharedString, -} - #[derive(Debug, Clone)] pub struct AvailableSkill { pub name: Arc, @@ -354,7 +348,6 @@ pub struct PromptCompletionProvider { source: Arc, editor: WeakEntity, mention_set: Entity, - prompt_store: Option>, workspace: WeakEntity, } @@ -363,7 +356,6 @@ impl PromptCompletionProvider { source: T, editor: WeakEntity, mention_set: Entity, - prompt_store: Option>, workspace: WeakEntity, ) -> Self { Self { @@ -371,7 +363,6 @@ impl PromptCompletionProvider { editor, mention_set, workspace, - prompt_store, } } @@ -460,8 +451,8 @@ impl PromptCompletionProvider { } } - fn completion_for_rules( - rule: RulesContextEntry, + fn completion_for_skill( + skill: AvailableSkill, source_range: Range, source: Arc, editor: WeakEntity, @@ -469,25 +460,35 @@ impl PromptCompletionProvider { workspace: Entity, cx: &mut App, ) -> Completion { - let uri = MentionUri::Rule { - id: rule.prompt_id.into(), - name: rule.title.to_string(), + let uri = MentionUri::Skill { + name: skill.name.to_string(), + source: skill.source.to_string(), + skill_file_path: skill.skill_file_path.clone(), }; let new_text = format!("{} ", uri.as_link()); let new_text_len = new_text.len(); let icon_path = uri.icon_path(cx); + let crease_text: SharedString = uri.name().into(); + let source_highlight_id = cx + .theme() + .syntax() + .highlight_id("variable") + .map(HighlightId::new); + let label = build_slash_item_label(&skill.name, Some(&skill.source), source_highlight_id); Completion { replace_range: source_range.clone(), new_text, - label: CodeLabel::plain(rule.title.to_string(), None), - documentation: None, + label, + documentation: Some(CompletionDocumentation::MultiLinePlainText( + skill.description.into(), + )), insert_text_mode: None, source: project::CompletionSource::Custom, match_start: None, snippet_deduplication_key: None, icon_path: Some(icon_path), confirm: Some(confirm_completion_callback( - rule.title, + crease_text, source_range.start, new_text_len - 1, uri, @@ -989,20 +990,16 @@ impl PromptCompletionProvider { } } - Some(PromptContextType::Rules) => { - if let Some(prompt_store) = self.prompt_store.as_ref() { - let search_rules_task = - search_rules(query, cancellation_flag, prompt_store, cx); - cx.background_spawn(async move { - search_rules_task - .await - .into_iter() - .map(Match::Rules) - .collect::>() - }) - } else { - Task::ready(Vec::new()) - } + Some(PromptContextType::Skill) => { + let skills = self.source.available_skills(cx); + let search_skills_task = search_skills(query, cancellation_flag, skills, cx); + cx.background_spawn(async move { + search_skills_task + .await + .into_iter() + .map(Match::Skill) + .collect::>() + }) } Some(PromptContextType::Diagnostics) => Task::ready(Vec::new()), @@ -1231,9 +1228,10 @@ impl PromptCompletionProvider { )); } - if self.prompt_store.is_some() && self.source.supports_context(PromptContextType::Rules, cx) + if self.source.supports_context(PromptContextType::Skill, cx) + && !self.source.available_skills(cx).is_empty() { - entries.push(PromptContextEntry::Mode(PromptContextType::Rules)); + entries.push(PromptContextEntry::Mode(PromptContextType::Skill)); } if self.source.supports_context(PromptContextType::Fetch, cx) { @@ -1617,8 +1615,8 @@ impl CompletionProvider for PromptCompletio cx, )) } - Match::Rules(user_rules) => Some(Self::completion_for_rules( - user_rules, + Match::Skill(skill) => Some(Self::completion_for_skill( + skill, source_range.clone(), source.clone(), editor.clone(), @@ -2281,29 +2279,36 @@ async fn filter_sessions( .collect() } -pub(crate) fn search_rules( +pub(crate) fn search_skills( query: String, cancellation_flag: Arc, - prompt_store: &Entity, + skills: Vec, cx: &mut App, -) -> Task> { - let search_task = prompt_store.read(cx).search(query, cancellation_flag, cx); +) -> Task> { + if skills.is_empty() { + return Task::ready(Vec::new()); + } + let executor = cx.background_executor().clone(); cx.background_spawn(async move { - search_task - .await + let candidates = skills + .iter() + .enumerate() + .map(|(id, skill)| StringMatchCandidate::new(id, &skill.name)) + .collect::>(); + let matches = fuzzy::match_strings( + &candidates, + &query, + false, + true, + 100, + &cancellation_flag, + executor, + ) + .await; + matches .into_iter() - .flat_map(|metadata| { - // Default prompts are filtered out as they are automatically included. - if metadata.default { - None - } else { - Some(RulesContextEntry { - prompt_id: metadata.id.as_user()?, - title: metadata.title?, - }) - } - }) - .collect::>() + .map(|mat| skills[mat.candidate_id].clone()) + .collect() }) } diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index b282ffc626..3bd2fb6326 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -4229,8 +4229,8 @@ impl ThreadView { }), ) .item( - ContextMenuEntry::new("Rules") - .icon(IconName::Reader) + ContextMenuEntry::new("Skills") + .icon(IconName::Sparkle) .icon_color(Color::Muted) .icon_size(IconSize::XSmall) .handler({ @@ -4238,7 +4238,7 @@ impl ThreadView { move |window, cx| { message_editor.focus_handle(cx).focus(window, cx); message_editor.update(cx, |editor, cx| { - editor.insert_context_type("rule", window, cx); + editor.insert_context_type("skill", window, cx); }); } }), diff --git a/crates/agent_ui/src/inline_prompt_editor.rs b/crates/agent_ui/src/inline_prompt_editor.rs index dcc6a5b326..31bdf83da1 100644 --- a/crates/agent_ui/src/inline_prompt_editor.rs +++ b/crates/agent_ui/src/inline_prompt_editor.rs @@ -63,7 +63,6 @@ pub struct PromptEditor { pub editor: Entity, mode: PromptEditorMode, mention_set: Entity, - prompt_store: Option>, workspace: WeakEntity, model_selector: Entity, edited_since_done: bool, @@ -334,7 +333,6 @@ impl PromptEditor { PromptEditorCompletionProviderDelegate, cx.weak_entity(), self.mention_set.clone(), - self.prompt_store.clone(), self.workspace.clone(), )))); }); @@ -1214,7 +1212,7 @@ impl PromptCompletionProviderDelegate for PromptEditorCompletionProviderDelegate PromptContextType::Symbol, PromptContextType::Thread, PromptContextType::Fetch, - PromptContextType::Rules, + PromptContextType::Skill, ] } @@ -1286,7 +1284,6 @@ impl PromptEditor { let mut this: PromptEditor = PromptEditor { editor: prompt_editor.clone(), mention_set, - prompt_store, workspace, model_selector: cx.new(|cx| { AgentModelSelector::new( @@ -1438,7 +1435,6 @@ impl PromptEditor { let mut this = Self { editor: prompt_editor.clone(), mention_set, - prompt_store, workspace, model_selector: cx.new(|cx| { AgentModelSelector::new( diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 7bfea72593..8a152239c1 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -100,7 +100,7 @@ impl SessionCapabilities { supported.extend(&[ PromptContextType::Diagnostics, PromptContextType::Fetch, - PromptContextType::Rules, + PromptContextType::Skill, PromptContextType::BranchDiff, ]); } @@ -516,7 +516,6 @@ impl MessageEditor { }, editor.downgrade(), mention_set.clone(), - prompt_store.clone(), workspace.clone(), )); editor.update(cx, |editor, _cx| {