Allow inspection of zeta2's LLM-based context retrieval (#41340)
Release Notes: - N/A --------- Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
co-authored by
Agus Zubiaga
parent
d31194dcf8
commit
60bd417d8b
Generated
+1
@@ -21670,6 +21670,7 @@ dependencies = [
|
||||
name = "zeta2_tools"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"client",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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<Project>,
|
||||
events: impl Iterator<Item = &'a crate::Event>,
|
||||
options: &LlmContextOptions,
|
||||
debug_tx: Option<mpsc::UnboundedSender<ZetaDebugInfo>>,
|
||||
cx: &App,
|
||||
) -> Task<Result<HashMap<Entity<Buffer>, Vec<Range<Anchor>>>>> {
|
||||
let language_model_registry = LanguageModelRegistry::global(cx);
|
||||
@@ -304,11 +308,33 @@ pub fn find_related_excerpts<'a>(
|
||||
snapshot: TextBufferSnapshot,
|
||||
}
|
||||
|
||||
let search_queries = search_calls
|
||||
.iter()
|
||||
.map(|(_, tool_use)| {
|
||||
Ok(serde_json::from_value::<SearchToolInput>(
|
||||
tool_use.input.clone(),
|
||||
)?)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
let mut result_buffers_by_path = HashMap::default();
|
||||
|
||||
for (index, tool_use) in search_calls.into_iter().rev() {
|
||||
let call = serde_json::from_value::<SearchToolInput>(tool_use.input.clone())?;
|
||||
|
||||
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() {
|
||||
|
||||
+71
-14
@@ -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<EntityId, ZetaProject>,
|
||||
options: ZetaOptions,
|
||||
update_required: bool,
|
||||
debug_tx: Option<mpsc::UnboundedSender<PredictionDebugInfo>>,
|
||||
debug_tx: Option<mpsc::UnboundedSender<ZetaDebugInfo>>,
|
||||
}
|
||||
|
||||
#[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<Project>,
|
||||
pub timestamp: Instant,
|
||||
}
|
||||
|
||||
pub struct ZetaEditPredictionDebugInfo {
|
||||
pub request: predict_edits_v3::PredictEditsRequest,
|
||||
pub retrieval_time: TimeDelta,
|
||||
pub buffer: WeakEntity<Buffer>,
|
||||
@@ -143,6 +156,12 @@ pub struct PredictionDebugInfo {
|
||||
pub response_rx: oneshot::Receiver<Result<predict_edits_v3::PredictEditsResponse, String>>,
|
||||
}
|
||||
|
||||
pub struct ZetaSearchQueryDebugInfo {
|
||||
pub project: Entity<Project>,
|
||||
pub timestamp: Instant,
|
||||
pub queries: Vec<SearchToolQuery>,
|
||||
}
|
||||
|
||||
pub type RequestDebugInfo = predict_edits_v3::DebugInfo;
|
||||
|
||||
struct ZetaProject {
|
||||
@@ -303,7 +322,7 @@ impl Zeta {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_info(&mut self) -> mpsc::UnboundedReceiver<PredictionDebugInfo> {
|
||||
pub fn debug_info(&mut self) -> mpsc::UnboundedReceiver<ZetaDebugInfo> {
|
||||
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<Project>) -> impl Iterator<Item = &Event> {
|
||||
static EMPTY_EVENTS: VecDeque<Event> = 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<Project>,
|
||||
) -> impl Iterator<Item = (Entity<Buffer>, &[Range<Anchor>])> {
|
||||
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<EditPredictionUsage> {
|
||||
@@ -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()
|
||||
}));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Project>,
|
||||
zeta: Entity<Zeta>,
|
||||
runs: VecDeque<RetrievalRun>,
|
||||
current_ix: usize,
|
||||
_update_task: Task<Result<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RetrievalRun {
|
||||
editor: Entity<Editor>,
|
||||
search_queries: Vec<SearchToolQuery>,
|
||||
started_at: Instant,
|
||||
search_results_generated_at: Option<Instant>,
|
||||
search_results_executed_at: Option<Instant>,
|
||||
finished_at: Option<Instant>,
|
||||
}
|
||||
|
||||
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<Project>,
|
||||
client: &Arc<Client>,
|
||||
user_store: &Entity<UserStore>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> 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<Self>,
|
||||
) {
|
||||
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<Self>,
|
||||
) {
|
||||
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<Self>,
|
||||
) {
|
||||
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::<Vec<_>>();
|
||||
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<Self>,
|
||||
) {
|
||||
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<Self>,
|
||||
) {
|
||||
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>,
|
||||
) {
|
||||
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>,
|
||||
) {
|
||||
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<Self>,
|
||||
_: &'a App,
|
||||
) -> Option<gpui::AnyView> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.to_any())
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
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<Self>) -> 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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<Self>,
|
||||
) {
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user