Detailed changes
@@ -1,4 +1,4 @@
-use crate::context::{AgentContext, RULES_ICON};
+use crate::context::{AgentContextHandle, RULES_ICON};
use crate::context_picker::MentionLink;
use crate::thread::{
LastRestoreCheckpoint, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent,
@@ -1491,19 +1491,13 @@ impl ActiveThread {
let workspace = self.workspace.clone();
let thread = self.thread.read(cx);
- let prompt_store = self.thread_store.read(cx).prompt_store().as_ref();
// Get all the data we need from thread before we start using it in closures
let checkpoint = thread.checkpoint_for_message(message_id);
- let added_context = if let Some(workspace) = workspace.upgrade() {
- let project = workspace.read(cx).project().read(cx);
- thread
- .context_for_message(message_id)
- .flat_map(|context| AddedContext::new(context.clone(), prompt_store, project, cx))
- .collect::<Vec<_>>()
- } else {
- return Empty.into_any();
- };
+ let added_context = thread
+ .context_for_message(message_id)
+ .map(|context| AddedContext::new_attached(context, cx))
+ .collect::<Vec<_>>();
let tool_uses = thread.tool_uses_for_message(message_id, cx);
let has_tool_uses = !tool_uses.is_empty();
@@ -1713,7 +1707,7 @@ impl ActiveThread {
.when(!added_context.is_empty(), |parent| {
parent.child(h_flex().flex_wrap().gap_1().children(
added_context.into_iter().map(|added_context| {
- let context = added_context.context.clone();
+ let context = added_context.handle.clone();
ContextPill::added(added_context, false, false, None).on_click(Rc::new(
cx.listener({
let workspace = workspace.clone();
@@ -3188,13 +3182,13 @@ impl Render for ActiveThread {
}
pub(crate) fn open_context(
- context: &AgentContext,
+ context: &AgentContextHandle,
workspace: Entity<Workspace>,
window: &mut Window,
cx: &mut App,
) {
match context {
- AgentContext::File(file_context) => {
+ AgentContextHandle::File(file_context) => {
if let Some(project_path) = file_context.project_path(cx) {
workspace.update(cx, |workspace, cx| {
workspace
@@ -3204,7 +3198,7 @@ pub(crate) fn open_context(
}
}
- AgentContext::Directory(directory_context) => {
+ AgentContextHandle::Directory(directory_context) => {
let entry_id = directory_context.entry_id;
workspace.update(cx, |workspace, cx| {
workspace.project().update(cx, |_project, cx| {
@@ -3213,7 +3207,7 @@ pub(crate) fn open_context(
})
}
- AgentContext::Symbol(symbol_context) => {
+ AgentContextHandle::Symbol(symbol_context) => {
let buffer = symbol_context.buffer.read(cx);
if let Some(project_path) = buffer.project_path(cx) {
let snapshot = buffer.snapshot();
@@ -3223,7 +3217,7 @@ pub(crate) fn open_context(
}
}
- AgentContext::Selection(selection_context) => {
+ AgentContextHandle::Selection(selection_context) => {
let buffer = selection_context.buffer.read(cx);
if let Some(project_path) = buffer.project_path(cx) {
let snapshot = buffer.snapshot();
@@ -3234,11 +3228,11 @@ pub(crate) fn open_context(
}
}
- AgentContext::FetchedUrl(fetched_url_context) => {
+ AgentContextHandle::FetchedUrl(fetched_url_context) => {
cx.open_url(&fetched_url_context.url);
}
- AgentContext::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
+ AgentContextHandle::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| {
let thread_id = thread_context.thread.read(cx).id().clone();
@@ -3249,14 +3243,14 @@ pub(crate) fn open_context(
}
}),
- AgentContext::Rules(rules_context) => window.dispatch_action(
+ AgentContextHandle::Rules(rules_context) => window.dispatch_action(
Box::new(OpenRulesLibrary {
prompt_to_select: Some(rules_context.prompt_id.0),
}),
cx,
),
- AgentContext::Image(_) => {}
+ AgentContextHandle::Image(_) => {}
}
}
@@ -1,4 +1,6 @@
+use std::fmt::{self, Display, Formatter, Write as _};
use std::hash::{Hash, Hasher};
+use std::path::PathBuf;
use std::{ops::Range, path::Path, sync::Arc};
use collections::HashSet;
@@ -10,9 +12,10 @@ use language_model::{LanguageModelImage, LanguageModelRequestMessage, MessageCon
use project::{Project, ProjectEntryId, ProjectPath, Worktree};
use prompt_store::{PromptStore, UserPromptId};
use ref_cast::RefCast;
-use rope::{Point, Rope};
+use rope::Point;
use text::{Anchor, OffsetRangeExt as _};
use ui::{ElementId, IconName};
+use util::markdown::MarkdownCodeBlock;
use util::{ResultExt as _, post_inc};
use crate::thread::Thread;
@@ -45,24 +48,24 @@ impl ContextKind {
}
}
-/// Handle for context that can be added to a user message.
+/// Handle for context that can be attached to a user message.
///
/// This uses IDs that are stable enough for tracking renames and identifying when context has
/// already been added to the thread. To use this in a set, wrap it in `AgentContextKey` to opt in
/// to `PartialEq` and `Hash` impls that use the subset of the fields used for this stable identity.
#[derive(Debug, Clone)]
-pub enum AgentContext {
- File(FileContext),
- Directory(DirectoryContext),
- Symbol(SymbolContext),
- Selection(SelectionContext),
+pub enum AgentContextHandle {
+ File(FileContextHandle),
+ Directory(DirectoryContextHandle),
+ Symbol(SymbolContextHandle),
+ Selection(SelectionContextHandle),
FetchedUrl(FetchedUrlContext),
- Thread(ThreadContext),
- Rules(RulesContext),
+ Thread(ThreadContextHandle),
+ Rules(RulesContextHandle),
Image(ImageContext),
}
-impl AgentContext {
+impl AgentContextHandle {
fn id(&self) -> ContextId {
match self {
Self::File(context) => context.context_id,
@@ -81,6 +84,39 @@ impl AgentContext {
}
}
+/// Loaded context that can be attached to a user message. This can be thought of as a
+/// snapshot of the context along with an `AgentContextHandle`.
+#[derive(Debug, Clone)]
+pub enum AgentContext {
+ File(FileContext),
+ Directory(DirectoryContext),
+ Symbol(SymbolContext),
+ Selection(SelectionContext),
+ FetchedUrl(FetchedUrlContext),
+ Thread(ThreadContext),
+ Rules(RulesContext),
+ Image(ImageContext),
+}
+
+impl AgentContext {
+ pub fn handle(&self) -> AgentContextHandle {
+ match self {
+ AgentContext::File(context) => AgentContextHandle::File(context.handle.clone()),
+ AgentContext::Directory(context) => {
+ AgentContextHandle::Directory(context.handle.clone())
+ }
+ AgentContext::Symbol(context) => AgentContextHandle::Symbol(context.handle.clone()),
+ AgentContext::Selection(context) => {
+ AgentContextHandle::Selection(context.handle.clone())
+ }
+ AgentContext::FetchedUrl(context) => AgentContextHandle::FetchedUrl(context.clone()),
+ AgentContext::Thread(context) => AgentContextHandle::Thread(context.handle.clone()),
+ AgentContext::Rules(context) => AgentContextHandle::Rules(context.handle.clone()),
+ AgentContext::Image(context) => AgentContextHandle::Image(context.clone()),
+ }
+ }
+}
+
/// ID created at time of context add, for use in ElementId. This is not the stable identity of a
/// context, instead that's handled by the `PartialEq` and `Hash` impls of `AgentContextKey`.
#[derive(Debug, Copy, Clone)]
@@ -106,12 +142,19 @@ impl ContextId {
/// be opened even if the file has been deleted. An alternative might be to use `ProjectEntryId`,
/// but then when deleted there is no path info or ability to open.
#[derive(Debug, Clone)]
-pub struct FileContext {
+pub struct FileContextHandle {
pub buffer: Entity<Buffer>,
pub context_id: ContextId,
}
-impl FileContext {
+#[derive(Debug, Clone)]
+pub struct FileContext {
+ pub handle: FileContextHandle,
+ pub full_path: Arc<Path>,
+ pub text: SharedString,
+}
+
+impl FileContextHandle {
pub fn eq_for_key(&self, other: &Self) -> bool {
self.buffer == other.buffer
}
@@ -128,19 +171,35 @@ impl FileContext {
})
}
- fn load(&self, cx: &App) -> Option<Task<(String, Entity<Buffer>)>> {
+ fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
let buffer_ref = self.buffer.read(cx);
let Some(file) = buffer_ref.file() else {
log::error!("file context missing path");
- return None;
+ return Task::ready(None);
};
let full_path = file.full_path(cx);
let rope = buffer_ref.as_rope().clone();
let buffer = self.buffer.clone();
- Some(
- cx.background_spawn(
- async move { (to_fenced_codeblock(&full_path, rope, None), buffer) },
- ),
+ cx.background_spawn(async move {
+ let context = AgentContext::File(FileContext {
+ handle: self,
+ full_path: full_path.into(),
+ text: rope.to_string().into(),
+ });
+ Some((context, vec![buffer]))
+ })
+ }
+}
+
+impl Display for FileContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ MarkdownCodeBlock {
+ tag: &codeblock_tag(&self.full_path, None),
+ text: &self.text,
+ }
)
}
}
@@ -149,12 +208,26 @@ impl FileContext {
///
/// This has a `ProjectEntryId` so that it follows renames.
#[derive(Debug, Clone)]
-pub struct DirectoryContext {
+pub struct DirectoryContextHandle {
pub entry_id: ProjectEntryId,
pub context_id: ContextId,
}
-impl DirectoryContext {
+#[derive(Debug, Clone)]
+pub struct DirectoryContext {
+ pub handle: DirectoryContextHandle,
+ pub full_path: Arc<Path>,
+ pub descendants: Vec<DirectoryContextDescendant>,
+}
+
+#[derive(Debug, Clone)]
+pub struct DirectoryContextDescendant {
+ /// Path within the directory.
+ pub rel_path: Arc<Path>,
+ pub fenced_codeblock: SharedString,
+}
+
+impl DirectoryContextHandle {
pub fn eq_for_key(&self, other: &Self) -> bool {
self.entry_id == other.entry_id
}
@@ -164,41 +237,116 @@ impl DirectoryContext {
}
fn load(
- &self,
+ self,
project: Entity<Project>,
cx: &mut App,
- ) -> Option<Task<Vec<(String, Entity<Buffer>)>>> {
- let worktree = project.read(cx).worktree_for_entry(self.entry_id, cx)?;
+ ) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
+ let Some(worktree) = project.read(cx).worktree_for_entry(self.entry_id, cx) else {
+ return Task::ready(None);
+ };
let worktree_ref = worktree.read(cx);
- let entry = worktree_ref.entry_for_id(self.entry_id)?;
+ let Some(entry) = worktree_ref.entry_for_id(self.entry_id) else {
+ return Task::ready(None);
+ };
if entry.is_file() {
log::error!("DirectoryContext unexpectedly refers to a file.");
- return None;
+ return Task::ready(None);
}
- let file_paths = collect_files_in_path(worktree_ref, entry.path.as_ref());
- let texts_future = future::join_all(file_paths.into_iter().map(|path| {
- load_file_path_text_as_fenced_codeblock(project.clone(), worktree.clone(), path, cx)
+ let directory_path = entry.path.clone();
+ let directory_full_path = worktree_ref.full_path(&directory_path).into();
+
+ let file_paths = collect_files_in_path(worktree_ref, &directory_path);
+ let descendants_future = future::join_all(file_paths.into_iter().map(|path| {
+ let worktree_ref = worktree.read(cx);
+ let worktree_id = worktree_ref.id();
+ let full_path = worktree_ref.full_path(&path);
+
+ let rel_path = path
+ .strip_prefix(&directory_path)
+ .log_err()
+ .map_or_else(|| path.clone(), |rel_path| rel_path.into());
+
+ let open_task = project.update(cx, |project, cx| {
+ project.buffer_store().update(cx, |buffer_store, cx| {
+ let project_path = ProjectPath { worktree_id, path };
+ buffer_store.open_buffer(project_path, cx)
+ })
+ });
+
+ // TODO: report load errors instead of just logging
+ let rope_task = cx.spawn(async move |cx| {
+ let buffer = open_task.await.log_err()?;
+ let rope = buffer
+ .read_with(cx, |buffer, _cx| buffer.as_rope().clone())
+ .log_err()?;
+ Some((rope, buffer))
+ });
+
+ cx.background_spawn(async move {
+ let (rope, buffer) = rope_task.await?;
+ let fenced_codeblock = MarkdownCodeBlock {
+ tag: &codeblock_tag(&full_path, None),
+ text: &rope.to_string(),
+ }
+ .to_string()
+ .into();
+ let descendant = DirectoryContextDescendant {
+ rel_path,
+ fenced_codeblock,
+ };
+ Some((descendant, buffer))
+ })
}));
- Some(cx.background_spawn(async move {
- texts_future.await.into_iter().flatten().collect::<Vec<_>>()
- }))
+ cx.background_spawn(async move {
+ let (descendants, buffers) = descendants_future.await.into_iter().flatten().unzip();
+ let context = AgentContext::Directory(DirectoryContext {
+ handle: self,
+ full_path: directory_full_path,
+ descendants,
+ });
+ Some((context, buffers))
+ })
+ }
+}
+
+impl Display for DirectoryContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let mut is_first = true;
+ for descendant in &self.descendants {
+ if !is_first {
+ write!(f, "\n")?;
+ } else {
+ is_first = false;
+ }
+ write!(f, "{}", descendant.fenced_codeblock)?;
+ }
+ Ok(())
}
}
#[derive(Debug, Clone)]
-pub struct SymbolContext {
+pub struct SymbolContextHandle {
pub buffer: Entity<Buffer>,
pub symbol: SharedString,
pub range: Range<Anchor>,
- /// The range that fully contain the symbol. e.g. for function symbol, this will include not
- /// only the signature, but also the body. Not used by `PartialEq` or `Hash` for `AgentContextKey`.
+ /// The range that fully contains the symbol. e.g. for function symbol, this will include not
+ /// only the signature, but also the body. Not used by `PartialEq` or `Hash` for
+ /// `AgentContextKey`.
pub enclosing_range: Range<Anchor>,
pub context_id: ContextId,
}
-impl SymbolContext {
+#[derive(Debug, Clone)]
+pub struct SymbolContext {
+ pub handle: SymbolContextHandle,
+ pub full_path: Arc<Path>,
+ pub line_range: Range<Point>,
+ pub text: SharedString,
+}
+
+impl SymbolContextHandle {
pub fn eq_for_key(&self, other: &Self) -> bool {
self.buffer == other.buffer && self.symbol == other.symbol && self.range == other.range
}
@@ -209,35 +357,69 @@ impl SymbolContext {
self.range.hash(state);
}
- fn load(&self, cx: &App) -> Option<Task<(String, Entity<Buffer>)>> {
+ pub fn full_path(&self, cx: &App) -> Option<PathBuf> {
+ Some(self.buffer.read(cx).file()?.full_path(cx))
+ }
+
+ pub fn enclosing_line_range(&self, cx: &App) -> Range<Point> {
+ self.enclosing_range
+ .to_point(&self.buffer.read(cx).snapshot())
+ }
+
+ pub fn text(&self, cx: &App) -> SharedString {
+ self.buffer
+ .read(cx)
+ .text_for_range(self.enclosing_range.clone())
+ .collect::<String>()
+ .into()
+ }
+
+ fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
let buffer_ref = self.buffer.read(cx);
let Some(file) = buffer_ref.file() else {
log::error!("symbol context's file has no path");
- return None;
+ return Task::ready(None);
};
- let full_path = file.full_path(cx);
- let rope = buffer_ref
- .text_for_range(self.enclosing_range.clone())
- .collect::<Rope>();
+ let full_path = file.full_path(cx).into();
let line_range = self.enclosing_range.to_point(&buffer_ref.snapshot());
+ let text = self.text(cx);
let buffer = self.buffer.clone();
- Some(cx.background_spawn(async move {
- (
- to_fenced_codeblock(&full_path, rope, Some(line_range)),
- buffer,
- )
- }))
+ let context = AgentContext::Symbol(SymbolContext {
+ handle: self,
+ full_path,
+ line_range,
+ text,
+ });
+ Task::ready(Some((context, vec![buffer])))
+ }
+}
+
+impl Display for SymbolContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let code_block = MarkdownCodeBlock {
+ tag: &codeblock_tag(&self.full_path, Some(self.line_range.clone())),
+ text: &self.text,
+ };
+ write!(f, "{code_block}",)
}
}
#[derive(Debug, Clone)]
-pub struct SelectionContext {
+pub struct SelectionContextHandle {
pub buffer: Entity<Buffer>,
pub range: Range<Anchor>,
pub context_id: ContextId,
}
-impl SelectionContext {
+#[derive(Debug, Clone)]
+pub struct SelectionContext {
+ pub handle: SelectionContextHandle,
+ pub full_path: Arc<Path>,
+ pub line_range: Range<Point>,
+ pub text: SharedString,
+}
+
+impl SelectionContextHandle {
pub fn eq_for_key(&self, other: &Self) -> bool {
self.buffer == other.buffer && self.range == other.range
}
@@ -247,24 +429,47 @@ impl SelectionContext {
self.range.hash(state);
}
- fn load(&self, cx: &App) -> Option<Task<(String, Entity<Buffer>)>> {
- let buffer_ref = self.buffer.read(cx);
- let Some(file) = buffer_ref.file() else {
+ pub fn full_path(&self, cx: &App) -> Option<PathBuf> {
+ Some(self.buffer.read(cx).file()?.full_path(cx))
+ }
+
+ pub fn line_range(&self, cx: &App) -> Range<Point> {
+ self.range.to_point(&self.buffer.read(cx).snapshot())
+ }
+
+ pub fn text(&self, cx: &App) -> SharedString {
+ self.buffer
+ .read(cx)
+ .text_for_range(self.range.clone())
+ .collect::<String>()
+ .into()
+ }
+
+ fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
+ let Some(full_path) = self.full_path(cx) else {
log::error!("selection context's file has no path");
- return None;
+ return Task::ready(None);
};
- let full_path = file.full_path(cx);
- let rope = buffer_ref
- .text_for_range(self.range.clone())
- .collect::<Rope>();
- let line_range = self.range.to_point(&buffer_ref.snapshot());
+ let text = self.text(cx);
let buffer = self.buffer.clone();
- Some(cx.background_spawn(async move {
- (
- to_fenced_codeblock(&full_path, rope, Some(line_range)),
- buffer,
- )
- }))
+ let context = AgentContext::Selection(SelectionContext {
+ full_path: full_path.into(),
+ line_range: self.line_range(cx),
+ text,
+ handle: self,
+ });
+
+ Task::ready(Some((context, vec![buffer])))
+ }
+}
+
+impl Display for SelectionContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let code_block = MarkdownCodeBlock {
+ tag: &codeblock_tag(&self.full_path, Some(self.line_range.clone())),
+ text: &self.text,
+ };
+ write!(f, "{code_block}",)
}
}
@@ -288,21 +493,39 @@ impl FetchedUrlContext {
}
pub fn lookup_key(url: SharedString) -> AgentContextKey {
- AgentContextKey(AgentContext::FetchedUrl(FetchedUrlContext {
+ AgentContextKey(AgentContextHandle::FetchedUrl(FetchedUrlContext {
url,
text: "".into(),
context_id: ContextId::for_lookup(),
}))
}
+
+ pub fn load(self) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
+ Task::ready(Some((AgentContext::FetchedUrl(self), vec![])))
+ }
+}
+
+impl Display for FetchedUrlContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ // TODO: Better format - url and contents are not delimited.
+ write!(f, "{}\n{}\n", self.url, self.text)
+ }
}
#[derive(Debug, Clone)]
-pub struct ThreadContext {
+pub struct ThreadContextHandle {
pub thread: Entity<Thread>,
pub context_id: ContextId,
}
-impl ThreadContext {
+#[derive(Debug, Clone)]
+pub struct ThreadContext {
+ pub handle: ThreadContextHandle,
+ pub title: SharedString,
+ pub text: SharedString,
+}
+
+impl ThreadContextHandle {
pub fn eq_for_key(&self, other: &Self) -> bool {
self.thread == other.thread
}
@@ -311,32 +534,44 @@ impl ThreadContext {
self.thread.hash(state)
}
- pub fn name(&self, cx: &App) -> SharedString {
+ pub fn title(&self, cx: &App) -> SharedString {
self.thread
.read(cx)
.summary()
.unwrap_or_else(|| "New thread".into())
}
- pub fn load(&self, cx: &App) -> String {
- let name = self.name(cx);
- let contents = self.thread.read(cx).latest_detailed_summary_or_text();
- let mut text = String::new();
- text.push_str(&name);
- text.push('\n');
- text.push_str(&contents.trim());
- text.push('\n');
- text
+ fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
+ let context = AgentContext::Thread(ThreadContext {
+ title: self.title(cx),
+ text: self.thread.read(cx).latest_detailed_summary_or_text(),
+ handle: self,
+ });
+ Task::ready(Some((context, vec![])))
+ }
+}
+
+impl Display for ThreadContext {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ // TODO: Better format for this - doesn't distinguish title and contents.
+ write!(f, "{}\n{}\n", &self.title, &self.text.trim())
}
}
#[derive(Debug, Clone)]
-pub struct RulesContext {
+pub struct RulesContextHandle {
pub prompt_id: UserPromptId,
pub context_id: ContextId,
}
-impl RulesContext {
+#[derive(Debug, Clone)]
+pub struct RulesContext {
+ pub handle: RulesContextHandle,
+ pub title: Option<SharedString>,
+ pub text: SharedString,
+}
+
+impl RulesContextHandle {
pub fn eq_for_key(&self, other: &Self) -> bool {
self.prompt_id == other.prompt_id
}
@@ -346,17 +581,17 @@ impl RulesContext {
}
pub fn lookup_key(prompt_id: UserPromptId) -> AgentContextKey {
- AgentContextKey(AgentContext::Rules(RulesContext {
+ AgentContextKey(AgentContextHandle::Rules(RulesContextHandle {
prompt_id,
context_id: ContextId::for_lookup(),
}))
}
pub fn load(
- &self,
+ self,
prompt_store: &Option<Entity<PromptStore>>,
cx: &App,
- ) -> Task<Option<String>> {
+ ) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
let Some(prompt_store) = prompt_store.as_ref() else {
return Task::ready(None);
};
@@ -365,23 +600,34 @@ impl RulesContext {
let Some(metadata) = prompt_store.metadata(prompt_id) else {
return Task::ready(None);
};
- let contents_task = prompt_store.load(prompt_id, cx);
+ let title = metadata.title;
+ let text_task = prompt_store.load(prompt_id, cx);
cx.background_spawn(async move {
- let contents = contents_task.await.ok()?;
- let mut text = String::new();
- if let Some(title) = metadata.title {
- text.push_str("Rules title: ");
- text.push_str(&title);
- text.push('\n');
- }
- text.push_str("``````\n");
- text.push_str(contents.trim());
- text.push_str("\n``````\n");
- Some(text)
+ // TODO: report load errors instead of just logging
+ let text = text_task.await.log_err()?.into();
+ let context = AgentContext::Rules(RulesContext {
+ handle: self,
+ title,
+ text,
+ });
+ Some((context, vec![]))
})
}
}
+impl Display for RulesContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if let Some(title) = &self.title {
+ write!(f, "Rules title: {}\n", title)?;
+ }
+ let code_block = MarkdownCodeBlock {
+ tag: "",
+ text: self.text.trim(),
+ };
+ write!(f, "{code_block}")
+ }
+}
+
#[derive(Debug, Clone)]
pub struct ImageContext {
pub original_image: Arc<gpui::Image>,
@@ -417,6 +663,13 @@ impl ImageContext {
Some(Some(_)) => ImageStatus::Ready,
}
}
+
+ pub fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
+ cx.background_spawn(async move {
+ self.image_task.clone().await;
+ Some((AgentContext::Image(self), vec![]))
+ })
+ }
}
#[derive(Debug, Clone, Default)]
@@ -463,64 +716,68 @@ impl LoadedContext {
/// Loads and formats a collection of contexts.
pub fn load_context(
- contexts: Vec<AgentContext>,
+ contexts: Vec<AgentContextHandle>,
project: &Entity<Project>,
prompt_store: &Option<Entity<PromptStore>>,
cx: &mut App,
) -> Task<ContextLoadResult> {
- let mut file_tasks = Vec::new();
- let mut directory_tasks = Vec::new();
- let mut symbol_tasks = Vec::new();
- let mut selection_tasks = Vec::new();
- let mut fetch_context = Vec::new();
- let mut thread_context = Vec::new();
- let mut rules_tasks = Vec::new();
- let mut image_tasks = Vec::new();
+ let mut load_tasks = Vec::new();
for context in contexts.iter().cloned() {
match context {
- AgentContext::File(context) => file_tasks.extend(context.load(cx)),
- AgentContext::Directory(context) => {
- directory_tasks.extend(context.load(project.clone(), cx))
+ AgentContextHandle::File(context) => load_tasks.push(context.load(cx)),
+ AgentContextHandle::Directory(context) => {
+ load_tasks.push(context.load(project.clone(), cx))
}
- AgentContext::Symbol(context) => symbol_tasks.extend(context.load(cx)),
- AgentContext::Selection(context) => selection_tasks.extend(context.load(cx)),
- AgentContext::FetchedUrl(context) => fetch_context.push(context),
- AgentContext::Thread(context) => thread_context.push(context.load(cx)),
- AgentContext::Rules(context) => rules_tasks.push(context.load(prompt_store, cx)),
- AgentContext::Image(context) => image_tasks.push(context.image_task.clone()),
+ AgentContextHandle::Symbol(context) => load_tasks.push(context.load(cx)),
+ AgentContextHandle::Selection(context) => load_tasks.push(context.load(cx)),
+ AgentContextHandle::FetchedUrl(context) => load_tasks.push(context.load()),
+ AgentContextHandle::Thread(context) => load_tasks.push(context.load(cx)),
+ AgentContextHandle::Rules(context) => load_tasks.push(context.load(prompt_store, cx)),
+ AgentContextHandle::Image(context) => load_tasks.push(context.load(cx)),
}
}
cx.background_spawn(async move {
- let (
- file_context,
- directory_context,
- symbol_context,
- selection_context,
- rules_context,
- images,
- ) = futures::join!(
- future::join_all(file_tasks),
- future::join_all(directory_tasks),
- future::join_all(symbol_tasks),
- future::join_all(selection_tasks),
- future::join_all(rules_tasks),
- future::join_all(image_tasks)
- );
-
- let directory_context = directory_context.into_iter().flatten().collect::<Vec<_>>();
- let rules_context = rules_context.into_iter().flatten().collect::<Vec<_>>();
- let images = images.into_iter().flatten().collect::<Vec<_>>();
+ let load_results = future::join_all(load_tasks).await;
- let mut referenced_buffers = HashSet::default();
+ let mut contexts = Vec::new();
let mut text = String::new();
+ let mut referenced_buffers = HashSet::default();
+ for context in load_results {
+ let Some((context, buffers)) = context else {
+ continue;
+ };
+ contexts.push(context);
+ referenced_buffers.extend(buffers);
+ }
+
+ let mut file_context = Vec::new();
+ let mut directory_context = Vec::new();
+ let mut symbol_context = Vec::new();
+ let mut selection_context = Vec::new();
+ let mut fetched_url_context = Vec::new();
+ let mut thread_context = Vec::new();
+ let mut rules_context = Vec::new();
+ let mut images = Vec::new();
+ for context in &contexts {
+ match context {
+ AgentContext::File(context) => file_context.push(context),
+ AgentContext::Directory(context) => directory_context.push(context),
+ AgentContext::Symbol(context) => symbol_context.push(context),
+ AgentContext::Selection(context) => selection_context.push(context),
+ AgentContext::FetchedUrl(context) => fetched_url_context.push(context),
+ AgentContext::Thread(context) => thread_context.push(context),
+ AgentContext::Rules(context) => rules_context.push(context),
+ AgentContext::Image(context) => images.extend(context.image()),
+ }
+ }
if file_context.is_empty()
&& directory_context.is_empty()
&& symbol_context.is_empty()
&& selection_context.is_empty()
- && fetch_context.is_empty()
+ && fetched_url_context.is_empty()
&& thread_context.is_empty()
&& rules_context.is_empty()
{
@@ -542,60 +799,54 @@ pub fn load_context(
if !file_context.is_empty() {
text.push_str("<files>");
- for (file_text, buffer) in file_context {
+ for context in file_context {
text.push('\n');
- text.push_str(&file_text);
- referenced_buffers.insert(buffer);
+ let _ = write!(text, "{context}");
}
text.push_str("</files>\n");
}
if !directory_context.is_empty() {
text.push_str("<directories>");
- for (file_text, buffer) in directory_context {
+ for context in directory_context {
text.push('\n');
- text.push_str(&file_text);
- referenced_buffers.insert(buffer);
+ let _ = write!(text, "{context}");
}
text.push_str("</directories>\n");
}
if !symbol_context.is_empty() {
text.push_str("<symbols>");
- for (symbol_text, buffer) in symbol_context {
+ for context in symbol_context {
text.push('\n');
- text.push_str(&symbol_text);
- referenced_buffers.insert(buffer);
+ let _ = write!(text, "{context}");
}
text.push_str("</symbols>\n");
}
if !selection_context.is_empty() {
text.push_str("<selections>");
- for (selection_text, buffer) in selection_context {
+ for context in selection_context {
text.push('\n');
- text.push_str(&selection_text);
- referenced_buffers.insert(buffer);
+ let _ = write!(text, "{context}");
}
text.push_str("</selections>\n");
}
- if !fetch_context.is_empty() {
+ if !fetched_url_context.is_empty() {
text.push_str("<fetched_urls>");
- for context in fetch_context {
- text.push('\n');
- text.push_str(&context.url);
+ for context in fetched_url_context {
text.push('\n');
- text.push_str(&context.text);
+ let _ = write!(text, "{context}");
}
text.push_str("</fetched_urls>\n");
}
if !thread_context.is_empty() {
text.push_str("<conversation_threads>");
- for thread_text in thread_context {
+ for context in thread_context {
text.push('\n');
- text.push_str(&thread_text);
+ let _ = write!(text, "{context}");
}
text.push_str("</conversation_threads>\n");
}
@@ -605,9 +856,9 @@ pub fn load_context(
"<user_rules>\n\
The user has specified the following rules that should be applied:\n",
);
- for rules_text in rules_context {
+ for context in rules_context {
text.push('\n');
- text.push_str(&rules_text);
+ let _ = write!(text, "{context}");
}
text.push_str("</user_rules>\n");
}
@@ -639,102 +890,34 @@ fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
files
}
-fn load_file_path_text_as_fenced_codeblock(
- project: Entity<Project>,
- worktree: Entity<Worktree>,
- path: Arc<Path>,
- cx: &mut App,
-) -> Task<Option<(String, Entity<Buffer>)>> {
- let worktree_ref = worktree.read(cx);
- let worktree_id = worktree_ref.id();
- let full_path = worktree_ref.full_path(&path);
-
- let open_task = project.update(cx, |project, cx| {
- project.buffer_store().update(cx, |buffer_store, cx| {
- let project_path = ProjectPath { worktree_id, path };
- buffer_store.open_buffer(project_path, cx)
- })
- });
+fn codeblock_tag(full_path: &Path, line_range: Option<Range<Point>>) -> String {
+ let mut result = String::new();
- let rope_task = cx.spawn(async move |cx| {
- let buffer = open_task.await.log_err()?;
- let rope = buffer
- .read_with(cx, |buffer, _cx| buffer.as_rope().clone())
- .log_err()?;
- Some((rope, buffer))
- });
+ if let Some(extension) = full_path.extension().and_then(|ext| ext.to_str()) {
+ let _ = write!(result, "{} ", extension);
+ }
- cx.background_spawn(async move {
- let (rope, buffer) = rope_task.await?;
- Some((to_fenced_codeblock(&full_path, rope, None), buffer))
- })
-}
+ let _ = write!(result, "{}", full_path.display());
-fn to_fenced_codeblock(
- full_path: &Path,
- content: Rope,
- line_range: Option<Range<Point>>,
-) -> String {
- let line_range_text = line_range.map(|range| {
+ if let Some(range) = line_range {
if range.start.row == range.end.row {
- format!(":{}", range.start.row + 1)
+ let _ = write!(result, ":{}", range.start.row + 1);
} else {
- format!(":{}-{}", range.start.row + 1, range.end.row + 1)
+ let _ = write!(result, ":{}-{}", range.start.row + 1, range.end.row + 1);
}
- });
-
- let path_extension = full_path.extension().and_then(|ext| ext.to_str());
- let path_string = full_path.to_string_lossy();
- let capacity = 3
- + path_extension.map_or(0, |extension| extension.len() + 1)
- + path_string.len()
- + line_range_text.as_ref().map_or(0, |text| text.len())
- + 1
- + content.len()
- + 5;
- let mut buffer = String::with_capacity(capacity);
-
- buffer.push_str("```");
-
- if let Some(extension) = path_extension {
- buffer.push_str(extension);
- buffer.push(' ');
}
- buffer.push_str(&path_string);
- if let Some(line_range_text) = line_range_text {
- buffer.push_str(&line_range_text);
- }
-
- buffer.push('\n');
- for chunk in content.chunks() {
- buffer.push_str(chunk);
- }
-
- if !buffer.ends_with('\n') {
- buffer.push('\n');
- }
-
- buffer.push_str("```\n");
-
- debug_assert!(
- buffer.len() == capacity - 1 || buffer.len() == capacity,
- "to_fenced_codeblock calculated capacity of {}, but length was {}",
- capacity,
- buffer.len(),
- );
-
- buffer
+ result
}
/// Wraps `AgentContext` to opt-in to `PartialEq` and `Hash` impls which use a subset of fields
/// needed for stable context identity.
#[derive(Debug, Clone, RefCast)]
#[repr(transparent)]
-pub struct AgentContextKey(pub AgentContext);
+pub struct AgentContextKey(pub AgentContextHandle);
-impl AsRef<AgentContext> for AgentContextKey {
- fn as_ref(&self) -> &AgentContext {
+impl AsRef<AgentContextHandle> for AgentContextKey {
+ fn as_ref(&self) -> &AgentContextHandle {
&self.0
}
}
@@ -744,43 +927,43 @@ impl Eq for AgentContextKey {}
impl PartialEq for AgentContextKey {
fn eq(&self, other: &Self) -> bool {
match &self.0 {
- AgentContext::File(context) => {
- if let AgentContext::File(other_context) = &other.0 {
+ AgentContextHandle::File(context) => {
+ if let AgentContextHandle::File(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
- AgentContext::Directory(context) => {
- if let AgentContext::Directory(other_context) = &other.0 {
+ AgentContextHandle::Directory(context) => {
+ if let AgentContextHandle::Directory(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
- AgentContext::Symbol(context) => {
- if let AgentContext::Symbol(other_context) = &other.0 {
+ AgentContextHandle::Symbol(context) => {
+ if let AgentContextHandle::Symbol(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
- AgentContext::Selection(context) => {
- if let AgentContext::Selection(other_context) = &other.0 {
+ AgentContextHandle::Selection(context) => {
+ if let AgentContextHandle::Selection(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
- AgentContext::FetchedUrl(context) => {
- if let AgentContext::FetchedUrl(other_context) = &other.0 {
+ AgentContextHandle::FetchedUrl(context) => {
+ if let AgentContextHandle::FetchedUrl(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
- AgentContext::Thread(context) => {
- if let AgentContext::Thread(other_context) = &other.0 {
+ AgentContextHandle::Thread(context) => {
+ if let AgentContextHandle::Thread(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
- AgentContext::Rules(context) => {
- if let AgentContext::Rules(other_context) = &other.0 {
+ AgentContextHandle::Rules(context) => {
+ if let AgentContextHandle::Rules(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
- AgentContext::Image(context) => {
- if let AgentContext::Image(other_context) = &other.0 {
+ AgentContextHandle::Image(context) => {
+ if let AgentContextHandle::Image(other_context) = &other.0 {
return context.eq_for_key(other_context);
}
}
@@ -17,8 +17,9 @@ use util::ResultExt as _;
use crate::ThreadStore;
use crate::context::{
- AgentContext, AgentContextKey, ContextId, DirectoryContext, FetchedUrlContext, FileContext,
- ImageContext, RulesContext, SelectionContext, SymbolContext, ThreadContext,
+ AgentContextHandle, AgentContextKey, ContextId, DirectoryContextHandle, FetchedUrlContext,
+ FileContextHandle, ImageContext, RulesContextHandle, SelectionContextHandle,
+ SymbolContextHandle, ThreadContextHandle,
};
use crate::context_strip::SuggestedContext;
use crate::thread::{Thread, ThreadId};
@@ -47,7 +48,7 @@ impl ContextStore {
}
}
- pub fn context(&self) -> impl Iterator<Item = &AgentContext> {
+ pub fn context(&self) -> impl Iterator<Item = &AgentContextHandle> {
self.context_set.iter().map(|entry| entry.as_ref())
}
@@ -56,11 +57,16 @@ impl ContextStore {
self.context_thread_ids.clear();
}
- pub fn new_context_for_thread(&self, thread: &Thread) -> Vec<AgentContext> {
+ pub fn new_context_for_thread(&self, thread: &Thread) -> Vec<AgentContextHandle> {
let existing_context = thread
.messages()
- .flat_map(|message| &message.loaded_context.contexts)
- .map(AgentContextKey::ref_cast)
+ .flat_map(|message| {
+ message
+ .loaded_context
+ .contexts
+ .iter()
+ .map(|context| AgentContextKey(context.handle()))
+ })
.collect::<HashSet<_>>();
self.context_set
.iter()
@@ -98,7 +104,7 @@ impl ContextStore {
cx: &mut Context<Self>,
) {
let context_id = self.next_context_id.post_inc();
- let context = AgentContext::File(FileContext { buffer, context_id });
+ let context = AgentContextHandle::File(FileContextHandle { buffer, context_id });
let already_included = if self.has_context(&context) {
if remove_if_exists {
@@ -133,7 +139,7 @@ impl ContextStore {
};
let context_id = self.next_context_id.post_inc();
- let context = AgentContext::Directory(DirectoryContext {
+ let context = AgentContextHandle::Directory(DirectoryContextHandle {
entry_id,
context_id,
});
@@ -159,7 +165,7 @@ impl ContextStore {
cx: &mut Context<Self>,
) -> bool {
let context_id = self.next_context_id.post_inc();
- let context = AgentContext::Symbol(SymbolContext {
+ let context = AgentContextHandle::Symbol(SymbolContextHandle {
buffer,
symbol,
range,
@@ -184,7 +190,7 @@ impl ContextStore {
cx: &mut Context<Self>,
) {
let context_id = self.next_context_id.post_inc();
- let context = AgentContext::Thread(ThreadContext { thread, context_id });
+ let context = AgentContextHandle::Thread(ThreadContextHandle { thread, context_id });
if self.has_context(&context) {
if remove_if_exists {
@@ -237,7 +243,7 @@ impl ContextStore {
cx: &mut Context<ContextStore>,
) {
let context_id = self.next_context_id.post_inc();
- let context = AgentContext::Rules(RulesContext {
+ let context = AgentContextHandle::Rules(RulesContextHandle {
prompt_id,
context_id,
});
@@ -257,7 +263,7 @@ impl ContextStore {
text: impl Into<SharedString>,
cx: &mut Context<ContextStore>,
) {
- let context = AgentContext::FetchedUrl(FetchedUrlContext {
+ let context = AgentContextHandle::FetchedUrl(FetchedUrlContext {
url: url.into(),
text: text.into(),
context_id: self.next_context_id.post_inc(),
@@ -268,7 +274,7 @@ impl ContextStore {
pub fn add_image(&mut self, image: Arc<Image>, cx: &mut Context<ContextStore>) {
let image_task = LanguageModelImage::from_image(image.clone(), cx).shared();
- let context = AgentContext::Image(ImageContext {
+ let context = AgentContextHandle::Image(ImageContext {
original_image: image,
image_task,
context_id: self.next_context_id.post_inc(),
@@ -283,7 +289,7 @@ impl ContextStore {
cx: &mut Context<ContextStore>,
) {
let context_id = self.next_context_id.post_inc();
- let context = AgentContext::Selection(SelectionContext {
+ let context = AgentContextHandle::Selection(SelectionContextHandle {
buffer,
range,
context_id,
@@ -304,14 +310,17 @@ impl ContextStore {
} => {
if let Some(buffer) = buffer.upgrade() {
let context_id = self.next_context_id.post_inc();
- self.insert_context(AgentContext::File(FileContext { buffer, context_id }), cx);
+ self.insert_context(
+ AgentContextHandle::File(FileContextHandle { buffer, context_id }),
+ cx,
+ );
};
}
SuggestedContext::Thread { thread, name: _ } => {
if let Some(thread) = thread.upgrade() {
let context_id = self.next_context_id.post_inc();
self.insert_context(
- AgentContext::Thread(ThreadContext { thread, context_id }),
+ AgentContextHandle::Thread(ThreadContextHandle { thread, context_id }),
cx,
);
}
@@ -319,9 +328,9 @@ impl ContextStore {
}
}
- fn insert_context(&mut self, context: AgentContext, cx: &mut Context<Self>) -> bool {
+ fn insert_context(&mut self, context: AgentContextHandle, cx: &mut Context<Self>) -> bool {
match &context {
- AgentContext::Thread(thread_context) => {
+ AgentContextHandle::Thread(thread_context) => {
self.context_thread_ids
.insert(thread_context.thread.read(cx).id().clone());
self.start_summarizing_thread_if_needed(&thread_context.thread, cx);
@@ -335,13 +344,13 @@ impl ContextStore {
inserted
}
- pub fn remove_context(&mut self, context: &AgentContext, cx: &mut Context<Self>) {
+ pub fn remove_context(&mut self, context: &AgentContextHandle, cx: &mut Context<Self>) {
if self
.context_set
.shift_remove(AgentContextKey::ref_cast(context))
{
match context {
- AgentContext::Thread(thread_context) => {
+ AgentContextHandle::Thread(thread_context) => {
self.context_thread_ids
.remove(thread_context.thread.read(cx).id());
}
@@ -351,7 +360,7 @@ impl ContextStore {
}
}
- pub fn has_context(&mut self, context: &AgentContext) -> bool {
+ pub fn has_context(&mut self, context: &AgentContextHandle) -> bool {
self.context_set
.contains(AgentContextKey::ref_cast(context))
}
@@ -361,8 +370,10 @@ impl ContextStore {
pub fn file_path_included(&self, path: &ProjectPath, cx: &App) -> Option<FileInclusion> {
let project = self.project.upgrade()?.read(cx);
self.context().find_map(|context| match context {
- AgentContext::File(file_context) => FileInclusion::check_file(file_context, path, cx),
- AgentContext::Directory(directory_context) => {
+ AgentContextHandle::File(file_context) => {
+ FileInclusion::check_file(file_context, path, cx)
+ }
+ AgentContextHandle::Directory(directory_context) => {
FileInclusion::check_directory(directory_context, path, project, cx)
}
_ => None,
@@ -376,7 +387,7 @@ impl ContextStore {
) -> Option<FileInclusion> {
let project = self.project.upgrade()?.read(cx);
self.context().find_map(|context| match context {
- AgentContext::Directory(directory_context) => {
+ AgentContextHandle::Directory(directory_context) => {
FileInclusion::check_directory(directory_context, path, project, cx)
}
_ => None,
@@ -385,7 +396,7 @@ impl ContextStore {
pub fn includes_symbol(&self, symbol: &Symbol, cx: &App) -> bool {
self.context().any(|context| match context {
- AgentContext::Symbol(context) => {
+ AgentContextHandle::Symbol(context) => {
if context.symbol != symbol.name {
return false;
}
@@ -410,7 +421,7 @@ impl ContextStore {
pub fn includes_user_rules(&self, prompt_id: UserPromptId) -> bool {
self.context_set
- .contains(&RulesContext::lookup_key(prompt_id))
+ .contains(&RulesContextHandle::lookup_key(prompt_id))
}
pub fn includes_url(&self, url: impl Into<SharedString>) -> bool {
@@ -421,17 +432,17 @@ impl ContextStore {
pub fn file_paths(&self, cx: &App) -> HashSet<ProjectPath> {
self.context()
.filter_map(|context| match context {
- AgentContext::File(file) => {
+ AgentContextHandle::File(file) => {
let buffer = file.buffer.read(cx);
buffer.project_path(cx)
}
- AgentContext::Directory(_)
- | AgentContext::Symbol(_)
- | AgentContext::Selection(_)
- | AgentContext::FetchedUrl(_)
- | AgentContext::Thread(_)
- | AgentContext::Rules(_)
- | AgentContext::Image(_) => None,
+ AgentContextHandle::Directory(_)
+ | AgentContextHandle::Symbol(_)
+ | AgentContextHandle::Selection(_)
+ | AgentContextHandle::FetchedUrl(_)
+ | AgentContextHandle::Thread(_)
+ | AgentContextHandle::Rules(_)
+ | AgentContextHandle::Image(_) => None,
})
.collect()
}
@@ -447,7 +458,7 @@ pub enum FileInclusion {
}
impl FileInclusion {
- fn check_file(file_context: &FileContext, path: &ProjectPath, cx: &App) -> Option<Self> {
+ fn check_file(file_context: &FileContextHandle, path: &ProjectPath, cx: &App) -> Option<Self> {
let file_path = file_context.buffer.read(cx).project_path(cx)?;
if path == &file_path {
Some(FileInclusion::Direct)
@@ -457,7 +468,7 @@ impl FileInclusion {
}
fn check_directory(
- directory_context: &DirectoryContext,
+ directory_context: &DirectoryContextHandle,
path: &ProjectPath,
project: &Project,
cx: &App,
@@ -14,7 +14,7 @@ use project::ProjectItem;
use ui::{KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
use workspace::Workspace;
-use crate::context::{AgentContext, ContextKind};
+use crate::context::{AgentContextHandle, ContextKind};
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::thread::Thread;
@@ -92,7 +92,9 @@ impl ContextStrip {
self.context_store
.read(cx)
.context()
- .flat_map(|context| AddedContext::new(context.clone(), prompt_store, project, cx))
+ .flat_map(|context| {
+ AddedContext::new_pending(context.clone(), prompt_store, project, cx)
+ })
.collect::<Vec<_>>()
} else {
Vec::new()
@@ -288,7 +290,7 @@ impl ContextStrip {
best.map(|(index, _, _)| index)
}
- fn open_context(&mut self, context: &AgentContext, window: &mut Window, cx: &mut App) {
+ fn open_context(&mut self, context: &AgentContextHandle, window: &mut Window, cx: &mut App) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
@@ -309,7 +311,7 @@ impl ContextStrip {
};
self.context_store.update(cx, |this, cx| {
- this.remove_context(&context.context, cx);
+ this.remove_context(&context.handle, cx);
});
let is_now_empty = added_contexts.len() == 1;
@@ -462,7 +464,7 @@ impl Render for ContextStrip {
.enumerate()
.map(|(i, added_context)| {
let name = added_context.name.clone();
- let context = added_context.context.clone();
+ let context = added_context.handle.clone();
ContextPill::added(
added_context,
dupe_names.contains(&name),
@@ -1,13 +1,23 @@
-use std::{rc::Rc, time::Duration};
+use std::{ops::Range, path::Path, rc::Rc, sync::Arc, time::Duration};
use file_icons::FileIcons;
-use gpui::{Animation, AnimationExt as _, ClickEvent, Entity, MouseButton, pulsating_between};
+use futures::FutureExt as _;
+use gpui::{
+ Animation, AnimationExt as _, AnyView, ClickEvent, Entity, Image, MouseButton, Task,
+ pulsating_between,
+};
+use language_model::LanguageModelImage;
use project::Project;
use prompt_store::PromptStore;
-use text::OffsetRangeExt;
+use rope::Point;
use ui::{IconButtonShape, Tooltip, prelude::*, tooltip_container};
-use crate::context::{AgentContext, ContextKind, ImageStatus};
+use crate::context::{
+ AgentContext, AgentContextHandle, ContextId, ContextKind, DirectoryContext,
+ DirectoryContextHandle, FetchedUrlContext, FileContext, FileContextHandle, ImageContext,
+ ImageStatus, RulesContext, RulesContextHandle, SelectionContext, SelectionContextHandle,
+ SymbolContext, SymbolContextHandle, ThreadContext, ThreadContextHandle,
+};
#[derive(IntoElement)]
pub enum ContextPill {
@@ -72,7 +82,7 @@ impl ContextPill {
pub fn id(&self) -> ElementId {
match self {
- Self::Added { context, .. } => context.context.element_id("context-pill".into()),
+ Self::Added { context, .. } => context.handle.element_id("context-pill".into()),
Self::Suggested { .. } => "suggested-context-pill".into(),
}
}
@@ -165,16 +175,11 @@ impl RenderOnce for ContextPill {
.map(|element| match &context.status {
ContextStatus::Ready => element
.when_some(
- context.render_preview.as_ref(),
- |element, render_preview| {
- element.hoverable_tooltip({
- let render_preview = render_preview.clone();
- move |_, cx| {
- cx.new(|_| ContextPillPreview {
- render_preview: render_preview.clone(),
- })
- .into()
- }
+ context.render_hover.as_ref(),
+ |element, render_hover| {
+ let render_hover = render_hover.clone();
+ element.hoverable_tooltip(move |window, cx| {
+ render_hover(window, cx)
})
},
)
@@ -197,7 +202,7 @@ impl RenderOnce for ContextPill {
.when_some(on_remove.as_ref(), |element, on_remove| {
element.child(
IconButton::new(
- context.context.element_id("remove".into()),
+ context.handle.element_id("remove".into()),
IconName::Close,
)
.shape(IconButtonShape::Square)
@@ -262,18 +267,16 @@ pub enum ContextStatus {
Error { message: SharedString },
}
-// TODO: Component commented out due to new dependency on `Project`.
-//
-// #[derive(RegisterComponent)]
+#[derive(RegisterComponent)]
pub struct AddedContext {
- pub context: AgentContext,
+ pub handle: AgentContextHandle,
pub kind: ContextKind,
pub name: SharedString,
pub parent: Option<SharedString>,
pub tooltip: Option<SharedString>,
pub icon_path: Option<SharedString>,
pub status: ContextStatus,
- pub render_preview: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
+ pub render_hover: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
}
impl AddedContext {
@@ -281,221 +284,430 @@ impl AddedContext {
/// `None` if `DirectoryContext` or `RulesContext` no longer exist.
///
/// TODO: `None` cases are unremovable from `ContextStore` and so are a very minor memory leak.
- pub fn new(
- context: AgentContext,
+ pub fn new_pending(
+ handle: AgentContextHandle,
prompt_store: Option<&Entity<PromptStore>>,
project: &Project,
cx: &App,
) -> Option<AddedContext> {
+ match handle {
+ AgentContextHandle::File(handle) => Self::pending_file(handle, cx),
+ AgentContextHandle::Directory(handle) => Self::pending_directory(handle, project, cx),
+ AgentContextHandle::Symbol(handle) => Self::pending_symbol(handle, cx),
+ AgentContextHandle::Selection(handle) => Self::pending_selection(handle, cx),
+ AgentContextHandle::FetchedUrl(handle) => Some(Self::fetched_url(handle)),
+ AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),
+ AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),
+ AgentContextHandle::Image(handle) => Some(Self::image(handle)),
+ }
+ }
+
+ pub fn new_attached(context: &AgentContext, cx: &App) -> AddedContext {
match context {
- AgentContext::File(ref file_context) => {
- let full_path = file_context.buffer.read(cx).file()?.full_path(cx);
- let full_path_string: SharedString =
- full_path.to_string_lossy().into_owned().into();
- let name = full_path
- .file_name()
- .map(|n| n.to_string_lossy().into_owned().into())
- .unwrap_or_else(|| full_path_string.clone());
- let parent = full_path
- .parent()
- .and_then(|p| p.file_name())
- .map(|n| n.to_string_lossy().into_owned().into());
- Some(AddedContext {
- kind: ContextKind::File,
- name,
- parent,
- tooltip: Some(full_path_string),
- icon_path: FileIcons::get_icon(&full_path, cx),
- status: ContextStatus::Ready,
- render_preview: None,
- context,
- })
- }
+ AgentContext::File(context) => Self::attached_file(context, cx),
+ AgentContext::Directory(context) => Self::attached_directory(context),
+ AgentContext::Symbol(context) => Self::attached_symbol(context, cx),
+ AgentContext::Selection(context) => Self::attached_selection(context, cx),
+ AgentContext::FetchedUrl(context) => Self::fetched_url(context.clone()),
+ AgentContext::Thread(context) => Self::attached_thread(context),
+ AgentContext::Rules(context) => Self::attached_rules(context),
+ AgentContext::Image(context) => Self::image(context.clone()),
+ }
+ }
- AgentContext::Directory(ref directory_context) => {
- let worktree = project
- .worktree_for_entry(directory_context.entry_id, cx)?
- .read(cx);
- let entry = worktree.entry_for_id(directory_context.entry_id)?;
- let full_path = worktree.full_path(&entry.path);
- let full_path_string: SharedString =
- full_path.to_string_lossy().into_owned().into();
- let name = full_path
- .file_name()
- .map(|n| n.to_string_lossy().into_owned().into())
- .unwrap_or_else(|| full_path_string.clone());
- let parent = full_path
- .parent()
- .and_then(|p| p.file_name())
- .map(|n| n.to_string_lossy().into_owned().into());
- Some(AddedContext {
- kind: ContextKind::Directory,
- name,
- parent,
- tooltip: Some(full_path_string),
- icon_path: None,
- status: ContextStatus::Ready,
- render_preview: None,
- context,
- })
- }
+ fn pending_file(handle: FileContextHandle, cx: &App) -> Option<AddedContext> {
+ let full_path = handle.buffer.read(cx).file()?.full_path(cx);
+ Some(Self::file(handle, &full_path, cx))
+ }
- AgentContext::Symbol(ref symbol_context) => Some(AddedContext {
- kind: ContextKind::Symbol,
- name: symbol_context.symbol.clone(),
- parent: None,
- tooltip: None,
- icon_path: None,
- status: ContextStatus::Ready,
- render_preview: None,
- context,
- }),
+ fn attached_file(context: &FileContext, cx: &App) -> AddedContext {
+ Self::file(context.handle.clone(), &context.full_path, cx)
+ }
- AgentContext::Selection(ref selection_context) => {
- let buffer = selection_context.buffer.read(cx);
- let full_path = buffer.file()?.full_path(cx);
- let mut full_path_string = full_path.to_string_lossy().into_owned();
- let mut name = full_path
- .file_name()
- .map(|n| n.to_string_lossy().into_owned())
- .unwrap_or_else(|| full_path_string.clone());
-
- let line_range = selection_context.range.to_point(&buffer.snapshot());
-
- let line_range_text =
- format!(" ({}-{})", line_range.start.row + 1, line_range.end.row + 1);
-
- full_path_string.push_str(&line_range_text);
- name.push_str(&line_range_text);
-
- let parent = full_path
- .parent()
- .and_then(|p| p.file_name())
- .map(|n| n.to_string_lossy().into_owned().into());
-
- Some(AddedContext {
- kind: ContextKind::Selection,
- name: name.into(),
- parent,
- tooltip: None,
- icon_path: FileIcons::get_icon(&full_path, cx),
- status: ContextStatus::Ready,
- render_preview: None,
- /*
- render_preview: Some(Rc::new({
- let content = selection_context.text.clone();
- move |_, cx| {
- div()
- .id("context-pill-selection-preview")
- .overflow_scroll()
- .max_w_128()
- .max_h_96()
- .child(Label::new(content.clone()).buffer_font(cx))
- .into_any_element()
- }
- })),
- */
- context,
- })
- }
+ fn file(handle: FileContextHandle, full_path: &Path, cx: &App) -> AddedContext {
+ let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
+ let name = full_path
+ .file_name()
+ .map(|n| n.to_string_lossy().into_owned().into())
+ .unwrap_or_else(|| full_path_string.clone());
+ let parent = full_path
+ .parent()
+ .and_then(|p| p.file_name())
+ .map(|n| n.to_string_lossy().into_owned().into());
+ AddedContext {
+ kind: ContextKind::File,
+ name,
+ parent,
+ tooltip: Some(full_path_string),
+ icon_path: FileIcons::get_icon(&full_path, cx),
+ status: ContextStatus::Ready,
+ render_hover: None,
+ handle: AgentContextHandle::File(handle),
+ }
+ }
- AgentContext::FetchedUrl(ref fetched_url_context) => Some(AddedContext {
- kind: ContextKind::FetchedUrl,
- name: fetched_url_context.url.clone(),
- parent: None,
- tooltip: None,
- icon_path: None,
- status: ContextStatus::Ready,
- render_preview: None,
- context,
- }),
+ fn pending_directory(
+ handle: DirectoryContextHandle,
+ project: &Project,
+ cx: &App,
+ ) -> Option<AddedContext> {
+ let worktree = project.worktree_for_entry(handle.entry_id, cx)?.read(cx);
+ let entry = worktree.entry_for_id(handle.entry_id)?;
+ let full_path = worktree.full_path(&entry.path);
+ Some(Self::directory(handle, &full_path))
+ }
- AgentContext::Thread(ref thread_context) => Some(AddedContext {
- kind: ContextKind::Thread,
- name: thread_context.name(cx),
- parent: None,
- tooltip: None,
- icon_path: None,
- status: if thread_context
- .thread
- .read(cx)
- .is_generating_detailed_summary()
- {
- ContextStatus::Loading {
- message: "Summarizing…".into(),
- }
- } else {
- ContextStatus::Ready
- },
- render_preview: None,
- context,
- }),
+ fn attached_directory(context: &DirectoryContext) -> AddedContext {
+ Self::directory(context.handle.clone(), &context.full_path)
+ }
- AgentContext::Rules(ref user_rules_context) => {
- let name = prompt_store
- .as_ref()?
- .read(cx)
- .metadata(user_rules_context.prompt_id.into())?
- .title?;
- Some(AddedContext {
- kind: ContextKind::Rules,
- name: name.clone(),
- parent: None,
- tooltip: None,
- icon_path: None,
- status: ContextStatus::Ready,
- render_preview: None,
- context,
- })
- }
+ fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext {
+ let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
+ let name = full_path
+ .file_name()
+ .map(|n| n.to_string_lossy().into_owned().into())
+ .unwrap_or_else(|| full_path_string.clone());
+ let parent = full_path
+ .parent()
+ .and_then(|p| p.file_name())
+ .map(|n| n.to_string_lossy().into_owned().into());
+ AddedContext {
+ kind: ContextKind::Directory,
+ name,
+ parent,
+ tooltip: Some(full_path_string),
+ icon_path: None,
+ status: ContextStatus::Ready,
+ render_hover: None,
+ handle: AgentContextHandle::Directory(handle),
+ }
+ }
- AgentContext::Image(ref image_context) => Some(AddedContext {
- kind: ContextKind::Image,
- name: "Image".into(),
- parent: None,
- tooltip: None,
- icon_path: None,
- status: match image_context.status() {
- ImageStatus::Loading => ContextStatus::Loading {
- message: "Loading…".into(),
- },
- ImageStatus::Error => ContextStatus::Error {
- message: "Failed to load image".into(),
- },
- ImageStatus::Ready => ContextStatus::Ready,
+ fn pending_symbol(handle: SymbolContextHandle, cx: &App) -> Option<AddedContext> {
+ let excerpt =
+ ContextFileExcerpt::new(&handle.full_path(cx)?, handle.enclosing_line_range(cx), cx);
+ Some(AddedContext {
+ kind: ContextKind::Symbol,
+ name: handle.symbol.clone(),
+ parent: Some(excerpt.file_name_and_range.clone()),
+ tooltip: None,
+ icon_path: None,
+ status: ContextStatus::Ready,
+ render_hover: {
+ let handle = handle.clone();
+ Some(Rc::new(move |_, cx| {
+ excerpt.hover_view(handle.text(cx), cx).into()
+ }))
+ },
+ handle: AgentContextHandle::Symbol(handle),
+ })
+ }
+
+ fn attached_symbol(context: &SymbolContext, cx: &App) -> AddedContext {
+ let excerpt = ContextFileExcerpt::new(&context.full_path, context.line_range.clone(), cx);
+ AddedContext {
+ kind: ContextKind::Symbol,
+ name: context.handle.symbol.clone(),
+ parent: Some(excerpt.file_name_and_range.clone()),
+ tooltip: None,
+ icon_path: None,
+ status: ContextStatus::Ready,
+ render_hover: {
+ let text = context.text.clone();
+ Some(Rc::new(move |_, cx| {
+ excerpt.hover_view(text.clone(), cx).into()
+ }))
+ },
+ handle: AgentContextHandle::Symbol(context.handle.clone()),
+ }
+ }
+
+ fn pending_selection(handle: SelectionContextHandle, cx: &App) -> Option<AddedContext> {
+ let excerpt = ContextFileExcerpt::new(&handle.full_path(cx)?, handle.line_range(cx), cx);
+ Some(AddedContext {
+ kind: ContextKind::Selection,
+ name: excerpt.file_name_and_range.clone(),
+ parent: excerpt.parent_name.clone(),
+ tooltip: None,
+ icon_path: excerpt.icon_path.clone(),
+ status: ContextStatus::Ready,
+ render_hover: {
+ let handle = handle.clone();
+ Some(Rc::new(move |_, cx| {
+ excerpt.hover_view(handle.text(cx), cx).into()
+ }))
+ },
+ handle: AgentContextHandle::Selection(handle),
+ })
+ }
+
+ fn attached_selection(context: &SelectionContext, cx: &App) -> AddedContext {
+ let excerpt = ContextFileExcerpt::new(&context.full_path, context.line_range.clone(), cx);
+ AddedContext {
+ kind: ContextKind::Selection,
+ name: excerpt.file_name_and_range.clone(),
+ parent: excerpt.parent_name.clone(),
+ tooltip: None,
+ icon_path: excerpt.icon_path.clone(),
+ status: ContextStatus::Ready,
+ render_hover: {
+ let text = context.text.clone();
+ Some(Rc::new(move |_, cx| {
+ excerpt.hover_view(text.clone(), cx).into()
+ }))
+ },
+ handle: AgentContextHandle::Selection(context.handle.clone()),
+ }
+ }
+
+ fn fetched_url(context: FetchedUrlContext) -> AddedContext {
+ AddedContext {
+ kind: ContextKind::FetchedUrl,
+ name: context.url.clone(),
+ parent: None,
+ tooltip: None,
+ icon_path: None,
+ status: ContextStatus::Ready,
+ render_hover: None,
+ handle: AgentContextHandle::FetchedUrl(context),
+ }
+ }
+
+ fn pending_thread(handle: ThreadContextHandle, cx: &App) -> AddedContext {
+ AddedContext {
+ kind: ContextKind::Thread,
+ name: handle.title(cx),
+ parent: None,
+ tooltip: None,
+ icon_path: None,
+ status: if handle.thread.read(cx).is_generating_detailed_summary() {
+ ContextStatus::Loading {
+ message: "Summarizing…".into(),
+ }
+ } else {
+ ContextStatus::Ready
+ },
+ render_hover: {
+ let thread = handle.thread.clone();
+ Some(Rc::new(move |_, cx| {
+ let text = thread.read(cx).latest_detailed_summary_or_text();
+ text_hover_view(text.clone(), cx).into()
+ }))
+ },
+ handle: AgentContextHandle::Thread(handle),
+ }
+ }
+
+ fn attached_thread(context: &ThreadContext) -> AddedContext {
+ AddedContext {
+ kind: ContextKind::Thread,
+ name: context.title.clone(),
+ parent: None,
+ tooltip: None,
+ icon_path: None,
+ status: ContextStatus::Ready,
+ render_hover: {
+ let text = context.text.clone();
+ Some(Rc::new(move |_, cx| {
+ text_hover_view(text.clone(), cx).into()
+ }))
+ },
+ handle: AgentContextHandle::Thread(context.handle.clone()),
+ }
+ }
+
+ fn pending_rules(
+ handle: RulesContextHandle,
+ prompt_store: Option<&Entity<PromptStore>>,
+ cx: &App,
+ ) -> Option<AddedContext> {
+ let title = prompt_store
+ .as_ref()?
+ .read(cx)
+ .metadata(handle.prompt_id.into())?
+ .title
+ .unwrap_or_else(|| "Unnamed Rule".into());
+ Some(AddedContext {
+ kind: ContextKind::Rules,
+ name: title.clone(),
+ parent: None,
+ tooltip: None,
+ icon_path: None,
+ status: ContextStatus::Ready,
+ render_hover: None,
+ handle: AgentContextHandle::Rules(handle),
+ })
+ }
+
+ fn attached_rules(context: &RulesContext) -> AddedContext {
+ let title = context
+ .title
+ .clone()
+ .unwrap_or_else(|| "Unnamed Rule".into());
+ AddedContext {
+ kind: ContextKind::Rules,
+ name: title,
+ parent: None,
+ tooltip: None,
+ icon_path: None,
+ status: ContextStatus::Ready,
+ render_hover: {
+ let text = context.text.clone();
+ Some(Rc::new(move |_, cx| {
+ text_hover_view(text.clone(), cx).into()
+ }))
+ },
+ handle: AgentContextHandle::Rules(context.handle.clone()),
+ }
+ }
+
+ fn image(context: ImageContext) -> AddedContext {
+ AddedContext {
+ kind: ContextKind::Image,
+ name: "Image".into(),
+ parent: None,
+ tooltip: None,
+ icon_path: None,
+ status: match context.status() {
+ ImageStatus::Loading => ContextStatus::Loading {
+ message: "Loading…".into(),
+ },
+ ImageStatus::Error => ContextStatus::Error {
+ message: "Failed to load image".into(),
},
- render_preview: Some(Rc::new({
- let image = image_context.original_image.clone();
- move |_, _| {
+ ImageStatus::Ready => ContextStatus::Ready,
+ },
+ render_hover: Some(Rc::new({
+ let image = context.original_image.clone();
+ move |_, cx| {
+ let image = image.clone();
+ ContextPillHover::new(cx, move |_, _| {
gpui::img(image.clone())
.max_w_96()
.max_h_96()
.into_any_element()
- }
- })),
- context,
- }),
+ })
+ .into()
+ }
+ })),
+ handle: AgentContextHandle::Image(context),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+struct ContextFileExcerpt {
+ pub file_name_and_range: SharedString,
+ pub full_path_and_range: SharedString,
+ pub parent_name: Option<SharedString>,
+ pub icon_path: Option<SharedString>,
+}
+
+impl ContextFileExcerpt {
+ pub fn new(full_path: &Path, line_range: Range<Point>, cx: &App) -> Self {
+ let full_path_string = full_path.to_string_lossy().into_owned();
+ let file_name = full_path
+ .file_name()
+ .map(|n| n.to_string_lossy().into_owned())
+ .unwrap_or_else(|| full_path_string.clone());
+
+ let line_range_text = format!(" ({}-{})", line_range.start.row + 1, line_range.end.row + 1);
+ let mut full_path_and_range = full_path_string;
+ full_path_and_range.push_str(&line_range_text);
+ let mut file_name_and_range = file_name;
+ file_name_and_range.push_str(&line_range_text);
+
+ let parent_name = full_path
+ .parent()
+ .and_then(|p| p.file_name())
+ .map(|n| n.to_string_lossy().into_owned().into());
+
+ let icon_path = FileIcons::get_icon(&full_path, cx);
+
+ ContextFileExcerpt {
+ file_name_and_range: file_name_and_range.into(),
+ full_path_and_range: full_path_and_range.into(),
+ parent_name,
+ icon_path,
}
}
+
+ fn hover_view(&self, text: SharedString, cx: &mut App) -> Entity<ContextPillHover> {
+ let icon_path = self.icon_path.clone();
+ let full_path_and_range = self.full_path_and_range.clone();
+ ContextPillHover::new(cx, move |_, cx| {
+ v_flex()
+ .child(
+ h_flex()
+ .gap_0p5()
+ .w_full()
+ .max_w_full()
+ .border_b_1()
+ .border_color(cx.theme().colors().border.opacity(0.6))
+ .children(
+ icon_path
+ .clone()
+ .map(Icon::from_path)
+ .map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)),
+ )
+ .child(
+ // TODO: make this truncate on the left.
+ Label::new(full_path_and_range.clone())
+ .size(LabelSize::Small)
+ .ml_1(),
+ ),
+ )
+ .child(
+ div()
+ .id("context-pill-hover-contents")
+ .overflow_scroll()
+ .max_w_128()
+ .max_h_96()
+ .child(Label::new(text.clone()).buffer_font(cx)),
+ )
+ .into_any_element()
+ })
+ }
+}
+
+fn text_hover_view(content: SharedString, cx: &mut App) -> Entity<ContextPillHover> {
+ ContextPillHover::new(cx, move |_, _| {
+ div()
+ .id("context-pill-hover-contents")
+ .overflow_scroll()
+ .max_w_128()
+ .max_h_96()
+ .child(content.clone())
+ .into_any_element()
+ })
}
-struct ContextPillPreview {
- render_preview: Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>,
+struct ContextPillHover {
+ render_hover: Box<dyn Fn(&mut Window, &mut App) -> AnyElement>,
+}
+
+impl ContextPillHover {
+ fn new(
+ cx: &mut App,
+ render_hover: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
+ ) -> Entity<Self> {
+ cx.new(|_| Self {
+ render_hover: Box::new(render_hover),
+ })
+ }
}
-impl Render for ContextPillPreview {
+impl Render for ContextPillHover {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
tooltip_container(window, cx, move |this, window, cx| {
this.occlude()
.on_mouse_move(|_, _, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
- .child((self.render_preview)(window, cx))
+ .child((self.render_hover)(window, cx))
})
}
}
-// TODO: Component commented out due to new dependency on `Project`.
-/*
impl Component for AddedContext {
fn scope() -> ComponentScope {
ComponentScope::Agent
@@ -505,47 +717,38 @@ impl Component for AddedContext {
"AddedContext"
}
- fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
- let next_context_id = ContextId::zero();
+ fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
+ let mut next_context_id = ContextId::zero();
let image_ready = (
"Ready",
- AddedContext::new(
- AgentContext::Image(ImageContext {
- context_id: next_context_id.post_inc(),
- original_image: Arc::new(Image::empty()),
- image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
- }),
- cx,
- ),
+ AddedContext::image(ImageContext {
+ context_id: next_context_id.post_inc(),
+ original_image: Arc::new(Image::empty()),
+ image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
+ }),
);
let image_loading = (
"Loading",
- AddedContext::new(
- AgentContext::Image(ImageContext {
- context_id: next_context_id.post_inc(),
- original_image: Arc::new(Image::empty()),
- image_task: cx
- .background_spawn(async move {
- smol::Timer::after(Duration::from_secs(60 * 5)).await;
- Some(LanguageModelImage::empty())
- })
- .shared(),
- }),
- cx,
- ),
+ AddedContext::image(ImageContext {
+ context_id: next_context_id.post_inc(),
+ original_image: Arc::new(Image::empty()),
+ image_task: cx
+ .background_spawn(async move {
+ smol::Timer::after(Duration::from_secs(60 * 5)).await;
+ Some(LanguageModelImage::empty())
+ })
+ .shared(),
+ }),
);
let image_error = (
"Error",
- AddedContext::new(
- AgentContext::Image(ImageContext {
- context_id: next_context_id.post_inc(),
- original_image: Arc::new(Image::empty()),
- image_task: Task::ready(None).shared(),
- }),
- cx,
- ),
+ AddedContext::image(ImageContext {
+ context_id: next_context_id.post_inc(),
+ original_image: Arc::new(Image::empty()),
+ image_task: Task::ready(None).shared(),
+ }),
);
Some(
@@ -563,8 +766,5 @@ impl Component for AddedContext {
)
.into_any(),
)
-
- None
}
}
-*/