Detailed changes
@@ -1543,6 +1543,13 @@
"model": "codestral-latest",
"max_tokens": 150,
},
+ "sweep": {
+ // When enabled, Sweep will not store edit prediction inputs or outputs.
+ // When disabled, Sweep may collect data including buffer contents,
+ // diagnostics, file paths, repository names, and generated predictions
+ // to improve the service.
+ "privacy_mode": false,
+ },
// Whether edit predictions are enabled when editing text threads in the agent panel.
// This setting has no effect if globally disabled.
"enabled_in_text_threads": true,
@@ -2341,7 +2348,6 @@
"line_indicator_format": "long",
// Set a proxy to use. The proxy protocol is specified by the URI scheme.
//
- // Supported URI scheme: `http`, `https`, `socks4`, `socks4a`, `socks5`,
// `socks5h`. `http` will be used when no scheme is specified.
//
// By default no proxy will be used, or Zed will try get proxy settings from
@@ -11,6 +11,7 @@ use gpui::{
App, AppContext as _, Entity, Global, SharedString, Task,
http_client::{self, AsyncBody, Method},
};
+use language::language_settings::all_language_settings;
use language::{Anchor, Buffer, BufferSnapshot, Point, ToOffset as _};
use language_model::{ApiKeyState, EnvVar, env_var};
use lsp::DiagnosticSeverity;
@@ -44,6 +45,10 @@ impl SweepAi {
inputs: EditPredictionModelInput,
cx: &mut App,
) -> Task<Result<Option<EditPredictionResult>>> {
+ let privacy_mode_enabled = all_language_settings(None, cx)
+ .edit_predictions
+ .sweep
+ .privacy_mode;
let debug_info = self.debug_info.clone();
self.api_token.update(cx, |key_state, cx| {
_ = key_state.load_if_needed(SWEEP_CREDENTIALS_URL, |s| s, cx);
@@ -197,8 +202,7 @@ impl SweepAi {
retrieval_chunks,
recent_user_actions,
use_bytes: true,
- // TODO
- privacy_mode_enabled: false,
+ privacy_mode_enabled,
};
let mut buf: Vec<u8> = Vec::new();
@@ -388,6 +388,8 @@ pub struct EditPredictionSettings {
pub copilot: CopilotSettings,
/// Settings specific to Codestral.
pub codestral: CodestralSettings,
+ /// Settings specific to Sweep.
+ pub sweep: SweepSettings,
/// Whether edit predictions are enabled in the assistant panel.
/// This setting has no effect if globally disabled.
pub enabled_in_text_threads: bool,
@@ -437,6 +439,15 @@ pub struct CodestralSettings {
pub api_url: Option<String>,
}
+#[derive(Clone, Debug, Default)]
+pub struct SweepSettings {
+ /// When enabled, Sweep will not store edit prediction inputs or outputs.
+ /// When disabled, Sweep may collect data including buffer contents,
+ /// diagnostics, file paths, repository names, and generated predictions
+ /// to improve the service.
+ pub privacy_mode: bool,
+}
+
impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name.
pub fn language<'a>(
@@ -663,6 +674,11 @@ impl settings::Settings for AllLanguageSettings {
api_url: codestral.api_url,
};
+ let sweep = edit_predictions.sweep.unwrap();
+ let sweep_settings = SweepSettings {
+ privacy_mode: sweep.privacy_mode.unwrap(),
+ };
+
let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap();
let mut file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)> = FxHashMap::default();
@@ -700,6 +716,7 @@ impl settings::Settings for AllLanguageSettings {
mode: edit_predictions_mode,
copilot: copilot_settings,
codestral: codestral_settings,
+ sweep: sweep_settings,
enabled_in_text_threads,
examples_dir: edit_predictions.examples_dir,
example_capture_rate: edit_predictions.example_capture_rate,
@@ -203,6 +203,8 @@ pub struct EditPredictionSettingsContent {
pub copilot: Option<CopilotSettingsContent>,
/// Settings specific to Codestral.
pub codestral: Option<CodestralSettingsContent>,
+ /// Settings specific to Sweep.
+ pub sweep: Option<SweepSettingsContent>,
/// Whether edit predictions are enabled in the assistant prompt editor.
/// This has no effect if globally disabled.
pub enabled_in_text_threads: Option<bool>,
@@ -250,6 +252,18 @@ pub struct CodestralSettingsContent {
pub api_url: Option<String>,
}
+#[with_fallible_options]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
+pub struct SweepSettingsContent {
+ /// When enabled, Sweep will not store edit prediction inputs or outputs.
+ /// When disabled, Sweep may collect data including buffer contents,
+ /// diagnostics, file paths, repository names, and generated predictions
+ /// to improve the service.
+ ///
+ /// Default: false
+ pub privacy_mode: Option<bool>,
+}
+
/// The mode in which edit predictions should be displayed.
#[derive(
Copy,
@@ -46,7 +46,16 @@ pub(crate) fn render_edit_prediction_setup_page(
"https://app.sweep.dev/".into(),
sweep_api_token(cx),
|_cx| SWEEP_CREDENTIALS_URL,
- None,
+ Some(
+ settings_window
+ .render_sub_page_items_section(
+ sweep_settings().iter().enumerate(),
+ true,
+ window,
+ cx,
+ )
+ .into_any_element(),
+ ),
window,
cx,
)
@@ -63,6 +72,7 @@ pub(crate) fn render_edit_prediction_setup_page(
settings_window
.render_sub_page_items_section(
codestral_settings().iter().enumerate(),
+ true,
window,
cx,
)
@@ -285,6 +295,39 @@ fn render_api_key_provider(
})
}
+fn sweep_settings() -> Box<[SettingsPageItem]> {
+ Box::new([SettingsPageItem::SettingItem(SettingItem {
+ title: "Privacy Mode",
+ description: "When enabled, Sweep will not store edit prediction inputs or outputs. When disabled, Sweep may collect data including buffer contents, diagnostics, file paths, and generated predictions to improve the service.",
+ field: Box::new(SettingField {
+ pick: |settings| {
+ settings
+ .project
+ .all_languages
+ .edit_predictions
+ .as_ref()?
+ .sweep
+ .as_ref()?
+ .privacy_mode
+ .as_ref()
+ },
+ write: |settings, value| {
+ settings
+ .project
+ .all_languages
+ .edit_predictions
+ .get_or_insert_default()
+ .sweep
+ .get_or_insert_default()
+ .privacy_mode = value;
+ },
+ json_path: Some("edit_predictions.sweep.privacy_mode"),
+ }),
+ metadata: None,
+ files: USER,
+ })])
+}
+
fn codestral_settings() -> Box<[SettingsPageItem]> {
Box::new([
SettingsPageItem::SettingItem(SettingItem {
@@ -846,7 +846,8 @@ impl SettingsPageItem {
&self,
settings_window: &SettingsWindow,
item_index: usize,
- is_last: bool,
+ bottom_border: bool,
+ extra_bottom_padding: bool,
window: &mut Window,
cx: &mut Context<SettingsWindow>,
) -> AnyElement {
@@ -854,7 +855,7 @@ impl SettingsPageItem {
let apply_padding = |element: Stateful<Div>| -> Stateful<Div> {
let element = element.pt_4();
- if is_last {
+ if extra_bottom_padding {
element.pb_10()
} else {
element.pb_4()
@@ -933,7 +934,7 @@ impl SettingsPageItem {
.group("setting-item")
.px_8()
.child(field_with_padding)
- .when(!is_last, |this| this.child(Divider::horizontal()))
+ .when(bottom_border, |this| this.child(Divider::horizontal()))
.into_any_element()
}
SettingsPageItem::SubPageLink(sub_page_link) => v_flex()
@@ -1010,7 +1011,7 @@ impl SettingsPageItem {
cx,
)),
)
- .when(!is_last, |this| this.child(Divider::horizontal()))
+ .when(bottom_border, |this| this.child(Divider::horizontal()))
.into_any_element(),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: discriminant_setting_item,
@@ -1036,7 +1037,7 @@ impl SettingsPageItem {
.px_8()
.child(discriminant_element.when(has_sub_fields, |this| this.pb_4())),
)
- .when(!has_sub_fields && !is_last, |this| {
+ .when(!has_sub_fields && bottom_border, |this| {
this.child(h_flex().px_8().child(Divider::horizontal()))
});
@@ -1057,7 +1058,9 @@ impl SettingsPageItem {
.p_4()
.border_t_1()
.when(is_last_sub_field, |this| this.border_b_1())
- .when(is_last_sub_field && is_last, |this| this.mb_8())
+ .when(is_last_sub_field && extra_bottom_padding, |this| {
+ this.mb_8()
+ })
.border_dashed()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().element_background.opacity(0.2)),
@@ -1114,7 +1117,7 @@ impl SettingsPageItem {
}),
),
)
- .when(!is_last, |this| this.child(Divider::horizontal()))
+ .when(bottom_border, |this| this.child(Divider::horizontal()))
.into_any_element(),
}
}
@@ -2927,12 +2930,16 @@ impl SettingsWindow {
return gpui::Empty.into_any_element();
};
- let no_bottom_border = visible_items
+ let next_is_header = visible_items
.next()
.map(|(_, item)| matches!(item, SettingsPageItem::SectionHeader(_)))
.unwrap_or(false);
let is_last = Some(actual_item_index) == last_non_header_index;
+ let is_last_in_section = next_is_header || is_last;
+
+ let bottom_border = !is_last_in_section;
+ let extra_bottom_padding = is_last_in_section;
let item_focus_handle = this.content_handles[current_page_index]
[actual_item_index]
@@ -2946,7 +2953,8 @@ impl SettingsWindow {
.child(item.render(
this,
actual_item_index,
- no_bottom_border || is_last,
+ bottom_border,
+ extra_bottom_padding,
window,
cx,
))
@@ -2974,12 +2982,13 @@ impl SettingsWindow {
.size_full()
.overflow_y_scroll()
.track_scroll(scroll_handle);
- self.render_sub_page_items_in(page_content, items, window, cx)
+ self.render_sub_page_items_in(page_content, items, false, window, cx)
}
fn render_sub_page_items_section<'a, Items>(
&self,
items: Items,
+ is_inline_section: bool,
window: &mut Window,
cx: &mut Context<SettingsWindow>,
) -> impl IntoElement
@@ -2987,13 +2996,14 @@ impl SettingsWindow {
Items: Iterator<Item = (usize, &'a SettingsPageItem)>,
{
let page_content = v_flex().id("settings-ui-sub-page-section").size_full();
- self.render_sub_page_items_in(page_content, items, window, cx)
+ self.render_sub_page_items_in(page_content, items, is_inline_section, window, cx)
}
fn render_sub_page_items_in<'a, Items>(
&self,
page_content: Stateful<Div>,
items: Items,
+ is_inline_section: bool,
window: &mut Window,
cx: &mut Context<SettingsWindow>,
) -> impl IntoElement
@@ -3030,12 +3040,14 @@ impl SettingsWindow {
})
.children(items.clone().into_iter().enumerate().map(
|(index, (actual_item_index, item))| {
- let no_bottom_border =
- items.get(index + 1).is_some_and(|(_, next_item)| {
- matches!(next_item, SettingsPageItem::SectionHeader(_))
- });
+ let is_last_item = Some(index) == last_non_header_index;
+ let next_is_header = items.get(index + 1).is_some_and(|(_, next_item)| {
+ matches!(next_item, SettingsPageItem::SectionHeader(_))
+ });
+ let bottom_border = !is_inline_section && !next_is_header && !is_last_item;
- let is_last = Some(index) == last_non_header_index;
+ let extra_bottom_padding =
+ !is_inline_section && (next_is_header || is_last_item);
v_flex()
.w_full()
@@ -3044,7 +3056,8 @@ impl SettingsWindow {
.child(item.render(
self,
actual_item_index,
- no_bottom_border || is_last,
+ bottom_border,
+ extra_bottom_padding,
window,
cx,
))