Compare commits
2 Commits
visual-key
...
embeddings
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
139c5c59b7 | ||
|
|
781fff220c |
@@ -146,12 +146,28 @@ impl ResolvedEdit {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(description) = &mut self.description {
|
||||
if let Some(other_description) = &other.description {
|
||||
let other_offset_range = other_range.to_offset(buffer);
|
||||
let offset_range = range.to_offset(buffer);
|
||||
|
||||
// If the other range is empty at the start of this edit's range, combine the new text
|
||||
if other_offset_range.is_empty() && other_offset_range.start == offset_range.start {
|
||||
self.new_text = format!("{}\n{}", other.new_text, self.new_text);
|
||||
self.range.start = other_range.start;
|
||||
|
||||
if let Some((description, other_description)) =
|
||||
self.description.as_mut().zip(other.description.as_ref())
|
||||
{
|
||||
*description = format!("{}\n{}", other_description, description)
|
||||
}
|
||||
} else {
|
||||
if let Some((description, other_description)) =
|
||||
self.description.as_mut().zip(other.description.as_ref())
|
||||
{
|
||||
description.push('\n');
|
||||
description.push_str(other_description);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -699,6 +715,73 @@ mod tests {
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Ensure InsertBefore merges correctly with Update of the same text
|
||||
|
||||
assert_edits(
|
||||
"
|
||||
fn foo() {
|
||||
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
vec![
|
||||
AssistantEditKind::InsertBefore {
|
||||
old_text: "
|
||||
fn foo() {"
|
||||
.unindent(),
|
||||
new_text: "
|
||||
fn bar() {
|
||||
qux();
|
||||
}"
|
||||
.unindent(),
|
||||
description: "implement bar".into(),
|
||||
},
|
||||
AssistantEditKind::Update {
|
||||
old_text: "
|
||||
fn foo() {
|
||||
|
||||
}"
|
||||
.unindent(),
|
||||
new_text: "
|
||||
fn foo() {
|
||||
bar();
|
||||
}"
|
||||
.unindent(),
|
||||
description: "call bar in foo".into(),
|
||||
},
|
||||
AssistantEditKind::InsertAfter {
|
||||
old_text: "
|
||||
fn foo() {
|
||||
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
new_text: "
|
||||
fn qux() {
|
||||
// todo
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
description: "implement qux".into(),
|
||||
},
|
||||
],
|
||||
"
|
||||
fn bar() {
|
||||
qux();
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
bar();
|
||||
}
|
||||
|
||||
fn qux() {
|
||||
// todo
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -21,6 +21,24 @@ use axum::{
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use collections::HashMap;
|
||||
use db::TokenUsage;
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ComputeEmbeddingsRequest {
|
||||
pub model: String,
|
||||
pub texts: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ComputeEmbeddingsResponse {
|
||||
pub embeddings: Vec<Embedding>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Embedding {
|
||||
pub digest: Vec<u8>,
|
||||
pub dimensions: Vec<f32>,
|
||||
}
|
||||
|
||||
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use reqwest_client::ReqwestClient;
|
||||
@@ -28,6 +46,7 @@ use rpc::{
|
||||
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
||||
};
|
||||
use rpc::{ListModelsResponse, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME};
|
||||
use sha2::Sha256;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
@@ -113,10 +132,79 @@ impl LlmState {
|
||||
}
|
||||
}
|
||||
|
||||
async fn compute_embeddings_http(
|
||||
Extension(state): Extension<Arc<LlmState>>,
|
||||
Extension(claims): Extension<LlmTokenClaims>,
|
||||
Json(request): Json<proto::ComputeEmbeddings>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let api_key = state
|
||||
.config
|
||||
.openai_api_key
|
||||
.as_ref()
|
||||
.context("no OpenAI API key configured on the server")?;
|
||||
|
||||
let rate_limit: Box<dyn RateLimit> = match claims.plan {
|
||||
proto::Plan::ZedPro => Box::new(ZedProComputeEmbeddingsRateLimit),
|
||||
proto::Plan::Free => Box::new(FreeComputeEmbeddingsRateLimit),
|
||||
};
|
||||
|
||||
state
|
||||
.app_state
|
||||
.rate_limiter
|
||||
.check(&*rate_limit, UserId::from_proto(claims.user_id))
|
||||
.await?;
|
||||
|
||||
let embeddings = match request.model.as_str() {
|
||||
"openai/text-embedding-3-small" => {
|
||||
open_ai::embed(
|
||||
&state.http_client,
|
||||
OPEN_AI_API_URL,
|
||||
api_key,
|
||||
OpenAiEmbeddingModel::TextEmbedding3Small,
|
||||
request.texts.iter().map(|text| text.as_str()),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
provider => return Err(anyhow!("unsupported embedding provider {:?}", provider))?,
|
||||
};
|
||||
|
||||
let embeddings = request
|
||||
.texts
|
||||
.iter()
|
||||
.map(|text| {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(text.as_bytes());
|
||||
let result = hasher.finalize();
|
||||
result.to_vec()
|
||||
})
|
||||
.zip(
|
||||
embeddings
|
||||
.data
|
||||
.into_iter()
|
||||
.map(|embedding| embedding.embedding),
|
||||
)
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
state
|
||||
.db
|
||||
.save_embeddings(&request.model, &embeddings)
|
||||
.await
|
||||
.context("failed to save embeddings")
|
||||
.trace_err();
|
||||
|
||||
Ok(Json(proto::ComputeEmbeddingsResponse {
|
||||
embeddings: embeddings
|
||||
.into_iter()
|
||||
.map(|(digest, dimensions)| proto::Embedding { digest, dimensions })
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn routes() -> Router<(), Body> {
|
||||
Router::new()
|
||||
.route("/models", get(list_models))
|
||||
.route("/completion", post(perform_completion))
|
||||
.route("/compute_embeddings", post(compute_embeddings_http))
|
||||
.layer(middleware::from_fn(validate_api_token))
|
||||
}
|
||||
|
||||
|
||||
@@ -824,7 +824,7 @@ impl X11Client {
|
||||
Event::XkbStateNotify(event) => {
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.xkb.update_mask(
|
||||
dbg!(event).base_mods.into(),
|
||||
event.base_mods.into(),
|
||||
event.latched_mods.into(),
|
||||
event.locked_mods.into(),
|
||||
event.base_group as u32,
|
||||
@@ -836,7 +836,7 @@ impl X11Client {
|
||||
latched_layout: event.latched_group as u32,
|
||||
locked_layout: event.locked_group.into(),
|
||||
};
|
||||
let modifiers = dbg!(Modifiers::from_xkb(&state.xkb));
|
||||
let modifiers = Modifiers::from_xkb(&state.xkb);
|
||||
if state.modifiers == modifiers {
|
||||
drop(state);
|
||||
} else {
|
||||
@@ -919,7 +919,7 @@ impl X11Client {
|
||||
}));
|
||||
}
|
||||
Event::KeyRelease(event) => {
|
||||
let window = self.get_window(dbg!(event).event)?;
|
||||
let window = self.get_window(event.event)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
|
||||
@@ -3216,11 +3216,24 @@ impl<'a> WindowContext<'a> {
|
||||
self.draw();
|
||||
}
|
||||
|
||||
let node_id = self
|
||||
.window
|
||||
.focus
|
||||
.and_then(|focus_id| {
|
||||
self.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.focusable_node_id(focus_id)
|
||||
})
|
||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
||||
|
||||
let dispatch_path = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_path(self.focused_node_id());
|
||||
.dispatch_path(node_id);
|
||||
|
||||
let mut keystroke: Option<Keystroke> = None;
|
||||
|
||||
if let Some(event) = event.downcast_ref::<ModifiersChangedEvent>() {
|
||||
if event.modifiers.number_of_modifiers() == 0
|
||||
@@ -3236,36 +3249,30 @@ impl<'a> WindowContext<'a> {
|
||||
_ => None,
|
||||
};
|
||||
if let Some(key) = key {
|
||||
let keystroke = Keystroke {
|
||||
keystroke = Some(Keystroke {
|
||||
key: key.to_string(),
|
||||
ime_key: None,
|
||||
modifiers: Modifiers::default(),
|
||||
};
|
||||
self.finish_dispatch_keystroke(keystroke, &dispatch_path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if self.window.pending_modifier.modifiers.number_of_modifiers() == 0
|
||||
&& event.modifiers.number_of_modifiers() == 1
|
||||
{
|
||||
self.window.pending_modifier.saw_keystroke = false;
|
||||
self.window.pending_modifier.saw_keystroke = false
|
||||
}
|
||||
self.window.pending_modifier.modifiers = event.modifiers;
|
||||
|
||||
self.finish_dispatch_key_event(event, &dispatch_path);
|
||||
self.window.pending_modifier.modifiers = event.modifiers
|
||||
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
self.window.pending_modifier.saw_keystroke = true;
|
||||
self.finish_dispatch_keystroke(key_down_event.keystroke.clone(), &dispatch_path);
|
||||
} else {
|
||||
self.finish_dispatch_key_event(event, &dispatch_path);
|
||||
keystroke = Some(key_down_event.keystroke.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_dispatch_keystroke(
|
||||
&self,
|
||||
keystroke: Keystroke,
|
||||
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
|
||||
) {
|
||||
let Some(keystroke) = keystroke else {
|
||||
self.finish_dispatch_key_event(event, dispatch_path);
|
||||
return;
|
||||
};
|
||||
|
||||
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
|
||||
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus {
|
||||
currently_pending = PendingInput::default();
|
||||
@@ -3299,8 +3306,7 @@ impl<'a> WindowContext<'a> {
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
// FIXME: valid?
|
||||
.dispatch_path(cx.focused_node_id());
|
||||
.dispatch_path(node_id);
|
||||
|
||||
let to_replay = cx
|
||||
.window
|
||||
@@ -3328,13 +3334,13 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_dispatch_key_event(event, &dispatch_path)
|
||||
self.finish_dispatch_key_event(event, dispatch_path)
|
||||
}
|
||||
|
||||
fn finish_dispatch_key_event(
|
||||
&mut self,
|
||||
event: &dyn Any,
|
||||
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
|
||||
dispatch_path: SmallVec<[DispatchNodeId; 32]>,
|
||||
) {
|
||||
self.dispatch_key_down_up_event(event, &dispatch_path);
|
||||
if !self.propagate_event {
|
||||
|
||||
@@ -372,7 +372,7 @@ impl PickerDelegate for TabSwitcherDelegate {
|
||||
icon.color(git_status_color.unwrap_or_default())
|
||||
});
|
||||
|
||||
let indicator = render_item_indicator(tab_match.item.boxed_clone(), None, cx);
|
||||
let indicator = render_item_indicator(tab_match.item.boxed_clone(), cx);
|
||||
let indicator_color = if let Some(ref indicator) = indicator {
|
||||
indicator.color
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,6 @@ enum IndicatorKind {
|
||||
Dot,
|
||||
Bar,
|
||||
Icon(AnyIcon),
|
||||
Character(char),
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
@@ -38,18 +37,12 @@ impl Indicator {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn character(character: char) -> Self {
|
||||
Self {
|
||||
kind: IndicatorKind::Character(character),
|
||||
color: Color::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Indicator {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let container = div().flex_none();
|
||||
@@ -67,9 +60,6 @@ impl RenderOnce for Indicator {
|
||||
.h_1p5()
|
||||
.rounded_t_md()
|
||||
.bg(self.color.color(cx)),
|
||||
IndicatorKind::Character(character) => container
|
||||
.text_color(self.color.color(cx))
|
||||
.child(character.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1835,18 +1835,6 @@ impl Pane {
|
||||
focus_handle: &FocusHandle,
|
||||
cx: &mut ViewContext<'_, Pane>,
|
||||
) -> impl IntoElement {
|
||||
let pending_keystrokes = cx.pending_input_keystrokes().unwrap_or(&[]);
|
||||
let char_to_type = cx.bindings_for_action(&ActivateItem(ix)).iter().find_map(
|
||||
|keybinding| match keybinding.remaining_keystrokes(pending_keystrokes) {
|
||||
Some([keystroke])
|
||||
if keystroke.modifiers == cx.modifiers() && keystroke.key.len() == 1 =>
|
||||
{
|
||||
keystroke.key.chars().next()
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
let project_path = item.project_path(cx);
|
||||
|
||||
let is_active = ix == self.active_item_index;
|
||||
@@ -1878,7 +1866,7 @@ impl Pane {
|
||||
|
||||
let icon = item.tab_icon(cx);
|
||||
let close_side = &ItemSettings::get_global(cx).close_position;
|
||||
let indicator = render_item_indicator(item.boxed_clone(), char_to_type, cx);
|
||||
let indicator = render_item_indicator(item.boxed_clone(), cx);
|
||||
let item_id = item.item_id();
|
||||
let is_first_item = ix == 0;
|
||||
let is_last_item = ix == self.items.len() - 1;
|
||||
@@ -3009,22 +2997,16 @@ pub fn tab_details(items: &[Box<dyn ItemHandle>], cx: &AppContext) -> Vec<usize>
|
||||
tab_details
|
||||
}
|
||||
|
||||
pub fn render_item_indicator(
|
||||
item: Box<dyn ItemHandle>,
|
||||
char_to_type: Option<char>,
|
||||
cx: &WindowContext,
|
||||
) -> Option<Indicator> {
|
||||
if let Some(char) = char_to_type {
|
||||
return Some(Indicator::character(char));
|
||||
}
|
||||
pub fn render_item_indicator(item: Box<dyn ItemHandle>, cx: &WindowContext) -> Option<Indicator> {
|
||||
maybe!({
|
||||
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
|
||||
(true, _) => Color::Warning,
|
||||
(_, true) => Color::Accent,
|
||||
(false, false) => return None,
|
||||
};
|
||||
|
||||
let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
|
||||
(true, _) => Color::Warning,
|
||||
(_, true) => Color::Accent,
|
||||
(false, false) => return None,
|
||||
};
|
||||
|
||||
Some(Indicator::dot().color(indicator_color))
|
||||
Some(Indicator::dot().color(indicator_color))
|
||||
})
|
||||
}
|
||||
|
||||
impl Render for DraggedTab {
|
||||
|
||||
Reference in New Issue
Block a user