diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index a7cb831b53..33da6d7a75 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -53,7 +53,8 @@ actions!( FocusLeft, FocusRight, RemoveFocusedContext, - AcceptSuggestedContext + AcceptSuggestedContext, + OpenActiveThreadAsMarkdown ] ); diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 3b9c45bec0..266ccb96ff 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -11,7 +11,7 @@ use assistant_slash_command::SlashCommandWorkingSet; use assistant_tool::ToolWorkingSet; use client::zed_urls; -use editor::Editor; +use editor::{Editor, MultiBuffer}; use fs::Fs; use gpui::{ prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter, @@ -38,7 +38,10 @@ use crate::message_editor::MessageEditor; use crate::thread::{Thread, ThreadError, ThreadId}; use crate::thread_history::{PastContext, PastThread, ThreadHistory}; use crate::thread_store::ThreadStore; -use crate::{InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory}; +use crate::{ + InlineAssistant, NewPromptEditor, NewThread, OpenActiveThreadAsMarkdown, OpenConfiguration, + OpenHistory, +}; pub fn init(cx: &mut App) { cx.observe_new( @@ -411,6 +414,70 @@ impl AssistantPanel { } } + pub(crate) fn open_active_thread_as_markdown( + &mut self, + _: &OpenActiveThreadAsMarkdown, + window: &mut Window, + cx: &mut Context, + ) { + let Some(workspace) = self + .workspace + .upgrade() + .ok_or_else(|| anyhow!("workspace dropped")) + .log_err() + else { + return; + }; + + let markdown_language_task = workspace + .read(cx) + .app_state() + .languages + .language_for_name("Markdown"); + let thread = self.active_thread(cx); + cx.spawn_in(window, |_this, mut cx| async move { + let markdown_language = markdown_language_task.await?; + + workspace.update_in(&mut cx, |workspace, window, cx| { + let thread = thread.read(cx); + let markdown = thread.to_markdown()?; + let thread_summary = thread + .summary() + .map(|summary| summary.to_string()) + .unwrap_or_else(|| "Thread".to_string()); + + let project = workspace.project().clone(); + let buffer = project.update(cx, |project, cx| { + project.create_local_buffer(&markdown, Some(markdown_language), cx) + }); + let buffer = cx.new(|cx| { + MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone()) + }); + + workspace.add_item_to_active_pane( + Box::new(cx.new(|cx| { + let mut editor = Editor::for_multibuffer( + buffer, + Some(project.clone()), + true, + window, + cx, + ); + editor.set_breadcrumb_header(thread_summary); + editor + })), + None, + true, + window, + cx, + ); + + anyhow::Ok(()) + }) + }) + .detach_and_log_err(cx); + } + fn handle_assistant_configuration_event( &mut self, _entity: &Entity, @@ -1011,6 +1078,7 @@ impl Render for AssistantPanel { .on_action(cx.listener(|this, _: &OpenHistory, window, cx| { this.open_history(window, cx); })) + .on_action(cx.listener(Self::open_active_thread_as_markdown)) .on_action(cx.listener(Self::deploy_prompt_library)) .child(self.render_toolbar(cx)) .map(|parent| match self.active_view { diff --git a/crates/assistant2/src/thread.rs b/crates/assistant2/src/thread.rs index 397dff50e1..ae2ffa3502 100644 --- a/crates/assistant2/src/thread.rs +++ b/crates/assistant2/src/thread.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::sync::Arc; use anyhow::{Context as _, Result}; @@ -794,6 +795,50 @@ impl Thread { false } } + + pub fn to_markdown(&self) -> Result { + let mut markdown = Vec::new(); + + for message in self.messages() { + writeln!( + markdown, + "## {role}\n", + role = match message.role { + Role::User => "User", + Role::Assistant => "Assistant", + Role::System => "System", + } + )?; + writeln!(markdown, "{}\n", message.text)?; + + for tool_use in self.tool_uses_for_message(message.id) { + writeln!( + markdown, + "**Use Tool: {} ({})**", + tool_use.name, tool_use.id + )?; + writeln!(markdown, "```json")?; + writeln!( + markdown, + "{}", + serde_json::to_string_pretty(&tool_use.input)? + )?; + writeln!(markdown, "```")?; + } + + for tool_result in self.tool_results_for_message(message.id) { + write!(markdown, "**Tool Results: {}", tool_result.tool_use_id)?; + if tool_result.is_error { + write!(markdown, " (Error)")?; + } + + writeln!(markdown, "**\n")?; + writeln!(markdown, "{}", tool_result.content)?; + } + } + + Ok(String::from_utf8_lossy(&markdown).to_string()) + } } #[derive(Debug, Clone)]