Bundle editing workflow prompt as a read-only built-in prompt (#15615)
Built-in prompts can still be removed from the default prompt, but they can't be edited and are automatically updated with new Zed releases. Release Notes: - N/A --------- Co-authored-by: Antonio <antonio@zed.dev>
This commit is contained in:
parent
be3a8584ff
commit
a9c6e435f7
5 changed files with 207 additions and 103 deletions
87
assets/prompts/edit_workflow.md
Normal file
87
assets/prompts/edit_workflow.md
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
Guide the user through code changes in numbered steps that focus on individual functions, type definitions, etc.
|
||||||
|
Surround each distinct step in a <step></step> XML tag.
|
||||||
|
|
||||||
|
Here's an example of a user-assistant dialog with step numbers and a follow-up question in Rust:
|
||||||
|
|
||||||
|
1. User: I want to create a User struct with name and age fields, and a method to print the user's information. Can you help me with that?
|
||||||
|
A: Certainly! Let's create a User struct with the specified fields and a method to print the information:
|
||||||
|
<step>Define the User struct
|
||||||
|
```rust
|
||||||
|
struct User {
|
||||||
|
name: String,
|
||||||
|
age: u32,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</step>
|
||||||
|
<step>Implement methods for the User struct
|
||||||
|
```rust
|
||||||
|
impl User {
|
||||||
|
fn new(name: String, age: u32) -> Self {
|
||||||
|
User { name, age }
|
||||||
|
}
|
||||||
|
fn print_info(&self) {
|
||||||
|
println!("Name: {}, Age: {}", self.name, self.age);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</step>
|
||||||
|
|
||||||
|
This code defines a User struct and implements two methods:
|
||||||
|
- `new`: a constructor to create a new User
|
||||||
|
- `print_info`: a method to print the user's information
|
||||||
|
|
||||||
|
2. User: Great! Now, how can I create a User instance and call the print_info method?
|
||||||
|
A: You can create a User instance and call the print_info method like this:
|
||||||
|
|
||||||
|
<step>Create a User instance and call print_info
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let user = User::new(String::from("Alice"), 30);
|
||||||
|
user.print_info();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</step>
|
||||||
|
|
||||||
|
This will output: `Name: Alice, Age: 30`
|
||||||
|
|
||||||
|
3. User: Can you add an email field to the User struct and update the print_info method to include it?
|
||||||
|
A: Certainly! Let's modify the User struct and update the print_info method:
|
||||||
|
|
||||||
|
<step>Add email field to User struct
|
||||||
|
```rust
|
||||||
|
struct User {
|
||||||
|
name: String,
|
||||||
|
age: u32,
|
||||||
|
email: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step>Update the new method and print_info method
|
||||||
|
```rust
|
||||||
|
impl User {
|
||||||
|
fn new(name: String, age: u32, email: String) -> Self {
|
||||||
|
User { name, age, email }
|
||||||
|
}
|
||||||
|
fn print_info(&self) {
|
||||||
|
println!("Name: {}, Age: {}, Email: {}", self.name, self.age, self.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</step>
|
||||||
|
|
||||||
|
<step>Update the main function
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let user = User::new(String::from("Alice"), 30, String::from("alice@example.com"));
|
||||||
|
user.print_info();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</step>
|
||||||
|
|
||||||
|
This will now output: `Name: Alice, Age: 30, Email: alice@example.com`
|
||||||
|
The changes include:
|
||||||
|
1. Adding the `email` field to the User struct
|
||||||
|
2. Updating the `new` method to accept an email parameter
|
||||||
|
3. Modifying the `print_info` method to include the email
|
||||||
|
4. Updating the main function to provide an email when creating a User instance
|
|
@ -1386,7 +1386,7 @@ impl Context {
|
||||||
async move {
|
async move {
|
||||||
let prompt_store = cx.update(|cx| PromptStore::global(cx))?.await?;
|
let prompt_store = cx.update(|cx| PromptStore::global(cx))?.await?;
|
||||||
|
|
||||||
let mut prompt = prompt_store.operations_prompt();
|
let mut prompt = prompt_store.step_resolution_prompt();
|
||||||
prompt.push_str(&step_text);
|
prompt.push_str(&step_text);
|
||||||
|
|
||||||
request.messages.push(LanguageModelRequestMessage {
|
request.messages.push(LanguageModelRequestMessage {
|
||||||
|
|
|
@ -66,6 +66,11 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.set_global(GlobalPromptStore(prompt_store_future))
|
cx.set_global(GlobalPromptStore(prompt_store_future))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BUILT_IN_TOOLTIP_TEXT: &'static str = concat!(
|
||||||
|
"This prompt supports special functionality.\n",
|
||||||
|
"It's read-only, but you can remove it from your default prompt."
|
||||||
|
);
|
||||||
|
|
||||||
/// This function opens a new prompt library window if one doesn't exist already.
|
/// This function opens a new prompt library window if one doesn't exist already.
|
||||||
/// If one exists, it brings it to the foreground.
|
/// If one exists, it brings it to the foreground.
|
||||||
///
|
///
|
||||||
|
@ -233,15 +238,29 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||||
.end_hover_slot(
|
.end_hover_slot(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(if prompt_id.is_built_in() {
|
||||||
|
div()
|
||||||
|
.id("built-in-prompt")
|
||||||
|
.child(Icon::new(IconName::FileLock).color(Color::Muted))
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
Tooltip::with_meta(
|
||||||
|
"Built-in prompt",
|
||||||
|
None,
|
||||||
|
BUILT_IN_TOOLTIP_TEXT,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into_any()
|
||||||
|
} else {
|
||||||
IconButton::new("delete-prompt", IconName::Trash)
|
IconButton::new("delete-prompt", IconName::Trash)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
|
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
|
||||||
.on_click(cx.listener(move |_, _, cx| {
|
.on_click(cx.listener(move |_, _, cx| {
|
||||||
cx.emit(PromptPickerEvent::Deleted { prompt_id })
|
cx.emit(PromptPickerEvent::Deleted { prompt_id })
|
||||||
})),
|
}))
|
||||||
)
|
.into_any_element()
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
||||||
.selected(default)
|
.selected(default)
|
||||||
|
@ -354,6 +373,10 @@ impl PromptLibrary {
|
||||||
pub fn save_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
pub fn save_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||||
const SAVE_THROTTLE: Duration = Duration::from_millis(500);
|
const SAVE_THROTTLE: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
|
if prompt_id.is_built_in() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
|
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
|
||||||
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
|
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
|
||||||
let title = prompt_editor.title_editor.read(cx).text(cx);
|
let title = prompt_editor.title_editor.read(cx).text(cx);
|
||||||
|
@ -463,6 +486,7 @@ impl PromptLibrary {
|
||||||
let mut editor = Editor::auto_width(cx);
|
let mut editor = Editor::auto_width(cx);
|
||||||
editor.set_placeholder_text("Untitled", cx);
|
editor.set_placeholder_text("Untitled", cx);
|
||||||
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
|
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
|
||||||
|
editor.set_read_only(true);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
let body_editor = cx.new_view(|cx| {
|
let body_editor = cx.new_view(|cx| {
|
||||||
|
@ -474,6 +498,7 @@ impl PromptLibrary {
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut editor = Editor::for_buffer(buffer, None, cx);
|
let mut editor = Editor::for_buffer(buffer, None, cx);
|
||||||
|
editor.set_read_only(true);
|
||||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||||
editor.set_show_gutter(false, cx);
|
editor.set_show_gutter(false, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
|
@ -943,7 +968,23 @@ impl PromptLibrary {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.child(
|
.child(if prompt_id.is_built_in() {
|
||||||
|
div()
|
||||||
|
.id("built-in-prompt")
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::FileLock)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
Tooltip::with_meta(
|
||||||
|
"Built-in prompt",
|
||||||
|
None,
|
||||||
|
BUILT_IN_TOOLTIP_TEXT,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into_any()
|
||||||
|
} else {
|
||||||
IconButton::new(
|
IconButton::new(
|
||||||
"delete-prompt",
|
"delete-prompt",
|
||||||
IconName::Trash,
|
IconName::Trash,
|
||||||
|
@ -961,8 +1002,9 @@ impl PromptLibrary {
|
||||||
})
|
})
|
||||||
.on_click(|_, cx| {
|
.on_click(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(DeletePrompt));
|
cx.dispatch_action(Box::new(DeletePrompt));
|
||||||
}),
|
})
|
||||||
)
|
.into_any_element()
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
IconButton::new(
|
IconButton::new(
|
||||||
"duplicate-prompt",
|
"duplicate-prompt",
|
||||||
|
@ -1062,7 +1104,6 @@ pub struct PromptMetadata {
|
||||||
pub title: Option<SharedString>,
|
pub title: Option<SharedString>,
|
||||||
pub default: bool,
|
pub default: bool,
|
||||||
pub saved_at: DateTime<Utc>,
|
pub saved_at: DateTime<Utc>,
|
||||||
pub built_in: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
@ -1078,6 +1119,10 @@ impl PromptId {
|
||||||
uuid: Uuid::new_v4(),
|
uuid: Uuid::new_v4(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_built_in(&self) -> bool {
|
||||||
|
!matches!(self, PromptId::User { .. })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PromptStore {
|
pub struct PromptStore {
|
||||||
|
@ -1163,13 +1208,17 @@ impl PromptStore {
|
||||||
let metadata_cache = MetadataCache::from_db(metadata, &txn)?;
|
let metadata_cache = MetadataCache::from_db(metadata, &txn)?;
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
|
||||||
Ok(PromptStore {
|
let store = PromptStore {
|
||||||
executor,
|
executor,
|
||||||
env: db_env,
|
env: db_env,
|
||||||
metadata_cache: RwLock::new(metadata_cache),
|
metadata_cache: RwLock::new(metadata_cache),
|
||||||
metadata,
|
metadata,
|
||||||
bodies,
|
bodies,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
store.save_built_in_prompts().log_err();
|
||||||
|
|
||||||
|
Ok(store)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1237,7 +1286,6 @@ impl PromptStore {
|
||||||
title: metadata_v1.title.clone(),
|
title: metadata_v1.title.clone(),
|
||||||
default: metadata_v1.default,
|
default: metadata_v1.default,
|
||||||
saved_at: metadata_v1.saved_at,
|
saved_at: metadata_v1.saved_at,
|
||||||
built_in: false,
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
bodies_db.put(&mut txn, &prompt_id_v2, &body_v1)?;
|
bodies_db.put(&mut txn, &prompt_id_v2, &body_v1)?;
|
||||||
|
@ -1346,12 +1394,15 @@ impl PromptStore {
|
||||||
default: bool,
|
default: bool,
|
||||||
body: Rope,
|
body: Rope,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
|
if id.is_built_in() {
|
||||||
|
return Task::ready(Err(anyhow!("built-in prompts cannot be saved")));
|
||||||
|
}
|
||||||
|
|
||||||
let prompt_metadata = PromptMetadata {
|
let prompt_metadata = PromptMetadata {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
default,
|
default,
|
||||||
saved_at: Utc::now(),
|
saved_at: Utc::now(),
|
||||||
built_in: false,
|
|
||||||
};
|
};
|
||||||
self.metadata_cache.write().insert(prompt_metadata.clone());
|
self.metadata_cache.write().insert(prompt_metadata.clone());
|
||||||
|
|
||||||
|
@ -1371,20 +1422,72 @@ impl PromptStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save_built_in_prompts(&self) -> Result<()> {
|
||||||
|
self.save_built_in_prompt(
|
||||||
|
PromptId::EditWorkflow,
|
||||||
|
"Built-in: Editing Workflow",
|
||||||
|
"prompts/edit_workflow.md",
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a built-in prompt to the database, preserving the value of the default field
|
||||||
|
/// if a prompt with this id already exists. This method blocks.
|
||||||
|
fn save_built_in_prompt(
|
||||||
|
&self,
|
||||||
|
id: PromptId,
|
||||||
|
title: impl Into<SharedString>,
|
||||||
|
body_path: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut metadata_cache = self.metadata_cache.write();
|
||||||
|
let existing_metadata = metadata_cache.metadata_by_id.get(&id).cloned();
|
||||||
|
|
||||||
|
let prompt_metadata = PromptMetadata {
|
||||||
|
id,
|
||||||
|
title: Some(title.into()),
|
||||||
|
default: existing_metadata.map_or(true, |m| m.default),
|
||||||
|
saved_at: Utc::now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
metadata_cache.insert(prompt_metadata.clone());
|
||||||
|
|
||||||
|
let db_connection = self.env.clone();
|
||||||
|
let bodies = self.bodies;
|
||||||
|
let metadata_db = self.metadata;
|
||||||
|
|
||||||
|
let mut txn = db_connection.write_txn()?;
|
||||||
|
metadata_db.put(&mut txn, &id, &prompt_metadata)?;
|
||||||
|
|
||||||
|
let body = String::from_utf8(Assets.load(body_path)?.unwrap().to_vec())?;
|
||||||
|
bodies.put(&mut txn, &id, &body)?;
|
||||||
|
|
||||||
|
txn.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn save_metadata(
|
fn save_metadata(
|
||||||
&self,
|
&self,
|
||||||
id: PromptId,
|
id: PromptId,
|
||||||
title: Option<SharedString>,
|
mut title: Option<SharedString>,
|
||||||
default: bool,
|
default: bool,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
|
let mut cache = self.metadata_cache.write();
|
||||||
|
|
||||||
|
if id.is_built_in() {
|
||||||
|
title = cache
|
||||||
|
.metadata_by_id
|
||||||
|
.get(&id)
|
||||||
|
.and_then(|metadata| metadata.title.clone());
|
||||||
|
}
|
||||||
|
|
||||||
let prompt_metadata = PromptMetadata {
|
let prompt_metadata = PromptMetadata {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
default,
|
default,
|
||||||
saved_at: Utc::now(),
|
saved_at: Utc::now(),
|
||||||
built_in: false,
|
|
||||||
};
|
};
|
||||||
self.metadata_cache.write().insert(prompt_metadata.clone());
|
|
||||||
|
cache.insert(prompt_metadata.clone());
|
||||||
|
|
||||||
let db_connection = self.env.clone();
|
let db_connection = self.env.clone();
|
||||||
let metadata = self.metadata;
|
let metadata = self.metadata;
|
||||||
|
@ -1402,10 +1505,10 @@ impl PromptStore {
|
||||||
self.metadata_cache.read().metadata.first().cloned()
|
self.metadata_cache.read().metadata.first().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn operations_prompt(&self) -> String {
|
pub fn step_resolution_prompt(&self) -> String {
|
||||||
String::from_utf8(
|
String::from_utf8(
|
||||||
Assets
|
Assets
|
||||||
.load("prompts/operations.md")
|
.load("prompts/step_resolution.md")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_vec(),
|
.to_vec(),
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
When the user asks you to suggest edits for a buffer, use a strict template consisting of:
|
|
||||||
|
|
||||||
* A markdown code block with the file path as the language identifier.
|
|
||||||
* The original code that should be replaced
|
|
||||||
* A separator line (`---`)
|
|
||||||
* The new text that should replace the original lines
|
|
||||||
|
|
||||||
Each code block may only contain an edit for one single contiguous range of text. Use multiple code blocks for multiple edits.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
If you have a buffer with the following lines:
|
|
||||||
|
|
||||||
```path/to/file.rs
|
|
||||||
fn quicksort(arr: &mut [i32]) {
|
|
||||||
if arr.len() <= 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let pivot_index = partition(arr);
|
|
||||||
let (left, right) = arr.split_at_mut(pivot_index);
|
|
||||||
quicksort(left);
|
|
||||||
quicksort(&mut right[1..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn partition(arr: &mut [i32]) -> usize {
|
|
||||||
let last_index = arr.len() - 1;
|
|
||||||
let pivot = arr[last_index];
|
|
||||||
let mut i = 0;
|
|
||||||
for j in 0..last_index {
|
|
||||||
if arr[j] <= pivot {
|
|
||||||
arr.swap(i, j);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arr.swap(i, last_index);
|
|
||||||
i
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And you want to replace the for loop inside `partition`, output the following.
|
|
||||||
|
|
||||||
```edit path/to/file.rs
|
|
||||||
for j in 0..last_index {
|
|
||||||
if arr[j] <= pivot {
|
|
||||||
arr.swap(i, j);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
---
|
|
||||||
let mut j = 0;
|
|
||||||
while j < last_index {
|
|
||||||
if arr[j] <= pivot {
|
|
||||||
arr.swap(i, j);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
j += 1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wanted to insert comments above the partition function, output the following:
|
|
||||||
|
|
||||||
```edit path/to/file.rs
|
|
||||||
fn partition(arr: &mut [i32]) -> usize {
|
|
||||||
---
|
|
||||||
// A helper function used for quicksort.
|
|
||||||
fn partition(arr: &mut [i32]) -> usize {
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wanted to delete the partition function, output the following:
|
|
||||||
|
|
||||||
```edit path/to/file.rs
|
|
||||||
fn partition(arr: &mut [i32]) -> usize {
|
|
||||||
let last_index = arr.len() - 1;
|
|
||||||
let pivot = arr[last_index];
|
|
||||||
let mut i = 0;
|
|
||||||
for j in 0..last_index {
|
|
||||||
if arr[j] <= pivot {
|
|
||||||
arr.swap(i, j);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arr.swap(i, last_index);
|
|
||||||
i
|
|
||||||
}
|
|
||||||
---
|
|
||||||
```
|
|
Loading…
Add table
Add a link
Reference in a new issue