From 60bd417d8baf36ae1ebc22cad1ad58eb131a8e30 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 28 Oct 2025 15:26:48 -0700 Subject: [PATCH] Allow inspection of zeta2's LLM-based context retrieval (#41340) Release Notes: - N/A --------- Co-authored-by: Agus Zubiaga --- Cargo.lock | 1 + assets/keymaps/default-linux.json | 7 + assets/keymaps/default-macos.json | 7 + assets/keymaps/default-windows.json | 7 + crates/zeta2/src/related_excerpts.rs | 65 ++- crates/zeta2/src/zeta2.rs | 85 +++- crates/zeta2_tools/Cargo.toml | 1 + crates/zeta2_tools/src/zeta2_context_view.rs | 412 +++++++++++++++++++ crates/zeta2_tools/src/zeta2_tools.rs | 39 +- 9 files changed, 591 insertions(+), 33 deletions(-) create mode 100644 crates/zeta2_tools/src/zeta2_context_view.rs diff --git a/Cargo.lock b/Cargo.lock index ceeb849fe5d009067913558c0bdf9b71acc4ebc3..92dd0f747a0808c017bdc4ed65527c0be6e05bc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21670,6 +21670,7 @@ dependencies = [ name = "zeta2_tools" version = "0.1.0" dependencies = [ + "anyhow", "chrono", "clap", "client", diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 2c5f25a29ca3e54e232cb54fbe54080ac37b2419..4108e601d45f29262896cce036abb08acd17b4f3 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -1298,5 +1298,12 @@ "ctrl-enter up": "dev::Zeta2RatePredictionPositive", "ctrl-enter down": "dev::Zeta2RatePredictionNegative" } + }, + { + "context": "Zeta2Context > Editor", + "bindings": { + "alt-left": "dev::Zeta2ContextGoBack", + "alt-right": "dev::Zeta2ContextGoForward" + } } ] diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index f0a165e462a009b826302469e1fc32182c9a4d27..65092df2496cd3c40847a4cbf164e26973648d44 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -1404,5 +1404,12 @@ "cmd-enter up": "dev::Zeta2RatePredictionPositive", "cmd-enter down": "dev::Zeta2RatePredictionNegative" } + }, + { + "context": "Zeta2Context > Editor", + "bindings": { + "alt-left": "dev::Zeta2ContextGoBack", + "alt-right": "dev::Zeta2ContextGoForward" + } } ] diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 5c84bb182adf7163d8330828005276405c918f9c..f867517027e12e692683f48723c0f188c5aec48d 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -1327,5 +1327,12 @@ "ctrl-enter up": "dev::Zeta2RatePredictionPositive", "ctrl-enter down": "dev::Zeta2RatePredictionNegative" } + }, + { + "context": "Zeta2Context > Editor", + "bindings": { + "alt-left": "dev::Zeta2ContextGoBack", + "alt-right": "dev::Zeta2ContextGoForward" + } } ] diff --git a/crates/zeta2/src/related_excerpts.rs b/crates/zeta2/src/related_excerpts.rs index 2f30ee15dc72720fca896580febc9fa75b1bc346..7434dbed9e48bb2dcf98131177dc65b2f3930094 100644 --- a/crates/zeta2/src/related_excerpts.rs +++ b/crates/zeta2/src/related_excerpts.rs @@ -1,10 +1,13 @@ -use std::{cmp::Reverse, fmt::Write, ops::Range, path::PathBuf, sync::Arc}; +use std::{cmp::Reverse, fmt::Write, ops::Range, path::PathBuf, sync::Arc, time::Instant}; -use crate::merge_excerpts::write_merged_excerpts; +use crate::{ + ZetaContextRetrievalDebugInfo, ZetaDebugInfo, ZetaSearchQueryDebugInfo, + merge_excerpts::write_merged_excerpts, +}; use anyhow::{Result, anyhow}; use collections::HashMap; use edit_prediction_context::{EditPredictionExcerpt, EditPredictionExcerptOptions, Line}; -use futures::{StreamExt, stream::BoxStream}; +use futures::{StreamExt, channel::mpsc, stream::BoxStream}; use gpui::{App, AsyncApp, Entity, Task}; use indoc::indoc; use language::{Anchor, Bias, Buffer, OffsetRangeExt, Point, TextBufferSnapshot, ToPoint as _}; @@ -61,22 +64,22 @@ const SEARCH_TOOL_NAME: &str = "search"; /// Search for relevant code /// /// For the best results, run multiple queries at once with a single invocation of this tool. -#[derive(Deserialize, JsonSchema)] -struct SearchToolInput { +#[derive(Clone, Deserialize, JsonSchema)] +pub struct SearchToolInput { /// An array of queries to run for gathering context relevant to the next prediction #[schemars(length(max = 5))] - queries: Box<[SearchToolQuery]>, + pub queries: Box<[SearchToolQuery]>, } -#[derive(Deserialize, JsonSchema)] -struct SearchToolQuery { +#[derive(Debug, Clone, Deserialize, JsonSchema)] +pub struct SearchToolQuery { /// A glob pattern to match file paths in the codebase - glob: String, + pub glob: String, /// A regular expression to match content within the files matched by the glob pattern - regex: String, + pub regex: String, /// Whether the regex is case-sensitive. Defaults to false (case-insensitive). #[serde(default)] - case_sensitive: bool, + pub case_sensitive: bool, } const RESULTS_MESSAGE: &str = indoc! {" @@ -124,6 +127,7 @@ pub fn find_related_excerpts<'a>( project: &Entity, events: impl Iterator, options: &LlmContextOptions, + debug_tx: Option>, cx: &App, ) -> Task, Vec>>>> { let language_model_registry = LanguageModelRegistry::global(cx); @@ -304,11 +308,33 @@ pub fn find_related_excerpts<'a>( snapshot: TextBufferSnapshot, } - let mut result_buffers_by_path = HashMap::default(); + let search_queries = search_calls + .iter() + .map(|(_, tool_use)| { + Ok(serde_json::from_value::( + tool_use.input.clone(), + )?) + }) + .collect::>>()?; + + if let Some(debug_tx) = &debug_tx { + debug_tx + .unbounded_send(ZetaDebugInfo::SearchQueriesGenerated( + ZetaSearchQueryDebugInfo { + project: project.clone(), + timestamp: Instant::now(), + queries: search_queries + .iter() + .flat_map(|call| call.queries.iter().cloned()) + .collect(), + }, + )) + .ok(); + } - for (index, tool_use) in search_calls.into_iter().rev() { - let call = serde_json::from_value::(tool_use.input.clone())?; + let mut result_buffers_by_path = HashMap::default(); + for ((index, tool_use), call) in search_calls.into_iter().zip(search_queries).rev() { let mut excerpts_by_buffer = HashMap::default(); for query in call.queries { @@ -392,6 +418,17 @@ pub fn find_related_excerpts<'a>( }, ], ); + + if let Some(debug_tx) = &debug_tx { + debug_tx + .unbounded_send(ZetaDebugInfo::SearchQueriesExecuted( + ZetaContextRetrievalDebugInfo { + project: project.clone(), + timestamp: Instant::now(), + }, + )) + .ok(); + } } if result_buffers_by_path.is_empty() { diff --git a/crates/zeta2/src/zeta2.rs b/crates/zeta2/src/zeta2.rs index 48eda0f79aec57c6061c2287a80a8075e5badc74..81fc4172592c59ca47527c545ee8d57398ae1247 100644 --- a/crates/zeta2/src/zeta2.rs +++ b/crates/zeta2/src/zeta2.rs @@ -45,8 +45,8 @@ mod related_excerpts; use crate::merge_excerpts::merge_excerpts; use crate::prediction::EditPrediction; -pub use crate::related_excerpts::LlmContextOptions; use crate::related_excerpts::find_related_excerpts; +pub use crate::related_excerpts::{LlmContextOptions, SearchToolQuery}; pub use provider::ZetaEditPredictionProvider; const BUFFER_CHANGE_GROUPING_INTERVAL: Duration = Duration::from_secs(1); @@ -107,7 +107,7 @@ pub struct Zeta { projects: HashMap, options: ZetaOptions, update_required: bool, - debug_tx: Option>, + debug_tx: Option>, } #[derive(Debug, Clone, PartialEq)] @@ -134,7 +134,20 @@ impl ContextMode { } } -pub struct PredictionDebugInfo { +pub enum ZetaDebugInfo { + ContextRetrievalStarted(ZetaContextRetrievalDebugInfo), + SearchQueriesGenerated(ZetaSearchQueryDebugInfo), + SearchQueriesExecuted(ZetaContextRetrievalDebugInfo), + ContextRetrievalFinished(ZetaContextRetrievalDebugInfo), + EditPredicted(ZetaEditPredictionDebugInfo), +} + +pub struct ZetaContextRetrievalDebugInfo { + pub project: Entity, + pub timestamp: Instant, +} + +pub struct ZetaEditPredictionDebugInfo { pub request: predict_edits_v3::PredictEditsRequest, pub retrieval_time: TimeDelta, pub buffer: WeakEntity, @@ -143,6 +156,12 @@ pub struct PredictionDebugInfo { pub response_rx: oneshot::Receiver>, } +pub struct ZetaSearchQueryDebugInfo { + pub project: Entity, + pub timestamp: Instant, + pub queries: Vec, +} + pub type RequestDebugInfo = predict_edits_v3::DebugInfo; struct ZetaProject { @@ -303,7 +322,7 @@ impl Zeta { } } - pub fn debug_info(&mut self) -> mpsc::UnboundedReceiver { + pub fn debug_info(&mut self) -> mpsc::UnboundedReceiver { let (debug_watch_tx, debug_watch_rx) = mpsc::unbounded(); self.debug_tx = Some(debug_watch_tx); debug_watch_rx @@ -324,11 +343,30 @@ impl Zeta { } pub fn history_for_project(&self, project: &Entity) -> impl Iterator { - static EMPTY_EVENTS: VecDeque = VecDeque::new(); self.projects .get(&project.entity_id()) - .map_or(&EMPTY_EVENTS, |project| &project.events) - .iter() + .map(|project| project.events.iter()) + .into_iter() + .flatten() + } + + pub fn context_for_project( + &self, + project: &Entity, + ) -> impl Iterator, &[Range])> { + self.projects + .get(&project.entity_id()) + .and_then(|project| { + Some( + project + .context + .as_ref()? + .iter() + .map(|(buffer, ranges)| (buffer.clone(), ranges.as_slice())), + ) + }) + .into_iter() + .flatten() } pub fn usage(&self, cx: &App) -> Option { @@ -781,24 +819,19 @@ impl Zeta { let debug_response_tx = if let Some(debug_tx) = &debug_tx { let (response_tx, response_rx) = oneshot::channel(); - if !request.referenced_declarations.is_empty() || !request.signatures.is_empty() - { - } else { - }; - let local_prompt = build_prompt(&request) .map(|(prompt, _)| prompt) .map_err(|err| err.to_string()); debug_tx - .unbounded_send(PredictionDebugInfo { + .unbounded_send(ZetaDebugInfo::EditPredicted(ZetaEditPredictionDebugInfo { request: request.clone(), retrieval_time, buffer: buffer.downgrade(), local_prompt, position, response_rx, - }) + })) .ok(); Some(response_tx) } else { @@ -1047,9 +1080,22 @@ impl Zeta { return; }; + let debug_tx = self.debug_tx.clone(); + zeta_project .refresh_context_task .get_or_insert(cx.spawn(async move |this, cx| { + if let Some(debug_tx) = &debug_tx { + debug_tx + .unbounded_send(ZetaDebugInfo::ContextRetrievalStarted( + ZetaContextRetrievalDebugInfo { + project: project.clone(), + timestamp: Instant::now(), + }, + )) + .ok(); + } + let related_excerpts = this .update(cx, |this, cx| { let Some(zeta_project) = this.projects.get(&project.entity_id()) else { @@ -1066,6 +1112,7 @@ impl Zeta { &project, zeta_project.events.iter(), options, + debug_tx, cx, ) }) @@ -1079,6 +1126,16 @@ impl Zeta { }; zeta_project.context = Some(related_excerpts); zeta_project.refresh_context_task.take(); + if let Some(debug_tx) = &this.debug_tx { + debug_tx + .unbounded_send(ZetaDebugInfo::ContextRetrievalFinished( + ZetaContextRetrievalDebugInfo { + project, + timestamp: Instant::now(), + }, + )) + .ok(); + } }) .ok() })); diff --git a/crates/zeta2_tools/Cargo.toml b/crates/zeta2_tools/Cargo.toml index edd1b1eb242c6c02001bec53120425f9a05e5d1d..0877ee6f4661e7dcdbbae5241702951746b74725 100644 --- a/crates/zeta2_tools/Cargo.toml +++ b/crates/zeta2_tools/Cargo.toml @@ -12,6 +12,7 @@ workspace = true path = "src/zeta2_tools.rs" [dependencies] +anyhow.workspace = true chrono.workspace = true client.workspace = true cloud_llm_client.workspace = true diff --git a/crates/zeta2_tools/src/zeta2_context_view.rs b/crates/zeta2_tools/src/zeta2_context_view.rs new file mode 100644 index 0000000000000000000000000000000000000000..421328df2c3f39d61352290c0ca5fd34ff39bb78 --- /dev/null +++ b/crates/zeta2_tools/src/zeta2_context_view.rs @@ -0,0 +1,412 @@ +use std::{ + any::TypeId, + collections::VecDeque, + ops::Add, + sync::Arc, + time::{Duration, Instant}, +}; + +use anyhow::Result; +use client::{Client, UserStore}; +use editor::{Editor, PathKey}; +use futures::StreamExt as _; +use gpui::{ + Animation, AnimationExt, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, + Focusable, ParentElement as _, SharedString, Styled as _, Task, TextAlign, Window, actions, + pulsating_between, +}; +use multi_buffer::MultiBuffer; +use project::Project; +use text::OffsetRangeExt; +use ui::{ + ButtonCommon, Clickable, Color, Disableable, FluentBuilder as _, Icon, IconButton, IconName, + IconSize, InteractiveElement, IntoElement, ListItem, StyledTypography, div, h_flex, v_flex, +}; +use workspace::{Item, ItemHandle as _}; +use zeta2::{ + SearchToolQuery, Zeta, ZetaContextRetrievalDebugInfo, ZetaDebugInfo, ZetaSearchQueryDebugInfo, +}; + +pub struct Zeta2ContextView { + empty_focus_handle: FocusHandle, + project: Entity, + zeta: Entity, + runs: VecDeque, + current_ix: usize, + _update_task: Task>, +} + +#[derive(Debug)] +pub struct RetrievalRun { + editor: Entity, + search_queries: Vec, + started_at: Instant, + search_results_generated_at: Option, + search_results_executed_at: Option, + finished_at: Option, +} + +actions!( + dev, + [ + /// Go to the previous context retrieval run + Zeta2ContextGoBack, + /// Go to the next context retrieval run + Zeta2ContextGoForward + ] +); + +impl Zeta2ContextView { + pub fn new( + project: Entity, + client: &Arc, + user_store: &Entity, + window: &mut gpui::Window, + cx: &mut Context, + ) -> Self { + let zeta = Zeta::global(client, user_store, cx); + + let mut debug_rx = zeta.update(cx, |zeta, _| zeta.debug_info()); + let _update_task = cx.spawn_in(window, async move |this, cx| { + while let Some(event) = debug_rx.next().await { + this.update_in(cx, |this, window, cx| { + this.handle_zeta_event(event, window, cx) + })?; + } + Ok(()) + }); + + Self { + empty_focus_handle: cx.focus_handle(), + project, + runs: VecDeque::new(), + current_ix: 0, + zeta, + _update_task, + } + } + + fn handle_zeta_event( + &mut self, + event: ZetaDebugInfo, + window: &mut gpui::Window, + cx: &mut Context, + ) { + match event { + ZetaDebugInfo::ContextRetrievalStarted(info) => { + if info.project == self.project { + self.handle_context_retrieval_started(info, window, cx); + } + } + ZetaDebugInfo::SearchQueriesGenerated(info) => { + if info.project == self.project { + self.handle_search_queries_generated(info, window, cx); + } + } + ZetaDebugInfo::SearchQueriesExecuted(info) => { + if info.project == self.project { + self.handle_search_queries_executed(info, window, cx); + } + } + ZetaDebugInfo::ContextRetrievalFinished(info) => { + if info.project == self.project { + self.handle_context_retrieval_finished(info, window, cx); + } + } + ZetaDebugInfo::EditPredicted(_) => {} + } + } + + fn handle_context_retrieval_started( + &mut self, + info: ZetaContextRetrievalDebugInfo, + window: &mut Window, + cx: &mut Context, + ) { + if self + .runs + .back() + .is_some_and(|run| run.search_results_executed_at.is_none()) + { + self.runs.pop_back(); + } + + let multibuffer = cx.new(|_| MultiBuffer::new(language::Capability::ReadOnly)); + let editor = cx + .new(|cx| Editor::for_multibuffer(multibuffer, Some(self.project.clone()), window, cx)); + + if self.runs.len() == 32 { + self.runs.pop_front(); + } + + self.runs.push_back(RetrievalRun { + editor, + search_queries: Vec::new(), + started_at: info.timestamp, + search_results_generated_at: None, + search_results_executed_at: None, + finished_at: None, + }); + + cx.notify(); + } + + fn handle_context_retrieval_finished( + &mut self, + info: ZetaContextRetrievalDebugInfo, + window: &mut Window, + cx: &mut Context, + ) { + let Some(run) = self.runs.back_mut() else { + return; + }; + + run.finished_at = Some(info.timestamp); + + let multibuffer = run.editor.read(cx).buffer().clone(); + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.clear(cx); + + let context = self.zeta.read(cx).context_for_project(&self.project); + let mut paths = Vec::new(); + for (buffer, ranges) in context { + let path = PathKey::for_buffer(&buffer, cx); + let snapshot = buffer.read(cx).snapshot(); + let ranges = ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(); + paths.push((path, buffer, ranges)); + } + + for (path, buffer, ranges) in paths { + multibuffer.set_excerpts_for_path(path, buffer, ranges, 0, cx); + } + }); + + run.editor.update(cx, |editor, cx| { + editor.move_to_beginning(&Default::default(), window, cx); + }); + + cx.notify(); + } + + fn handle_search_queries_generated( + &mut self, + info: ZetaSearchQueryDebugInfo, + _window: &mut Window, + cx: &mut Context, + ) { + let Some(run) = self.runs.back_mut() else { + return; + }; + + run.search_results_generated_at = Some(info.timestamp); + run.search_queries = info.queries; + cx.notify(); + } + + fn handle_search_queries_executed( + &mut self, + info: ZetaContextRetrievalDebugInfo, + _window: &mut Window, + cx: &mut Context, + ) { + if self.current_ix + 2 == self.runs.len() { + // Switch to latest when the queries are executed + self.current_ix += 1; + } + + let Some(run) = self.runs.back_mut() else { + return; + }; + + run.search_results_executed_at = Some(info.timestamp); + cx.notify(); + } + + fn handle_go_back( + &mut self, + _: &Zeta2ContextGoBack, + window: &mut Window, + cx: &mut Context, + ) { + self.current_ix = self.current_ix.saturating_sub(1); + cx.focus_self(window); + cx.notify(); + } + + fn handle_go_forward( + &mut self, + _: &Zeta2ContextGoForward, + window: &mut Window, + cx: &mut Context, + ) { + self.current_ix = self + .current_ix + .add(1) + .min(self.runs.len().saturating_sub(1)); + cx.focus_self(window); + cx.notify(); + } + + fn render_informational_footer(&self, cx: &mut Context<'_, Zeta2ContextView>) -> ui::Div { + let is_latest = self.runs.len() == self.current_ix + 1; + let run = &self.runs[self.current_ix]; + + h_flex() + .w_full() + .font_buffer(cx) + .text_xs() + .border_t_1() + .child( + v_flex() + .h_full() + .flex_1() + .children(run.search_queries.iter().enumerate().map(|(ix, query)| { + ListItem::new(ix) + .start_slot( + Icon::new(IconName::MagnifyingGlass) + .color(Color::Muted) + .size(IconSize::Small), + ) + .child(query.regex.clone()) + })), + ) + .child( + v_flex() + .h_full() + .pr_2() + .text_align(TextAlign::Right) + .child( + h_flex() + .justify_end() + .child( + IconButton::new("go-back", IconName::ChevronLeft) + .disabled(self.current_ix == 0 || self.runs.len() < 2) + .tooltip(ui::Tooltip::for_action_title( + "Go to previous run", + &Zeta2ContextGoBack, + )) + .on_click(cx.listener(|this, _, window, cx| { + this.handle_go_back(&Zeta2ContextGoBack, window, cx); + })), + ) + .child( + div() + .child(format!("{}/{}", self.current_ix + 1, self.runs.len())) + .map(|this| { + if self.runs.back().is_some_and(|back| { + back.search_results_executed_at.is_none() + }) { + this.with_animation( + "pulsating-count", + Animation::new(Duration::from_secs(2)) + .repeat() + .with_easing(pulsating_between(0.4, 0.8)), + |label, delta| label.opacity(delta), + ) + .into_any_element() + } else { + this.into_any_element() + } + }), + ) + .child( + IconButton::new("go-forward", IconName::ChevronRight) + .disabled(self.current_ix + 1 == self.runs.len()) + .tooltip(ui::Tooltip::for_action_title( + "Go to next run", + &Zeta2ContextGoBack, + )) + .on_click(cx.listener(|this, _, window, cx| { + this.handle_go_forward(&Zeta2ContextGoForward, window, cx); + })), + ), + ) + .map(|mut div| { + let t0 = run.started_at; + let Some(t1) = run.search_results_generated_at else { + return div.child("Planning search..."); + }; + div = div.child(format!("Planned search: {:>5} ms", (t1 - t0).as_millis())); + + let Some(t2) = run.search_results_executed_at else { + return div.child("Running search..."); + }; + div = div.child(format!("Ran search: {:>5} ms", (t2 - t1).as_millis())); + + let Some(t3) = run.finished_at else { + if is_latest { + return div.child("Filtering results..."); + } else { + return div.child("Canceled"); + } + }; + div.child(format!("Filtered results: {:>5} ms", (t3 - t2).as_millis())) + }), + ) + } +} + +impl Focusable for Zeta2ContextView { + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.runs + .get(self.current_ix) + .map(|run| run.editor.read(cx).focus_handle(cx)) + .unwrap_or_else(|| self.empty_focus_handle.clone()) + } +} + +impl EventEmitter<()> for Zeta2ContextView {} + +impl Item for Zeta2ContextView { + type Event = (); + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Edit Prediction Context".into() + } + + fn buffer_kind(&self, _cx: &App) -> workspace::item::ItemBufferKind { + workspace::item::ItemBufferKind::Multibuffer + } + + fn act_as_type<'a>( + &'a self, + type_id: TypeId, + self_handle: &'a Entity, + _: &'a App, + ) -> Option { + if type_id == TypeId::of::() { + Some(self_handle.to_any()) + } else if type_id == TypeId::of::() { + Some(self.runs.get(self.current_ix)?.editor.to_any()) + } else { + None + } + } +} + +impl gpui::Render for Zeta2ContextView { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { + v_flex() + .key_context("Zeta2Context") + .on_action(cx.listener(Self::handle_go_back)) + .on_action(cx.listener(Self::handle_go_forward)) + .size_full() + .map(|this| { + if self.runs.is_empty() { + this.child( + v_flex() + .size_full() + .justify_center() + .items_center() + .child("No retrieval runs yet"), + ) + } else { + this.child(self.runs[self.current_ix].editor.clone()) + .child(self.render_informational_footer(cx)) + } + }) + } +} diff --git a/crates/zeta2_tools/src/zeta2_tools.rs b/crates/zeta2_tools/src/zeta2_tools.rs index d44852971b3a06b240ab1a827989cf81c0be58de..0b4a59844d7b4a02c2f41ff7654c7df0c4292f7a 100644 --- a/crates/zeta2_tools/src/zeta2_tools.rs +++ b/crates/zeta2_tools/src/zeta2_tools.rs @@ -1,3 +1,5 @@ +mod zeta2_context_view; + use std::{cmp::Reverse, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; use chrono::TimeDelta; @@ -21,16 +23,19 @@ use ui_input::InputField; use util::{ResultExt, paths::PathStyle, rel_path::RelPath}; use workspace::{Item, SplitDirection, Workspace}; use zeta2::{ - ContextMode, DEFAULT_SYNTAX_CONTEXT_OPTIONS, LlmContextOptions, PredictionDebugInfo, Zeta, - Zeta2FeatureFlag, ZetaOptions, + ContextMode, DEFAULT_SYNTAX_CONTEXT_OPTIONS, LlmContextOptions, Zeta, Zeta2FeatureFlag, + ZetaDebugInfo, ZetaEditPredictionDebugInfo, ZetaOptions, }; use edit_prediction_context::{EditPredictionContextOptions, EditPredictionExcerptOptions}; +use zeta2_context_view::Zeta2ContextView; actions!( dev, [ - /// Opens the language server protocol logs viewer. + /// Opens the edit prediction context view. + OpenZeta2ContextView, + /// Opens the edit prediction inspector. OpenZeta2Inspector, /// Rate prediction as positive. Zeta2RatePredictionPositive, @@ -60,6 +65,27 @@ pub fn init(cx: &mut App) { }); }) .detach(); + + cx.observe_new(move |workspace: &mut Workspace, _, _cx| { + workspace.register_action(move |workspace, _: &OpenZeta2ContextView, window, cx| { + let project = workspace.project(); + workspace.split_item( + SplitDirection::Right, + Box::new(cx.new(|cx| { + Zeta2ContextView::new( + project.clone(), + workspace.client(), + workspace.user_store(), + window, + cx, + ) + })), + window, + cx, + ); + }); + }) + .detach(); } // TODO show included diagnostics, and events @@ -320,7 +346,7 @@ impl Zeta2Inspector { fn update_last_prediction( &mut self, - prediction: zeta2::PredictionDebugInfo, + prediction: zeta2::ZetaDebugInfo, window: &mut Window, cx: &mut Context, ) { @@ -340,6 +366,9 @@ impl Zeta2Inspector { let language_registry = self.project.read(cx).languages().clone(); async move |this, cx| { let mut languages = HashMap::default(); + let ZetaDebugInfo::EditPredicted(prediction) = prediction else { + return; + }; for ext in prediction .request .referenced_declarations @@ -450,7 +479,7 @@ impl Zeta2Inspector { editor }); - let PredictionDebugInfo { + let ZetaEditPredictionDebugInfo { response_rx, position, buffer,