{
+ div()
+ .w_2_3()
+ .h_full()
+ .id("prompt-editor")
+ .border_l_1()
+ .border_color(cx.theme().colors().border)
+ .bg(cx.theme().colors().editor_background)
+ .flex_none()
+ .min_w_64()
+ .children(self.active_prompt_id.and_then(|prompt_id| {
+ let prompt_metadata = self.store.metadata(prompt_id)?;
+ let editor = self.prompt_editors[&prompt_id].editor.clone();
+ Some(
+ v_flex()
+ .size_full()
+ .child(
+ h_flex()
+ .h(TitleBar::height(cx))
+ .px(Spacing::Large.rems(cx))
+ .justify_end()
+ .child(
+ h_flex()
+ .gap_4()
+ .child(
+ IconButton::new(
+ "toggle-default-prompt",
+ if prompt_metadata.default {
+ IconName::StarFilled
+ } else {
+ IconName::Star
+ },
+ )
+ .shape(IconButtonShape::Square)
+ .tooltip(move |cx| {
+ Tooltip::for_action(
+ if prompt_metadata.default {
+ "Remove from Default Prompt"
+ } else {
+ "Add to Default Prompt"
+ },
+ &ToggleDefaultPrompt,
+ cx,
+ )
+ })
+ .on_click(
+ |_, cx| {
+ cx.dispatch_action(Box::new(
+ ToggleDefaultPrompt,
+ ));
+ },
+ ),
+ )
+ .child(
+ IconButton::new("delete-prompt", IconName::Trash)
+ .shape(IconButtonShape::Square)
+ .tooltip(move |cx| {
+ Tooltip::for_action(
+ "Delete Prompt",
+ &DeletePrompt,
+ cx,
+ )
+ })
+ .on_click(|_, cx| {
+ cx.dispatch_action(Box::new(DeletePrompt));
+ }),
+ ),
+ ),
+ )
+ .child(div().flex_grow().p(Spacing::Large.rems(cx)).child(editor)),
+ )
+ }))
+ }
+}
+
+impl Render for PromptLibrary {
+ fn render(&mut self, cx: &mut ViewContext
) -> impl IntoElement {
+ h_flex()
+ .id("prompt-manager")
+ .key_context("PromptLibrary")
+ .on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
+ .on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
+ .on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
+ this.toggle_default_for_active_prompt(cx)
+ }))
+ .size_full()
+ .overflow_hidden()
+ .child(self.render_prompt_list(cx))
+ .child(self.render_active_prompt(cx))
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct PromptMetadata {
+ pub id: PromptId,
+ pub title: Option,
+ pub default: bool,
+ pub saved_at: DateTime,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub struct PromptId(Uuid);
+
+impl PromptId {
+ pub fn new() -> PromptId {
+ PromptId(Uuid::new_v4())
+ }
+}
+
+pub struct PromptStore {
+ executor: BackgroundExecutor,
+ env: heed::Env,
+ bodies: Database, SerdeBincode>,
+ metadata: Database, SerdeBincode>,
+ metadata_cache: RwLock,
+ updates: (Arc>, async_watch::Receiver<()>),
+}
+
+#[derive(Default)]
+struct MetadataCache {
+ metadata: Vec,
+ metadata_by_id: HashMap,
+}
+
+impl MetadataCache {
+ fn from_db(
+ db: Database, SerdeBincode>,
+ txn: &RoTxn,
+ ) -> Result {
+ let mut cache = MetadataCache::default();
+ for result in db.iter(txn)? {
+ let (prompt_id, metadata) = result?;
+ cache.metadata.push(metadata.clone());
+ cache.metadata_by_id.insert(prompt_id, metadata);
+ }
+ cache
+ .metadata
+ .sort_unstable_by_key(|metadata| Reverse(metadata.saved_at));
+ Ok(cache)
+ }
+
+ fn insert(&mut self, metadata: PromptMetadata) {
+ self.metadata_by_id.insert(metadata.id, metadata.clone());
+ if let Some(old_metadata) = self.metadata.iter_mut().find(|m| m.id == metadata.id) {
+ *old_metadata = metadata;
+ } else {
+ self.metadata.push(metadata);
+ }
+ self.metadata.sort_by_key(|m| Reverse(m.saved_at));
+ }
+
+ fn remove(&mut self, id: PromptId) {
+ self.metadata.retain(|metadata| metadata.id != id);
+ self.metadata_by_id.remove(&id);
+ }
+}
+
+impl PromptStore {
+ pub fn global(cx: &AppContext) -> impl Future