Compare commits
1 Commits
direct-lin
...
rename-set
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6eea5a2415 |
71
Cargo.lock
generated
71
Cargo.lock
generated
@@ -52,9 +52,9 @@ dependencies = [
|
||||
name = "agent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_context_editor",
|
||||
"assistant_settings",
|
||||
"assistant_slash_command",
|
||||
"assistant_tool",
|
||||
"async-watch",
|
||||
@@ -62,6 +62,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"component",
|
||||
"context_server",
|
||||
"convert_case 0.8.0",
|
||||
@@ -133,6 +134,31 @@ dependencies = [
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent_settings"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"deepseek",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"gpui",
|
||||
"indexmap",
|
||||
"language_model",
|
||||
"lmstudio",
|
||||
"log",
|
||||
"ollama",
|
||||
"open_ai",
|
||||
"paths",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -474,9 +500,9 @@ dependencies = [
|
||||
name = "assistant"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_context_editor",
|
||||
"assistant_settings",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"assistant_tool",
|
||||
@@ -536,8 +562,8 @@ dependencies = [
|
||||
name = "assistant_context_editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_settings",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"chrono",
|
||||
@@ -546,6 +572,7 @@ dependencies = [
|
||||
"collections",
|
||||
"context_server",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
@@ -587,32 +614,6 @@ dependencies = [
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_settings"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"collections",
|
||||
"deepseek",
|
||||
"fs",
|
||||
"gpui",
|
||||
"indexmap",
|
||||
"language_model",
|
||||
"lmstudio",
|
||||
"log",
|
||||
"ollama",
|
||||
"open_ai",
|
||||
"paths",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"workspace-hack",
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_slash_command"
|
||||
version = "0.1.0"
|
||||
@@ -710,9 +711,9 @@ dependencies = [
|
||||
name = "assistant_tools"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
"assistant_settings",
|
||||
"assistant_tool",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
@@ -3004,7 +3005,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"assistant",
|
||||
"assistant_context_editor",
|
||||
"assistant_settings",
|
||||
"assistant_slash_command",
|
||||
"assistant_tool",
|
||||
"async-stripe",
|
||||
@@ -5004,8 +5004,8 @@ name = "eval"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent",
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_settings",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"async-trait",
|
||||
@@ -6064,9 +6064,9 @@ dependencies = [
|
||||
name = "git_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"askpass",
|
||||
"assistant_settings",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
"collections",
|
||||
@@ -15085,6 +15085,7 @@ dependencies = [
|
||||
"client",
|
||||
"collections",
|
||||
"db",
|
||||
"feature_flags",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"notifications",
|
||||
@@ -18693,17 +18694,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.187.0"
|
||||
version = "0.186.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"agent",
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
"askpass",
|
||||
"assets",
|
||||
"assistant",
|
||||
"assistant_context_editor",
|
||||
"assistant_settings",
|
||||
"assistant_tools",
|
||||
"async-watch",
|
||||
"audio",
|
||||
|
||||
@@ -3,12 +3,12 @@ resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/agent",
|
||||
"crates/agent_settings",
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/assistant_context_editor",
|
||||
"crates/assistant_settings",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_slash_commands",
|
||||
"crates/assistant_tool",
|
||||
@@ -216,7 +216,7 @@ askpass = { path = "crates/askpass" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||
assistant_settings = { path = "crates/assistant_settings" }
|
||||
agent_settings = { path = "crates/agent_settings" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||
assistant_tool = { path = "crates/assistant_tool" }
|
||||
|
||||
|
Before Width: | Height: | Size: 474 B After Width: | Height: | Size: 474 B |
@@ -237,14 +237,11 @@
|
||||
"save": "workspace::Save",
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"ctrl-alt-/": "agent::ToggleModelSelector",
|
||||
"shift-enter": "assistant::Split",
|
||||
"ctrl-r": "assistant::CycleMessageRole",
|
||||
"enter": "assistant::ConfirmCommand",
|
||||
"alt-enter": "editor::Newline",
|
||||
"ctrl-k c": "assistant::CopyCode",
|
||||
"ctrl-g": "search::SelectNextMatch",
|
||||
"ctrl-shift-g": "search::SelectPreviousMatch",
|
||||
"ctrl-k l": "agent::OpenRulesLibrary"
|
||||
"alt-enter": "editor::Newline"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -261,8 +258,7 @@
|
||||
"ctrl-shift-o": "agent::ToggleNavigationMenu",
|
||||
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
||||
"shift-escape": "agent::ExpandMessageEditor",
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus"
|
||||
"ctrl-alt-e": "agent::RemoveAllContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -614,11 +614,13 @@
|
||||
//
|
||||
// Default: main
|
||||
"fallback_branch_name": "main",
|
||||
|
||||
// Whether to sort entries in the panel by path
|
||||
// or by status (the default).
|
||||
//
|
||||
// Default: false
|
||||
"sort_by_path": false,
|
||||
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the git panel.
|
||||
//
|
||||
@@ -644,8 +646,6 @@
|
||||
"version": "2",
|
||||
// Whether the agent is enabled.
|
||||
"enabled": true,
|
||||
/// What completion mode to start new threads in, if available. Can be 'normal' or 'max'.
|
||||
"preferred_completion_mode": "normal",
|
||||
// Whether to show the agent panel button in the status bar.
|
||||
"button": true,
|
||||
// Where to dock the agent panel. Can be 'left', 'right' or 'bottom'.
|
||||
@@ -668,28 +668,6 @@
|
||||
// The model to use.
|
||||
"model": "claude-3-7-sonnet-latest"
|
||||
},
|
||||
// Additional parameters for language model requests. When making a request to a model, parameters will be taken
|
||||
// from the last entry in this list that matches the model's provider and name. In each entry, both provider
|
||||
// and model are optional, so that you can specify parameters for either one.
|
||||
"model_parameters": [
|
||||
// To set parameters for all requests to OpenAI models:
|
||||
// {
|
||||
// "provider": "openai",
|
||||
// "temperature": 0.5
|
||||
// }
|
||||
//
|
||||
// To set parameters for all requests in general:
|
||||
// {
|
||||
// "temperature": 0
|
||||
// }
|
||||
//
|
||||
// To set parameters for a specific provider and model:
|
||||
// {
|
||||
// "provider": "zed.dev",
|
||||
// "model": "claude-3-7-sonnet-latest",
|
||||
// "temperature": 1.0
|
||||
// }
|
||||
],
|
||||
// When enabled, the agent can run potentially destructive actions without asking for your confirmation.
|
||||
"always_allow_tool_actions": false,
|
||||
// When enabled, the agent will stream edits.
|
||||
|
||||
@@ -21,7 +21,7 @@ test-support = [
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
agent_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-watch.workspace = true
|
||||
@@ -29,6 +29,7 @@ buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
component.workspace = true
|
||||
context_server.workspace = true
|
||||
convert_case.workspace = true
|
||||
|
||||
@@ -12,8 +12,8 @@ use crate::tool_use::{PendingToolUseStatus, ToolUse};
|
||||
use crate::ui::{
|
||||
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
|
||||
};
|
||||
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
|
||||
use anyhow::Context as _;
|
||||
use assistant_settings::{AssistantSettings, NotifyWhenAgentWaiting};
|
||||
use assistant_tool::ToolUseStatus;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::actions::{MoveUp, Paste};
|
||||
@@ -43,6 +43,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use text::ToPoint;
|
||||
use theme::ThemeSettings;
|
||||
use ui::utils::WithRemSize;
|
||||
use ui::{
|
||||
Disclosure, IconButton, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
|
||||
Tooltip, prelude::*,
|
||||
@@ -1144,7 +1145,7 @@ impl ActiveThread {
|
||||
.summary()
|
||||
.unwrap_or("Agent Panel".into());
|
||||
|
||||
match AssistantSettings::get_global(cx).notify_when_agent_waiting {
|
||||
match AgentSettings::get_global(cx).notify_when_agent_waiting {
|
||||
NotifyWhenAgentWaiting::PrimaryScreen => {
|
||||
if let Some(primary) = cx.primary_display() {
|
||||
self.pop_up(icon, caption.into(), title.clone(), window, primary, cx);
|
||||
@@ -1282,6 +1283,9 @@ impl ActiveThread {
|
||||
return;
|
||||
};
|
||||
|
||||
// Cancel any ongoing streaming when user starts editing a previous message
|
||||
self.cancel_last_completion(window, cx);
|
||||
|
||||
let editor = crate::message_editor::create_editor(
|
||||
self.workspace.clone(),
|
||||
self.context_store.downgrade(),
|
||||
@@ -1413,10 +1417,7 @@ impl ActiveThread {
|
||||
messages: vec![request_message],
|
||||
tools: vec![],
|
||||
stop: vec![],
|
||||
temperature: AssistantSettings::temperature_for_model(
|
||||
&configured_model.model,
|
||||
cx,
|
||||
),
|
||||
temperature: None,
|
||||
};
|
||||
|
||||
Some(configured_model.model.count_tokens(request, cx))
|
||||
@@ -2075,185 +2076,202 @@ impl ActiveThread {
|
||||
|
||||
let panel_background = cx.theme().colors().panel_background;
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.map(|parent| {
|
||||
if let Some(checkpoint) = checkpoint.filter(|_| !is_generating) {
|
||||
let mut is_pending = false;
|
||||
let mut error = None;
|
||||
if let Some(last_restore_checkpoint) =
|
||||
self.thread.read(cx).last_restore_checkpoint()
|
||||
{
|
||||
if last_restore_checkpoint.message_id() == message_id {
|
||||
match last_restore_checkpoint {
|
||||
LastRestoreCheckpoint::Pending { .. } => is_pending = true,
|
||||
LastRestoreCheckpoint::Error { error: err, .. } => {
|
||||
error = Some(err.clone());
|
||||
WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
|
||||
.size_full()
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.map(|parent| {
|
||||
if let Some(checkpoint) = checkpoint.filter(|_| !is_generating) {
|
||||
let mut is_pending = false;
|
||||
let mut error = None;
|
||||
if let Some(last_restore_checkpoint) =
|
||||
self.thread.read(cx).last_restore_checkpoint()
|
||||
{
|
||||
if last_restore_checkpoint.message_id() == message_id {
|
||||
match last_restore_checkpoint {
|
||||
LastRestoreCheckpoint::Pending { .. } => is_pending = true,
|
||||
LastRestoreCheckpoint::Error { error: err, .. } => {
|
||||
error = Some(err.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let restore_checkpoint_button =
|
||||
Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
|
||||
.icon(if error.is_some() {
|
||||
IconName::XCircle
|
||||
} else {
|
||||
IconName::Undo
|
||||
})
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_color(if error.is_some() {
|
||||
Some(Color::Error)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.label_size(LabelSize::XSmall)
|
||||
.disabled(is_pending)
|
||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||
this.thread.update(cx, |thread, cx| {
|
||||
thread
|
||||
.restore_checkpoint(checkpoint.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}));
|
||||
let restore_checkpoint_button =
|
||||
Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
|
||||
.icon(if error.is_some() {
|
||||
IconName::XCircle
|
||||
} else {
|
||||
IconName::Undo
|
||||
})
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_color(if error.is_some() {
|
||||
Some(Color::Error)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.label_size(LabelSize::XSmall)
|
||||
.disabled(is_pending)
|
||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||
this.thread.update(cx, |thread, cx| {
|
||||
thread
|
||||
.restore_checkpoint(checkpoint.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}));
|
||||
|
||||
let restore_checkpoint_button = if is_pending {
|
||||
restore_checkpoint_button
|
||||
.with_animation(
|
||||
("pulsating-restore-checkpoint-button", ix),
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.6, 1.)),
|
||||
|label, delta| label.alpha(delta),
|
||||
let restore_checkpoint_button = if is_pending {
|
||||
restore_checkpoint_button
|
||||
.with_animation(
|
||||
("pulsating-restore-checkpoint-button", ix),
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.6, 1.)),
|
||||
|label, delta| label.alpha(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
} else if let Some(error) = error {
|
||||
restore_checkpoint_button
|
||||
.tooltip(Tooltip::text(error.to_string()))
|
||||
.into_any_element()
|
||||
} else {
|
||||
restore_checkpoint_button.into_any_element()
|
||||
};
|
||||
|
||||
parent.child(
|
||||
h_flex()
|
||||
.pt_2p5()
|
||||
.px_2p5()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(ui::Divider::horizontal())
|
||||
.child(restore_checkpoint_button)
|
||||
.child(ui::Divider::horizontal()),
|
||||
)
|
||||
.into_any_element()
|
||||
} else if let Some(error) = error {
|
||||
restore_checkpoint_button
|
||||
.tooltip(Tooltip::text(error.to_string()))
|
||||
.into_any_element()
|
||||
} else {
|
||||
restore_checkpoint_button.into_any_element()
|
||||
};
|
||||
|
||||
parent.child(
|
||||
h_flex()
|
||||
.pt_2p5()
|
||||
.px_2p5()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(ui::Divider::horizontal())
|
||||
.child(restore_checkpoint_button)
|
||||
.child(ui::Divider::horizontal()),
|
||||
)
|
||||
} else {
|
||||
parent
|
||||
}
|
||||
})
|
||||
.when(is_first_message, |parent| {
|
||||
parent.child(self.render_rules_item(cx))
|
||||
})
|
||||
.child(styled_message)
|
||||
.when(is_generating && is_last_message, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.h_8()
|
||||
.mt_2()
|
||||
.mb_4()
|
||||
.ml_4()
|
||||
.py_1p5()
|
||||
.when_some(loading_dots, |this, loading_dots| this.child(loading_dots)),
|
||||
)
|
||||
})
|
||||
.when(show_feedback, move |parent| {
|
||||
parent.child(feedback_items).when_some(
|
||||
self.open_feedback_editors.get(&message_id),
|
||||
move |parent, feedback_editor| {
|
||||
let focus_handle = feedback_editor.focus_handle(cx);
|
||||
parent.child(
|
||||
v_flex()
|
||||
.key_context("AgentFeedbackMessageEditor")
|
||||
.on_action(cx.listener(move |this, _: &menu::Cancel, _, cx| {
|
||||
this.open_feedback_editors.remove(&message_id);
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(move |this, _: &menu::Confirm, _, cx| {
|
||||
this.submit_feedback_message(message_id, cx);
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(Self::confirm_editing_message))
|
||||
.mb_2()
|
||||
.mx_4()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(feedback_editor.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.child(
|
||||
Button::new("dismiss-feedback-message", "Cancel")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, _window, cx| {
|
||||
this.open_feedback_editors
|
||||
.remove(&message_id);
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
Button::new(
|
||||
"submit-feedback-message",
|
||||
"Share Feedback",
|
||||
)
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(move |this, _, _window, cx| {
|
||||
this.submit_feedback_message(message_id, cx);
|
||||
cx.notify()
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
} else {
|
||||
parent
|
||||
}
|
||||
})
|
||||
.when(is_first_message, |parent| {
|
||||
parent.child(self.render_rules_item(cx))
|
||||
})
|
||||
.child(styled_message)
|
||||
.when(is_generating && is_last_message, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.h_8()
|
||||
.mt_2()
|
||||
.mb_4()
|
||||
.ml_4()
|
||||
.py_1p5()
|
||||
.when_some(loading_dots, |this, loading_dots| {
|
||||
this.child(loading_dots)
|
||||
}),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.when(after_editing_message, |parent| {
|
||||
// Backdrop to dim out the whole thread below the editing user message
|
||||
parent.relative().child(
|
||||
div()
|
||||
.occlude()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.size_full()
|
||||
.bg(panel_background)
|
||||
.opacity(0.8),
|
||||
)
|
||||
})
|
||||
})
|
||||
.when(show_feedback, move |parent| {
|
||||
parent.child(feedback_items).when_some(
|
||||
self.open_feedback_editors.get(&message_id),
|
||||
move |parent, feedback_editor| {
|
||||
let focus_handle = feedback_editor.focus_handle(cx);
|
||||
parent.child(
|
||||
v_flex()
|
||||
.key_context("AgentFeedbackMessageEditor")
|
||||
.on_action(cx.listener(
|
||||
move |this, _: &menu::Cancel, _, cx| {
|
||||
this.open_feedback_editors.remove(&message_id);
|
||||
cx.notify();
|
||||
},
|
||||
))
|
||||
.on_action(cx.listener(
|
||||
move |this, _: &menu::Confirm, _, cx| {
|
||||
this.submit_feedback_message(message_id, cx);
|
||||
cx.notify();
|
||||
},
|
||||
))
|
||||
.on_action(cx.listener(Self::confirm_editing_message))
|
||||
.mb_2()
|
||||
.mx_4()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(feedback_editor.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.child(
|
||||
Button::new(
|
||||
"dismiss-feedback-message",
|
||||
"Cancel",
|
||||
)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, _window, cx| {
|
||||
this.open_feedback_editors
|
||||
.remove(&message_id);
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
Button::new(
|
||||
"submit-feedback-message",
|
||||
"Share Feedback",
|
||||
)
|
||||
.style(ButtonStyle::Tinted(
|
||||
ui::TintColor::Accent,
|
||||
))
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, _window, cx| {
|
||||
this.submit_feedback_message(
|
||||
message_id, cx,
|
||||
);
|
||||
cx.notify()
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.when(after_editing_message, |parent| {
|
||||
// Backdrop to dim out the whole thread below the editing user message
|
||||
parent.relative().child(
|
||||
div()
|
||||
.occlude()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.size_full()
|
||||
.bg(panel_background)
|
||||
.opacity(0.8),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@@ -3062,7 +3080,7 @@ impl ActiveThread {
|
||||
.on_click(cx.listener(
|
||||
move |this, event, window, cx| {
|
||||
if let Some(fs) = fs.clone() {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
|settings, _| {
|
||||
@@ -3445,11 +3463,6 @@ pub(crate) fn open_active_thread_as_markdown(
|
||||
.unwrap_or_else(|| "Thread".to_string());
|
||||
|
||||
let project = workspace.project().clone();
|
||||
|
||||
if !project.read(cx).is_local() {
|
||||
anyhow::bail!("failed to open active thread as markdown in remote project");
|
||||
}
|
||||
|
||||
let buffer = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer(&markdown, Some(markdown_language), cx)
|
||||
});
|
||||
@@ -3580,3 +3593,152 @@ fn open_editor_at_position(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use assistant_tool::{ToolRegistry, ToolWorkingSet};
|
||||
use editor::EditorSettings;
|
||||
use fs::FakeFs;
|
||||
use gpui::{TestAppContext, VisualTestContext};
|
||||
use language_model::{LanguageModel, fake_provider::FakeLanguageModel};
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
|
||||
use crate::{ContextLoadResult, thread_store};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_current_completion_cancelled_when_message_edited(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (cx, active_thread, thread, model) = setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Insert user message without any context (empty context vector)
|
||||
let message = thread.update(cx, |thread, cx| {
|
||||
let message_id = thread.insert_user_message(
|
||||
"What is the best way to learn Rust?",
|
||||
ContextLoadResult::default(),
|
||||
None,
|
||||
vec![],
|
||||
cx,
|
||||
);
|
||||
thread
|
||||
.message(message_id)
|
||||
.expect("message should exist")
|
||||
.clone()
|
||||
});
|
||||
|
||||
// Stream response to user message
|
||||
thread.update(cx, |thread, cx| {
|
||||
let request = thread.to_completion_request(model.clone(), cx);
|
||||
thread.stream_completion(request, model, cx.active_window(), cx)
|
||||
});
|
||||
let generating = thread.update(cx, |thread, _cx| thread.is_generating());
|
||||
assert!(generating, "There should be one pending completion");
|
||||
|
||||
// Edit the previous message
|
||||
active_thread.update_in(cx, |active_thread, window, cx| {
|
||||
active_thread.start_editing_message(message.id, &message.segments, window, cx);
|
||||
});
|
||||
|
||||
// Check that the stream was cancelled
|
||||
let generating = thread.update(cx, |thread, _cx| thread.is_generating());
|
||||
assert!(!generating, "The completion should have been cancelled");
|
||||
}
|
||||
|
||||
fn init_test_settings(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
AgentSettings::register(cx);
|
||||
prompt_store::init(cx);
|
||||
thread_store::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
language_model::init_settings(cx);
|
||||
ThemeSettings::register(cx);
|
||||
EditorSettings::register(cx);
|
||||
ToolRegistry::default_global(cx);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to create a test project with test files
|
||||
async fn create_test_project(
|
||||
cx: &mut TestAppContext,
|
||||
files: serde_json::Value,
|
||||
) -> Entity<Project> {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/test"), files).await;
|
||||
Project::test(fs, [path!("/test").as_ref()], cx).await
|
||||
}
|
||||
|
||||
async fn setup_test_environment(
|
||||
cx: &mut TestAppContext,
|
||||
project: Entity<Project>,
|
||||
) -> (
|
||||
&mut VisualTestContext,
|
||||
Entity<ActiveThread>,
|
||||
Entity<Thread>,
|
||||
Arc<dyn LanguageModel>,
|
||||
) {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let thread_store = cx
|
||||
.update(|_, cx| {
|
||||
ThreadStore::load(
|
||||
project.clone(),
|
||||
cx.new(|_| ToolWorkingSet::default()),
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let text_thread_store = cx
|
||||
.update(|_, cx| {
|
||||
TextThreadStore::new(project.clone(), prompt_builder, Default::default(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
||||
let context_store = cx.new(|_cx| ContextStore::new(project.downgrade(), None));
|
||||
|
||||
let model = FakeLanguageModel::default();
|
||||
let model: Arc<dyn LanguageModel> = Arc::new(model);
|
||||
|
||||
let language_registry = LanguageRegistry::new(cx.executor());
|
||||
let language_registry = Arc::new(language_registry);
|
||||
|
||||
let active_thread = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
thread_store.clone(),
|
||||
text_thread_store.clone(),
|
||||
context_store.clone(),
|
||||
language_registry.clone(),
|
||||
workspace.downgrade(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
(cx, active_thread, thread, model)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
Keep, KeepAll, OpenAgentDiff, Reject, RejectAll, Thread, ThreadEvent, ui::AnimatedLabel,
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AssistantSettings;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
@@ -1250,9 +1250,9 @@ impl AgentDiff {
|
||||
|
||||
let settings_subscription = cx.observe_global_in::<SettingsStore>(window, {
|
||||
let workspace = workspace.clone();
|
||||
let mut was_active = AssistantSettings::get_global(cx).single_file_review;
|
||||
let mut was_active = AgentSettings::get_global(cx).single_file_review;
|
||||
move |this, window, cx| {
|
||||
let is_active = AssistantSettings::get_global(cx).single_file_review;
|
||||
let is_active = AgentSettings::get_global(cx).single_file_review;
|
||||
if was_active != is_active {
|
||||
was_active = is_active;
|
||||
this.update_reviewing_editors(&workspace, window, cx);
|
||||
@@ -1457,7 +1457,7 @@ impl AgentDiff {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if !AssistantSettings::get_global(cx).single_file_review {
|
||||
if !AgentSettings::get_global(cx).single_file_review {
|
||||
for (editor, _) in self.reviewing_editors.drain() {
|
||||
editor
|
||||
.update(cx, |editor, cx| editor.end_temporary_diff_override(cx))
|
||||
@@ -1732,7 +1732,7 @@ impl editor::Addon for EditorAgentDiffAddon {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Keep, ThreadStore, thread_store};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use editor::EditorSettings;
|
||||
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
|
||||
@@ -1751,7 +1751,7 @@ mod tests {
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
prompt_store::init(cx);
|
||||
thread_store::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
@@ -1907,7 +1907,7 @@ mod tests {
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
prompt_store::init(cx);
|
||||
thread_store::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
|
||||
@@ -27,8 +27,10 @@ mod ui;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::{AgentProfileId, AssistantSettings};
|
||||
use agent_settings::{AgentProfileId, AgentSettings};
|
||||
use client::Client;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::Fs;
|
||||
use gpui::{App, actions, impl_actions};
|
||||
use language::LanguageRegistry;
|
||||
@@ -105,6 +107,8 @@ impl ManageProfiles {
|
||||
|
||||
impl_actions!(agent, [NewThread, ManageProfiles]);
|
||||
|
||||
const NAMESPACE: &str = "agent";
|
||||
|
||||
/// Initializes the `agent` crate.
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -113,7 +117,7 @@ pub fn init(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
thread_store::init(cx);
|
||||
assistant_panel::init(cx);
|
||||
context_server_configuration::init(language_registry, cx);
|
||||
@@ -132,4 +136,25 @@ pub fn init(
|
||||
);
|
||||
cx.observe_new(AddContextServerModal::register).detach();
|
||||
cx.observe_new(ManageProfilesModal::register).detach();
|
||||
|
||||
feature_gate_agent_actions(cx);
|
||||
}
|
||||
|
||||
fn feature_gate_agent_actions(cx: &mut App) {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(NAMESPACE);
|
||||
});
|
||||
|
||||
cx.observe_flag::<Assistant2FeatureFlag, _>(move |is_enabled, cx| {
|
||||
if is_enabled {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.show_namespace(NAMESPACE);
|
||||
});
|
||||
} else {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(NAMESPACE);
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ mod tool_picker;
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use assistant_settings::AssistantSettings;
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
@@ -213,7 +213,7 @@ impl AssistantConfiguration {
|
||||
}
|
||||
|
||||
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let always_allow_tool_actions = AssistantSettings::get_global(cx).always_allow_tool_actions;
|
||||
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
|
||||
|
||||
h_flex()
|
||||
.gap_4()
|
||||
@@ -241,7 +241,7 @@ impl AssistantConfiguration {
|
||||
let fs = self.fs.clone();
|
||||
move |state, _window, cx| {
|
||||
let allow = state == &ToggleState::Selected;
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| {
|
||||
@@ -254,7 +254,7 @@ impl AssistantConfiguration {
|
||||
}
|
||||
|
||||
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let single_file_review = AssistantSettings::get_global(cx).single_file_review;
|
||||
let single_file_review = AgentSettings::get_global(cx).single_file_review;
|
||||
|
||||
h_flex()
|
||||
.gap_4()
|
||||
@@ -279,7 +279,7 @@ impl AssistantConfiguration {
|
||||
let fs = self.fs.clone();
|
||||
move |state, _window, cx| {
|
||||
let allow = state == &ToggleState::Selected;
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| {
|
||||
|
||||
@@ -2,7 +2,7 @@ mod profile_modal_header;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings, builtin_profiles};
|
||||
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use convert_case::{Case, Casing as _};
|
||||
use editor::Editor;
|
||||
@@ -42,7 +42,7 @@ enum Mode {
|
||||
|
||||
impl Mode {
|
||||
pub fn choose_profile(_window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
let mut builtin_profiles = Vec::new();
|
||||
let mut custom_profiles = Vec::new();
|
||||
@@ -196,7 +196,7 @@ impl ManageProfilesModal {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
|
||||
return;
|
||||
};
|
||||
@@ -234,7 +234,7 @@ impl ManageProfilesModal {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
|
||||
return;
|
||||
};
|
||||
@@ -270,7 +270,7 @@ impl ManageProfilesModal {
|
||||
match &self.mode {
|
||||
Mode::ChooseProfile { .. } => {}
|
||||
Mode::NewProfile(mode) => {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
let base_profile = mode
|
||||
.base_profile_id
|
||||
@@ -332,7 +332,7 @@ impl ManageProfilesModal {
|
||||
profile: AgentProfile,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
|
||||
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
|
||||
move |settings, _cx| {
|
||||
settings.create_profile(profile_id, profile).log_err();
|
||||
}
|
||||
@@ -485,7 +485,7 @@ impl ManageProfilesModal {
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
|
||||
settings
|
||||
@@ -518,7 +518,7 @@ impl ManageProfilesModal {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
let profile_id = &settings.default_profile;
|
||||
let profile_name = settings
|
||||
@@ -704,7 +704,7 @@ impl ManageProfilesModal {
|
||||
|
||||
impl Render for ManageProfilesModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
let go_back_item = div()
|
||||
.id("cancel-item")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use assistant_settings::{
|
||||
AgentProfile, AgentProfileContent, AgentProfileId, AssistantSettings, AssistantSettingsContent,
|
||||
use agent_settings::{
|
||||
AgentProfile, AgentProfileContent, AgentProfileId, AgentSettings, AgentSettingsContent,
|
||||
ContextServerPresetContent,
|
||||
};
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
@@ -259,7 +259,7 @@ impl PickerDelegate for ToolPickerDelegate {
|
||||
is_enabled
|
||||
};
|
||||
|
||||
let active_profile_id = &AssistantSettings::get_global(cx).default_profile;
|
||||
let active_profile_id = &AgentSettings::get_global(cx).default_profile;
|
||||
if active_profile_id == &self.profile_id {
|
||||
self.thread_store
|
||||
.update(cx, |this, cx| {
|
||||
@@ -268,12 +268,12 @@ impl PickerDelegate for ToolPickerDelegate {
|
||||
.log_err();
|
||||
}
|
||||
|
||||
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
|
||||
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
|
||||
let profile_id = self.profile_id.clone();
|
||||
let default_profile = self.profile.clone();
|
||||
let server_id = server_id.clone();
|
||||
let tool_name = tool_name.clone();
|
||||
move |settings: &mut AssistantSettingsContent, _cx| {
|
||||
move |settings: &mut AgentSettingsContent, _cx| {
|
||||
settings
|
||||
.v2_setting(|v2_settings| {
|
||||
let profiles = v2_settings.profiles.get_or_insert_default();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use assistant_settings::AssistantSettings;
|
||||
use agent_settings::AgentSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{Entity, FocusHandle, SharedString};
|
||||
|
||||
@@ -63,7 +63,7 @@ impl AssistantModelSelector {
|
||||
);
|
||||
}
|
||||
});
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
@@ -72,7 +72,7 @@ impl AssistantModelSelector {
|
||||
);
|
||||
}
|
||||
ModelType::InlineAssistant => {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
|
||||
@@ -7,13 +7,13 @@ use db::kvp::KEY_VALUE_STORE;
|
||||
use markdown::Markdown;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use agent_settings::{AgentDockPosition, AgentSettings};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_context_editor::{
|
||||
AssistantContext, AssistantPanelDelegate, ConfigurationError, ContextEditor, ContextEvent,
|
||||
SlashCommandCompletionProvider, humanize_token_count, make_lsp_adapter_delegate,
|
||||
render_remaining_tokens,
|
||||
};
|
||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
|
||||
@@ -39,7 +39,6 @@ use search::{BufferSearchBar, buffer_search};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use time::UtcOffset;
|
||||
use ui::utils::WithRemSize;
|
||||
use ui::{
|
||||
Banner, CheckboxWithLabel, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle,
|
||||
ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
|
||||
@@ -47,7 +46,7 @@ use ui::{
|
||||
use util::{ResultExt as _, maybe};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::{CollaboratorId, DraggedSelection, DraggedTab, ToolbarItemView, Workspace};
|
||||
use zed_actions::agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding};
|
||||
use zed_actions::agent::OpenConfiguration;
|
||||
use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
|
||||
use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};
|
||||
use zed_llm_client::UsageLimit;
|
||||
@@ -60,7 +59,6 @@ use crate::message_editor::{MessageEditor, MessageEditorEvent};
|
||||
use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};
|
||||
use crate::thread_history::{EntryTimeFormat, PastContext, PastThread, ThreadHistory};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::AgentOnboardingModal;
|
||||
use crate::{
|
||||
AddContextServer, AgentDiffPane, ContextStore, DeleteRecentlyOpenThread, ExpandMessageEditor,
|
||||
Follow, InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
|
||||
@@ -147,13 +145,6 @@ pub fn init(cx: &mut App) {
|
||||
});
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
|
||||
AgentOnboardingModal::toggle(workspace, window, cx)
|
||||
})
|
||||
.register_action(|_workspace, _: &ResetOnboarding, window, cx| {
|
||||
window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
|
||||
window.refresh();
|
||||
})
|
||||
.register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {
|
||||
set_trial_upsell_dismissed(false, cx);
|
||||
});
|
||||
@@ -178,21 +169,7 @@ enum ActiveView {
|
||||
Configuration,
|
||||
}
|
||||
|
||||
enum WhichFontSize {
|
||||
AgentFont,
|
||||
BufferFont,
|
||||
None,
|
||||
}
|
||||
|
||||
impl ActiveView {
|
||||
pub fn which_font_size_used(&self) -> WhichFontSize {
|
||||
match self {
|
||||
ActiveView::Thread { .. } | ActiveView::History => WhichFontSize::AgentFont,
|
||||
ActiveView::PromptEditor { .. } => WhichFontSize::BufferFont,
|
||||
ActiveView::Configuration => WhichFontSize::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn thread(thread: Entity<Thread>, window: &mut Window, cx: &mut App) -> Self {
|
||||
let summary = thread.read(cx).summary_or_default();
|
||||
|
||||
@@ -510,7 +487,6 @@ impl AssistantPanel {
|
||||
thread_store.clone(),
|
||||
context_store.clone(),
|
||||
[RecentEntry::Thread(thread_id, thread.clone())],
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -765,9 +741,9 @@ impl AssistantPanel {
|
||||
});
|
||||
|
||||
if let Some(other_thread_id) = action.from_thread_id.clone() {
|
||||
let other_thread_task = self.thread_store.update(cx, |this, cx| {
|
||||
this.open_thread(&other_thread_id, window, cx)
|
||||
});
|
||||
let other_thread_task = self
|
||||
.thread_store
|
||||
.update(cx, |this, cx| this.open_thread(&other_thread_id, cx));
|
||||
|
||||
cx.spawn({
|
||||
let context_store = context_store.clone();
|
||||
@@ -968,7 +944,7 @@ impl AssistantPanel {
|
||||
) -> Task<Result<()>> {
|
||||
let open_thread_task = self
|
||||
.thread_store
|
||||
.update(cx, |this, cx| this.open_thread(thread_id, window, cx));
|
||||
.update(cx, |this, cx| this.open_thread(thread_id, cx));
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let thread = open_thread_task.await?;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
@@ -1088,7 +1064,7 @@ impl AssistantPanel {
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.handle_font_size_action(action.persist, px(1.0), cx);
|
||||
self.adjust_font_size(action.persist, px(1.0), cx);
|
||||
}
|
||||
|
||||
pub fn decrease_font_size(
|
||||
@@ -1097,36 +1073,21 @@ impl AssistantPanel {
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.handle_font_size_action(action.persist, px(-1.0), cx);
|
||||
self.adjust_font_size(action.persist, px(-1.0), cx);
|
||||
}
|
||||
|
||||
fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
|
||||
match self.active_view.which_font_size_used() {
|
||||
WhichFontSize::AgentFont => {
|
||||
if persist {
|
||||
update_settings_file::<ThemeSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, cx| {
|
||||
let agent_font_size =
|
||||
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
|
||||
let _ = settings
|
||||
.agent_font_size
|
||||
.insert(theme::clamp_font_size(agent_font_size).0);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
theme::adjust_agent_font_size(cx, |size| {
|
||||
*size += delta;
|
||||
});
|
||||
}
|
||||
}
|
||||
WhichFontSize::BufferFont => {
|
||||
// Prompt editor uses the buffer font size, so allow the action to propagate to the
|
||||
// default handler that changes that font size.
|
||||
cx.propagate();
|
||||
}
|
||||
WhichFontSize::None => {}
|
||||
fn adjust_font_size(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
|
||||
if persist {
|
||||
update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, cx| {
|
||||
let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
|
||||
let _ = settings
|
||||
.agent_font_size
|
||||
.insert(theme::clamp_font_size(agent_font_size).0);
|
||||
});
|
||||
} else {
|
||||
theme::adjust_agent_font_size(cx, |size| {
|
||||
*size += delta;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1218,7 +1179,7 @@ impl AssistantPanel {
|
||||
.map_or(true, |model| model.provider.id() != provider.id())
|
||||
{
|
||||
if let Some(model) = provider.default_model(cx) {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model),
|
||||
@@ -1337,10 +1298,10 @@ impl Focusable for AssistantPanel {
|
||||
}
|
||||
|
||||
fn agent_panel_dock_position(cx: &App) -> DockPosition {
|
||||
match AssistantSettings::get_global(cx).dock {
|
||||
AssistantDockPosition::Left => DockPosition::Left,
|
||||
AssistantDockPosition::Bottom => DockPosition::Bottom,
|
||||
AssistantDockPosition::Right => DockPosition::Right,
|
||||
match AgentSettings::get_global(cx).dock {
|
||||
AgentDockPosition::Left => DockPosition::Left,
|
||||
AgentDockPosition::Bottom => DockPosition::Bottom,
|
||||
AgentDockPosition::Right => DockPosition::Right,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1364,22 +1325,18 @@ impl Panel for AssistantPanel {
|
||||
message_editor.set_dock_position(position, cx);
|
||||
});
|
||||
|
||||
settings::update_settings_file::<AssistantSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => AssistantDockPosition::Left,
|
||||
DockPosition::Bottom => AssistantDockPosition::Bottom,
|
||||
DockPosition::Right => AssistantDockPosition::Right,
|
||||
};
|
||||
settings.set_dock(dock);
|
||||
},
|
||||
);
|
||||
settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => AgentDockPosition::Left,
|
||||
DockPosition::Bottom => AgentDockPosition::Bottom,
|
||||
DockPosition::Right => AgentDockPosition::Right,
|
||||
};
|
||||
settings.set_dock(dock);
|
||||
});
|
||||
}
|
||||
|
||||
fn size(&self, window: &Window, cx: &App) -> Pixels {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
match self.position(window, cx) {
|
||||
DockPosition::Left | DockPosition::Right => {
|
||||
self.width.unwrap_or(settings.default_width)
|
||||
@@ -1404,8 +1361,7 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
|
||||
fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
|
||||
(self.enabled(cx) && AssistantSettings::get_global(cx).button)
|
||||
.then_some(IconName::ZedAssistant)
|
||||
(self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
|
||||
@@ -1421,7 +1377,7 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
|
||||
fn enabled(&self, cx: &App) -> bool {
|
||||
AssistantSettings::get_global(cx).enabled
|
||||
AgentSettings::get_global(cx).enabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1844,10 +1800,6 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn should_render_upsell(&self, cx: &mut Context<Self>) -> bool {
|
||||
if !matches!(self.active_view, ActiveView::Thread { .. }) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.hide_trial_upsell || dismissed_trial_upsell() {
|
||||
return false;
|
||||
}
|
||||
@@ -2591,46 +2543,6 @@ impl AssistantPanel {
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_prompt_editor(
|
||||
&self,
|
||||
context_editor: &Entity<ContextEditor>,
|
||||
buffer_search_bar: &Entity<BufferSearchBar>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Div {
|
||||
let mut registrar = buffer_search::DivRegistrar::new(
|
||||
|this, _, _cx| match &this.active_view {
|
||||
ActiveView::PromptEditor {
|
||||
buffer_search_bar, ..
|
||||
} => Some(buffer_search_bar.clone()),
|
||||
_ => None,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
BufferSearchBar::register(&mut registrar);
|
||||
registrar
|
||||
.into_div()
|
||||
.size_full()
|
||||
.relative()
|
||||
.map(|parent| {
|
||||
buffer_search_bar.update(cx, |buffer_search_bar, cx| {
|
||||
if buffer_search_bar.is_dismissed() {
|
||||
return parent;
|
||||
}
|
||||
parent.child(
|
||||
div()
|
||||
.p(DynamicSpacing::Base08.rems(cx))
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(buffer_search_bar.render(window, cx)),
|
||||
)
|
||||
})
|
||||
})
|
||||
.child(context_editor.clone())
|
||||
.child(self.render_drag_target(cx))
|
||||
}
|
||||
|
||||
fn render_drag_target(&self, cx: &Context<Self>) -> Div {
|
||||
let is_local = self.project.read(cx).is_local();
|
||||
div()
|
||||
@@ -2754,41 +2666,6 @@ impl AssistantPanel {
|
||||
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let content = match &self.active_view {
|
||||
ActiveView::Thread { .. } => v_flex()
|
||||
.relative()
|
||||
.justify_between()
|
||||
.size_full()
|
||||
.child(self.render_active_thread_or_empty_state(window, cx))
|
||||
.children(self.render_tool_use_limit_reached(cx))
|
||||
.child(h_flex().child(self.message_editor.clone()))
|
||||
.children(self.render_last_error(cx))
|
||||
.child(self.render_drag_target(cx))
|
||||
.into_any(),
|
||||
ActiveView::History => self.history.clone().into_any_element(),
|
||||
ActiveView::PromptEditor {
|
||||
context_editor,
|
||||
buffer_search_bar,
|
||||
..
|
||||
} => self
|
||||
.render_prompt_editor(context_editor, buffer_search_bar, window, cx)
|
||||
.into_any(),
|
||||
ActiveView::Configuration => v_flex()
|
||||
.size_full()
|
||||
.children(self.configuration.clone())
|
||||
.into_any(),
|
||||
};
|
||||
|
||||
let content = match self.active_view.which_font_size_used() {
|
||||
WhichFontSize::AgentFont => {
|
||||
WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
|
||||
.size_full()
|
||||
.child(content)
|
||||
.into_any()
|
||||
}
|
||||
_ => content,
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.key_context(self.key_context())
|
||||
.justify_between()
|
||||
@@ -2814,7 +2691,60 @@ impl Render for AssistantPanel {
|
||||
.on_action(cx.listener(Self::reset_font_size))
|
||||
.child(self.render_toolbar(window, cx))
|
||||
.children(self.render_trial_upsell(window, cx))
|
||||
.child(content)
|
||||
.map(|parent| match &self.active_view {
|
||||
ActiveView::Thread { .. } => parent.child(
|
||||
v_flex()
|
||||
.relative()
|
||||
.justify_between()
|
||||
.size_full()
|
||||
.child(self.render_active_thread_or_empty_state(window, cx))
|
||||
.children(self.render_tool_use_limit_reached(cx))
|
||||
.child(h_flex().child(self.message_editor.clone()))
|
||||
.children(self.render_last_error(cx))
|
||||
.child(self.render_drag_target(cx)),
|
||||
),
|
||||
ActiveView::History => parent.child(self.history.clone()),
|
||||
ActiveView::PromptEditor {
|
||||
context_editor,
|
||||
buffer_search_bar,
|
||||
..
|
||||
} => {
|
||||
let mut registrar = buffer_search::DivRegistrar::new(
|
||||
|this, _, _cx| match &this.active_view {
|
||||
ActiveView::PromptEditor {
|
||||
buffer_search_bar, ..
|
||||
} => Some(buffer_search_bar.clone()),
|
||||
_ => None,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
BufferSearchBar::register(&mut registrar);
|
||||
parent.child(
|
||||
registrar
|
||||
.into_div()
|
||||
.size_full()
|
||||
.relative()
|
||||
.map(|parent| {
|
||||
buffer_search_bar.update(cx, |buffer_search_bar, cx| {
|
||||
if buffer_search_bar.is_dismissed() {
|
||||
return parent;
|
||||
}
|
||||
parent.child(
|
||||
div()
|
||||
.p(DynamicSpacing::Base08.rems(cx))
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(buffer_search_bar.render(window, cx)),
|
||||
)
|
||||
})
|
||||
})
|
||||
.child(context_editor.clone())
|
||||
.child(self.render_drag_target(cx)),
|
||||
)
|
||||
}
|
||||
ActiveView::Configuration => parent.children(self.configuration.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::context::ContextLoadResult;
|
||||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use crate::{context::load_context, context_store::ContextStore};
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::HashSet;
|
||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||
@@ -384,7 +383,7 @@ impl CodegenAlternative {
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(&model, user_prompt, cx)?;
|
||||
let request = self.build_request(user_prompt, cx)?;
|
||||
cx.spawn(async move |_, cx| model.stream_completion_text(request.await, &cx).await)
|
||||
.boxed_local()
|
||||
};
|
||||
@@ -394,7 +393,6 @@ impl CodegenAlternative {
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
user_prompt: String,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<LanguageModelRequest>> {
|
||||
@@ -443,8 +441,6 @@ impl CodegenAlternative {
|
||||
}
|
||||
});
|
||||
|
||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
||||
|
||||
Ok(cx.spawn(async move |_cx| {
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
@@ -467,7 +463,7 @@ impl CodegenAlternative {
|
||||
mode: None,
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
temperature: None,
|
||||
messages: vec![request_message],
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -425,9 +425,9 @@ impl ContextPicker {
|
||||
render_thread_context_entry(&view_thread, context_store.clone(), cx)
|
||||
.into_any()
|
||||
},
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
context_picker.update(cx, |this, cx| {
|
||||
this.add_recent_thread(thread.clone(), window, cx)
|
||||
this.add_recent_thread(thread.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
},
|
||||
@@ -459,7 +459,6 @@ impl ContextPicker {
|
||||
fn add_recent_thread(
|
||||
&self,
|
||||
entry: ThreadContextEntry,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(context_store) = self.context_store.upgrade() else {
|
||||
@@ -477,7 +476,7 @@ impl ContextPicker {
|
||||
};
|
||||
|
||||
let open_thread_task =
|
||||
thread_store.update(cx, |this, cx| this.open_thread(&id, window, cx));
|
||||
thread_store.update(cx, |this, cx| this.open_thread(&id, cx));
|
||||
cx.spawn(async move |this, cx| {
|
||||
let thread = open_thread_task.await?;
|
||||
context_store.update(cx, |context_store, cx| {
|
||||
|
||||
@@ -438,15 +438,15 @@ impl ContextPickerCompletionProvider {
|
||||
new_text_len,
|
||||
editor.clone(),
|
||||
context_store.clone(),
|
||||
move |window, cx| match &thread_entry {
|
||||
move |cx| match &thread_entry {
|
||||
ThreadContextEntry::Thread { id, .. } => {
|
||||
let thread_id = id.clone();
|
||||
let context_store = context_store.clone();
|
||||
let thread_store = thread_store.clone();
|
||||
window.spawn::<_, Option<_>>(cx, async move |cx| {
|
||||
cx.spawn::<_, Option<_>>(async move |cx| {
|
||||
let thread: Entity<Thread> = thread_store
|
||||
.update_in(cx, |thread_store, window, cx| {
|
||||
thread_store.open_thread(&thread_id, window, cx)
|
||||
.update(cx, |thread_store, cx| {
|
||||
thread_store.open_thread(&thread_id, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await
|
||||
@@ -507,7 +507,7 @@ impl ContextPickerCompletionProvider {
|
||||
new_text_len,
|
||||
editor.clone(),
|
||||
context_store.clone(),
|
||||
move |_, cx| {
|
||||
move |cx| {
|
||||
let user_prompt_id = rules.prompt_id;
|
||||
let context = context_store.update(cx, |context_store, cx| {
|
||||
context_store.add_rules(user_prompt_id, false, cx)
|
||||
@@ -544,7 +544,7 @@ impl ContextPickerCompletionProvider {
|
||||
new_text_len,
|
||||
editor.clone(),
|
||||
context_store.clone(),
|
||||
move |_, cx| {
|
||||
move |cx| {
|
||||
let context_store = context_store.clone();
|
||||
let http_client = http_client.clone();
|
||||
let url_to_fetch = url_to_fetch.clone();
|
||||
@@ -629,7 +629,7 @@ impl ContextPickerCompletionProvider {
|
||||
new_text_len,
|
||||
editor,
|
||||
context_store.clone(),
|
||||
move |_, cx| {
|
||||
move |cx| {
|
||||
if is_directory {
|
||||
Task::ready(
|
||||
context_store
|
||||
@@ -700,7 +700,7 @@ impl ContextPickerCompletionProvider {
|
||||
new_text_len,
|
||||
editor.clone(),
|
||||
context_store.clone(),
|
||||
move |_, cx| {
|
||||
move |cx| {
|
||||
let symbol = symbol.clone();
|
||||
let context_store = context_store.clone();
|
||||
let workspace = workspace.clone();
|
||||
@@ -954,13 +954,10 @@ fn confirm_completion_callback(
|
||||
content_len: usize,
|
||||
editor: Entity<Editor>,
|
||||
context_store: Entity<ContextStore>,
|
||||
add_context_fn: impl Fn(&mut Window, &mut App) -> Task<Option<AgentContextHandle>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
add_context_fn: impl Fn(&mut App) -> Task<Option<AgentContextHandle>> + Send + Sync + 'static,
|
||||
) -> Arc<dyn Fn(CompletionIntent, &mut Window, &mut App) -> bool + Send + Sync> {
|
||||
Arc::new(move |_, window, cx| {
|
||||
let context = add_context_fn(window, cx);
|
||||
let context = add_context_fn(cx);
|
||||
|
||||
let crease_text = crease_text.clone();
|
||||
let crease_icon_path = crease_icon_path.clone();
|
||||
|
||||
@@ -154,7 +154,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let Some(entry) = self.matches.get(self.selected_index) else {
|
||||
return;
|
||||
};
|
||||
@@ -165,7 +165,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
return;
|
||||
};
|
||||
let open_thread_task =
|
||||
thread_store.update(cx, |this, cx| this.open_thread(&id, window, cx));
|
||||
thread_store.update(cx, |this, cx| this.open_thread(&id, cx));
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let thread = open_thread_task.await?;
|
||||
|
||||
@@ -115,7 +115,7 @@ impl Tool for ContextServerTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result.into())
|
||||
Ok(result)
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@ use gpui::{Entity, Task, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::future::FutureExt;
|
||||
use std::time::Duration;
|
||||
use ui::{App, SharedString, Window};
|
||||
use ui::{App, SharedString};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{
|
||||
@@ -82,7 +82,6 @@ impl HistoryStore {
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
initial_recent_entries: impl IntoIterator<Item = RecentEntry>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let subscriptions = vec![
|
||||
@@ -90,62 +89,56 @@ impl HistoryStore {
|
||||
cx.observe(&context_store, |_, _, cx| cx.notify()),
|
||||
];
|
||||
|
||||
window
|
||||
.spawn(cx, {
|
||||
let thread_store = thread_store.downgrade();
|
||||
let context_store = context_store.downgrade();
|
||||
let this = cx.weak_entity();
|
||||
async move |cx| {
|
||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
||||
let contents = cx
|
||||
.background_spawn(async move { std::fs::read_to_string(path) })
|
||||
.await
|
||||
.ok()?;
|
||||
let entries = serde_json::from_str::<Vec<SerializedRecentEntry>>(&contents)
|
||||
.context("deserializing persisted agent panel navigation history")
|
||||
.log_err()?
|
||||
.into_iter()
|
||||
.take(MAX_RECENTLY_OPENED_ENTRIES)
|
||||
.map(|serialized| match serialized {
|
||||
SerializedRecentEntry::Thread(id) => thread_store
|
||||
.update_in(cx, |thread_store, window, cx| {
|
||||
let thread_id = ThreadId::from(id.as_str());
|
||||
thread_store
|
||||
.open_thread(&thread_id, window, cx)
|
||||
.map_ok(|thread| RecentEntry::Thread(thread_id, thread))
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
async { Err(anyhow!("no thread store")) }.boxed()
|
||||
}),
|
||||
SerializedRecentEntry::Context(id) => context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store
|
||||
.open_local_context(Path::new(&id).into(), cx)
|
||||
.map_ok(RecentEntry::Context)
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
async { Err(anyhow!("no context store")) }.boxed()
|
||||
}),
|
||||
});
|
||||
let entries = join_all(entries)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|result| result.log_err())
|
||||
.collect::<VecDeque<_>>();
|
||||
cx.spawn({
|
||||
let thread_store = thread_store.downgrade();
|
||||
let context_store = context_store.downgrade();
|
||||
async move |this, cx| {
|
||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
||||
let contents = cx
|
||||
.background_spawn(async move { std::fs::read_to_string(path) })
|
||||
.await
|
||||
.ok()?;
|
||||
let entries = serde_json::from_str::<Vec<SerializedRecentEntry>>(&contents)
|
||||
.context("deserializing persisted agent panel navigation history")
|
||||
.log_err()?
|
||||
.into_iter()
|
||||
.take(MAX_RECENTLY_OPENED_ENTRIES)
|
||||
.map(|serialized| match serialized {
|
||||
SerializedRecentEntry::Thread(id) => thread_store
|
||||
.update(cx, |thread_store, cx| {
|
||||
let thread_id = ThreadId::from(id.as_str());
|
||||
thread_store
|
||||
.open_thread(&thread_id, cx)
|
||||
.map_ok(|thread| RecentEntry::Thread(thread_id, thread))
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| async { Err(anyhow!("no thread store")) }.boxed()),
|
||||
SerializedRecentEntry::Context(id) => context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store
|
||||
.open_local_context(Path::new(&id).into(), cx)
|
||||
.map_ok(RecentEntry::Context)
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| async { Err(anyhow!("no context store")) }.boxed()),
|
||||
});
|
||||
let entries = join_all(entries)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|result| result.log_err())
|
||||
.collect::<VecDeque<_>>();
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this.recently_opened_entries.extend(entries);
|
||||
this.recently_opened_entries
|
||||
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
|
||||
})
|
||||
.ok();
|
||||
this.update(cx, |this, _| {
|
||||
this.recently_opened_entries.extend(entries);
|
||||
this.recently_opened_entries
|
||||
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
|
||||
})
|
||||
.ok();
|
||||
|
||||
Some(())
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
Some(())
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
thread_store,
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::{
|
||||
@@ -17,6 +17,7 @@ use editor::{
|
||||
ToDisplayPoint,
|
||||
},
|
||||
};
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagViewExt as _};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task, UpdateGlobal,
|
||||
@@ -65,6 +66,15 @@ pub fn init(
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
inline_assistant.register_workspace(&workspace, window, cx)
|
||||
});
|
||||
|
||||
cx.observe_flag::<Assistant2FeatureFlag, _>(window, {
|
||||
|is_assistant2_enabled, _workspace, _window, cx| {
|
||||
InlineAssistant::update_global(cx, |inline_assistant, _cx| {
|
||||
inline_assistant.is_assistant2_enabled = is_assistant2_enabled;
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -87,6 +97,7 @@ pub struct InlineAssistant {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
is_assistant2_enabled: bool,
|
||||
}
|
||||
|
||||
impl Global for InlineAssistant {}
|
||||
@@ -108,6 +119,7 @@ impl InlineAssistant {
|
||||
prompt_builder,
|
||||
telemetry,
|
||||
fs,
|
||||
is_assistant2_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +145,7 @@ impl InlineAssistant {
|
||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let enabled = AssistantSettings::get_global(cx).enabled;
|
||||
let enabled = AgentSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||
});
|
||||
@@ -176,7 +188,7 @@ impl InlineAssistant {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let is_assistant2_enabled = true;
|
||||
let is_assistant2_enabled = self.is_assistant2_enabled;
|
||||
|
||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
@@ -219,7 +231,7 @@ impl InlineAssistant {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
if !settings.enabled {
|
||||
return;
|
||||
}
|
||||
@@ -1754,7 +1766,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
_: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
if !AssistantSettings::get_global(cx).enabled {
|
||||
if !AgentSettings::get_global(cx).enabled {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::ui::{
|
||||
AnimatedLabel, MaxModeTooltip,
|
||||
preview::{AgentPreview, UsageCallout},
|
||||
};
|
||||
use assistant_settings::{AssistantSettings, CompletionMode};
|
||||
use buffer_diff::BufferDiff;
|
||||
use client::UserStore;
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -17,6 +16,7 @@ use editor::{
|
||||
AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorEvent,
|
||||
EditorMode, EditorStyle, MultiBuffer,
|
||||
};
|
||||
use feature_flags::{FeatureFlagAppExt, NewBillingFeatureFlag};
|
||||
use file_icons::FileIcons;
|
||||
use fs::Fs;
|
||||
use futures::future::Shared;
|
||||
@@ -42,6 +42,7 @@ use ui::{Disclosure, DocumentationSide, KeyBinding, PopoverMenuHandle, Tooltip,
|
||||
use util::{ResultExt as _, maybe};
|
||||
use workspace::dock::DockPosition;
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
use zed_llm_client::CompletionMode;
|
||||
|
||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
||||
use crate::context_store::ContextStore;
|
||||
@@ -463,6 +464,10 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
if !cx.has_flag::<NewBillingFeatureFlag>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let thread = self.thread.read(cx);
|
||||
let model = thread.configured_model();
|
||||
if !model?.model.supports_max_mode() {
|
||||
@@ -637,7 +642,7 @@ impl MessageEditor {
|
||||
this.h(vh(0.8, window)).justify_between()
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
div()
|
||||
.min_h_16()
|
||||
.when(is_editor_expanded, |this| this.h_full())
|
||||
.child({
|
||||
@@ -1069,6 +1074,10 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
|
||||
if !cx.has_flag::<NewBillingFeatureFlag>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_using_zed_provider = self
|
||||
.thread
|
||||
.read(cx)
|
||||
@@ -1130,6 +1139,10 @@ impl MessageEditor {
|
||||
token_usage_ratio: TokenUsageRatio,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Div> {
|
||||
if !cx.has_flag::<NewBillingFeatureFlag>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let title = if token_usage_ratio == TokenUsageRatio::Exceeded {
|
||||
"Thread reached the token limit"
|
||||
} else {
|
||||
@@ -1260,7 +1273,7 @@ impl MessageEditor {
|
||||
messages: vec![request_message],
|
||||
tools: vec![],
|
||||
stop: vec![],
|
||||
temperature: AssistantSettings::temperature_for_model(&model.model, cx),
|
||||
temperature: None,
|
||||
};
|
||||
|
||||
Some(model.model.count_tokens(request, cx))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::{
|
||||
AgentProfile, AgentProfileId, AssistantSettings, GroupedAgentProfiles, builtin_profiles,
|
||||
use agent_settings::{
|
||||
AgentProfile, AgentProfileId, AgentSettings, GroupedAgentProfiles, builtin_profiles,
|
||||
};
|
||||
use fs::Fs;
|
||||
use gpui::{Action, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
|
||||
@@ -38,7 +38,7 @@ impl ProfileSelector {
|
||||
});
|
||||
|
||||
Self {
|
||||
profiles: GroupedAgentProfiles::from_settings(AssistantSettings::get_global(cx)),
|
||||
profiles: GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx)),
|
||||
fs,
|
||||
thread_store,
|
||||
menu_handle: PopoverMenuHandle::default(),
|
||||
@@ -58,7 +58,7 @@ impl ProfileSelector {
|
||||
}
|
||||
|
||||
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
|
||||
self.profiles = GroupedAgentProfiles::from_settings(AssistantSettings::get_global(cx));
|
||||
self.profiles = GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx));
|
||||
}
|
||||
|
||||
fn build_context_menu(
|
||||
@@ -67,7 +67,7 @@ impl ProfileSelector {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<ContextMenu> {
|
||||
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
for (profile_id, profile) in self.profiles.builtin.iter() {
|
||||
menu =
|
||||
menu.item(self.menu_entry_for_profile(profile_id.clone(), profile, settings));
|
||||
@@ -99,7 +99,7 @@ impl ProfileSelector {
|
||||
&self,
|
||||
profile_id: AgentProfileId,
|
||||
profile: &AgentProfile,
|
||||
settings: &AssistantSettings,
|
||||
settings: &AgentSettings,
|
||||
) -> ContextMenuEntry {
|
||||
let documentation = match profile.name.to_lowercase().as_str() {
|
||||
builtin_profiles::WRITE => Some("Get help to write anything."),
|
||||
@@ -124,7 +124,7 @@ impl ProfileSelector {
|
||||
let thread_store = self.thread_store.clone();
|
||||
let profile_id = profile_id.clone();
|
||||
move |_window, cx| {
|
||||
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
|
||||
update_settings_file::<AgentSettings>(fs.clone(), cx, {
|
||||
let profile_id = profile_id.clone();
|
||||
move |settings, _cx| {
|
||||
settings.set_profile(profile_id.clone());
|
||||
@@ -143,7 +143,7 @@ impl ProfileSelector {
|
||||
|
||||
impl Render for ProfileSelector {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
let profile_id = &settings.default_profile;
|
||||
let profile = settings.profiles.get(profile_id);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::inline_prompt_editor::{
|
||||
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::{MultiBuffer, actions::SelectAll};
|
||||
@@ -267,12 +266,6 @@ impl TerminalInlineAssistant {
|
||||
load_context(contexts, project, &assist.prompt_store, cx)
|
||||
})?;
|
||||
|
||||
let ConfiguredModel { model, .. } = LanguageModelRegistry::read_global(cx)
|
||||
.inline_assistant_model()
|
||||
.context("No inline assistant model")?;
|
||||
|
||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
||||
|
||||
Ok(cx.background_spawn(async move {
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
@@ -294,7 +287,7 @@ impl TerminalInlineAssistant {
|
||||
messages: vec![request_message],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
temperature: None,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_settings::{AssistantSettings, CompletionMode};
|
||||
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
@@ -35,10 +35,9 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use thiserror::Error;
|
||||
use ui::Window;
|
||||
use util::{ResultExt as _, TryFutureExt as _, post_inc};
|
||||
use uuid::Uuid;
|
||||
use zed_llm_client::CompletionRequestStatus;
|
||||
use zed_llm_client::{CompletionMode, CompletionRequestStatus};
|
||||
|
||||
use crate::ThreadStore;
|
||||
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
|
||||
@@ -313,6 +312,14 @@ pub enum TokenUsageRatio {
|
||||
Exceeded,
|
||||
}
|
||||
|
||||
fn default_completion_mode(cx: &App) -> CompletionMode {
|
||||
if cx.is_staff() {
|
||||
CompletionMode::Max
|
||||
} else {
|
||||
CompletionMode::Normal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum QueueState {
|
||||
Sending,
|
||||
@@ -329,7 +336,7 @@ pub struct Thread {
|
||||
detailed_summary_task: Task<Option<()>>,
|
||||
detailed_summary_tx: postage::watch::Sender<DetailedSummaryState>,
|
||||
detailed_summary_rx: postage::watch::Receiver<DetailedSummaryState>,
|
||||
completion_mode: assistant_settings::CompletionMode,
|
||||
completion_mode: CompletionMode,
|
||||
messages: Vec<Message>,
|
||||
next_message_id: MessageId,
|
||||
last_prompt_id: PromptId,
|
||||
@@ -388,7 +395,7 @@ impl Thread {
|
||||
detailed_summary_task: Task::ready(None),
|
||||
detailed_summary_tx,
|
||||
detailed_summary_rx,
|
||||
completion_mode: AssistantSettings::get_global(cx).preferred_completion_mode,
|
||||
completion_mode: default_completion_mode(cx),
|
||||
messages: Vec::new(),
|
||||
next_message_id: MessageId(0),
|
||||
last_prompt_id: PromptId::new(),
|
||||
@@ -431,7 +438,6 @@ impl Thread {
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
project_context: SharedProjectContext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let next_message_id = MessageId(
|
||||
@@ -441,13 +447,7 @@ impl Thread {
|
||||
.map(|message| message.id.0 + 1)
|
||||
.unwrap_or(0),
|
||||
);
|
||||
let tool_use = ToolUseState::from_serialized_messages(
|
||||
tools.clone(),
|
||||
&serialized.messages,
|
||||
project.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let tool_use = ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages);
|
||||
let (detailed_summary_tx, detailed_summary_rx) =
|
||||
postage::watch::channel_with(serialized.detailed_summary_state);
|
||||
|
||||
@@ -464,10 +464,6 @@ impl Thread {
|
||||
.or_else(|| registry.default_model())
|
||||
});
|
||||
|
||||
let completion_mode = serialized
|
||||
.completion_mode
|
||||
.unwrap_or_else(|| AssistantSettings::get_global(cx).preferred_completion_mode);
|
||||
|
||||
Self {
|
||||
id,
|
||||
updated_at: serialized.updated_at,
|
||||
@@ -476,7 +472,7 @@ impl Thread {
|
||||
detailed_summary_task: Task::ready(None),
|
||||
detailed_summary_tx,
|
||||
detailed_summary_rx,
|
||||
completion_mode,
|
||||
completion_mode: default_completion_mode(cx),
|
||||
messages: serialized
|
||||
.messages
|
||||
.into_iter()
|
||||
@@ -1072,7 +1068,6 @@ impl Thread {
|
||||
tool_use_id: tool_result.tool_use_id.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: tool_result.content.clone(),
|
||||
output: tool_result.output.clone(),
|
||||
})
|
||||
.collect(),
|
||||
context: message.loaded_context.text.clone(),
|
||||
@@ -1100,7 +1095,6 @@ impl Thread {
|
||||
provider: model.provider.id().0.to_string(),
|
||||
model: model.model.id().0.to_string(),
|
||||
}),
|
||||
completion_mode: Some(this.completion_mode),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1154,7 +1148,7 @@ impl Thread {
|
||||
messages: vec![],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: AssistantSettings::temperature_for_model(&model, cx),
|
||||
temperature: None,
|
||||
};
|
||||
|
||||
let available_tools = self.available_tools(cx, model.clone());
|
||||
@@ -1252,20 +1246,15 @@ impl Thread {
|
||||
|
||||
request.tools = available_tools;
|
||||
request.mode = if model.supports_max_mode() {
|
||||
Some(self.completion_mode.into())
|
||||
Some(self.completion_mode)
|
||||
} else {
|
||||
Some(CompletionMode::Normal.into())
|
||||
Some(CompletionMode::Normal)
|
||||
};
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
fn to_summarize_request(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
added_user_message: String,
|
||||
cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
fn to_summarize_request(&self, added_user_message: String) -> LanguageModelRequest {
|
||||
let mut request = LanguageModelRequest {
|
||||
thread_id: None,
|
||||
prompt_id: None,
|
||||
@@ -1273,7 +1262,7 @@ impl Thread {
|
||||
messages: vec![],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: AssistantSettings::temperature_for_model(model, cx),
|
||||
temperature: None,
|
||||
};
|
||||
|
||||
for message in &self.messages {
|
||||
@@ -1710,7 +1699,7 @@ impl Thread {
|
||||
If the conversation is about a specific subject, include it in the title. \
|
||||
Be descriptive. DO NOT speak in the first person.";
|
||||
|
||||
let request = self.to_summarize_request(&model.model, added_user_message.into(), cx);
|
||||
let request = self.to_summarize_request(added_user_message.into());
|
||||
|
||||
self.pending_summary = cx.spawn(async move |this, cx| {
|
||||
async move {
|
||||
@@ -1796,7 +1785,7 @@ impl Thread {
|
||||
4. Any action items or next steps if any\n\
|
||||
Format it in Markdown with headings and bullet points.";
|
||||
|
||||
let request = self.to_summarize_request(&model, added_user_message.into(), cx);
|
||||
let request = self.to_summarize_request(added_user_message.into());
|
||||
|
||||
*self.detailed_summary_tx.borrow_mut() = DetailedSummaryState::Generating {
|
||||
message_id: last_message_id,
|
||||
@@ -1901,7 +1890,7 @@ impl Thread {
|
||||
for tool_use in pending_tool_uses.iter() {
|
||||
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
|
||||
if tool.needs_confirmation(&tool_use.input, cx)
|
||||
&& !AssistantSettings::get_global(cx).always_allow_tool_actions
|
||||
&& !AgentSettings::get_global(cx).always_allow_tool_actions
|
||||
{
|
||||
self.tool_use.confirm_tool_use(
|
||||
tool_use.id.clone(),
|
||||
@@ -2669,7 +2658,7 @@ struct PendingCompletion {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
|
||||
use assistant_settings::{AssistantSettings, LanguageModelParameters};
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_tool::ToolRegistry;
|
||||
use editor::EditorSettings;
|
||||
use gpui::TestAppContext;
|
||||
@@ -3080,107 +3069,13 @@ fn main() {{
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_temperature_setting(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
|
||||
let project = create_test_project(
|
||||
cx,
|
||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (_workspace, _thread_store, thread, _context_store, model) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Both model and provider
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: Some(model.provider_id().0.to_string().into()),
|
||||
model: Some(model.id().0.clone()),
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, Some(0.66));
|
||||
|
||||
// Only model
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: None,
|
||||
model: Some(model.id().0.clone()),
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, Some(0.66));
|
||||
|
||||
// Only provider
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: Some(model.provider_id().0.to_string().into()),
|
||||
model: None,
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, Some(0.66));
|
||||
|
||||
// Same model name, different provider
|
||||
cx.update(|cx| {
|
||||
AssistantSettings::override_global(
|
||||
AssistantSettings {
|
||||
model_parameters: vec![LanguageModelParameters {
|
||||
provider: Some("anthropic".into()),
|
||||
model: Some(model.id().0.clone()),
|
||||
temperature: Some(0.66),
|
||||
}],
|
||||
..AssistantSettings::get_global(cx).clone()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let request = thread.update(cx, |thread, cx| {
|
||||
thread.to_completion_request(model.clone(), cx)
|
||||
});
|
||||
assert_eq!(request.temperature, None);
|
||||
}
|
||||
|
||||
fn init_test_settings(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
prompt_store::init(cx);
|
||||
thread_store::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_context_editor::SavedContextMetadata;
|
||||
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
|
||||
use chrono::{Datelike as _, NaiveDate, TimeDelta, Utc};
|
||||
use editor::{Editor, EditorEvent};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
@@ -140,14 +140,10 @@ impl ThreadHistory {
|
||||
|
||||
let bg_task = cx.background_spawn(async move {
|
||||
let mut bucket = None;
|
||||
let today = Local::now().naive_local().date();
|
||||
let today = Utc::now().naive_local().date();
|
||||
|
||||
for (index, entry) in all_entries.iter().enumerate() {
|
||||
let entry_date = entry
|
||||
.updated_at()
|
||||
.with_timezone(&Local)
|
||||
.naive_local()
|
||||
.date();
|
||||
let entry_date = entry.updated_at().naive_local().date();
|
||||
let entry_bucket = TimeBucket::from_dates(today, entry_date);
|
||||
|
||||
if Some(entry_bucket) != bucket {
|
||||
@@ -256,14 +252,6 @@ impl ThreadHistory {
|
||||
}
|
||||
}
|
||||
|
||||
fn list_item_count(&self) -> usize {
|
||||
match &self.search_state {
|
||||
SearchState::Empty => self.separated_items.len(),
|
||||
SearchState::Searching { .. } => 0,
|
||||
SearchState::Searched { matches, .. } => matches.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn search_produced_no_matches(&self) -> bool {
|
||||
match &self.search_state {
|
||||
SearchState::Empty => false,
|
||||
@@ -587,7 +575,7 @@ impl Render for ThreadHistory {
|
||||
uniform_list(
|
||||
cx.entity().clone(),
|
||||
"thread-history",
|
||||
self.list_item_count(),
|
||||
self.matched_count(),
|
||||
Self::list_items,
|
||||
)
|
||||
.p_1()
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings, CompletionMode};
|
||||
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
@@ -28,7 +28,6 @@ use prompt_store::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use ui::Window;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::context_server_tool::ContextServerTool;
|
||||
@@ -389,20 +388,18 @@ impl ThreadStore {
|
||||
pub fn open_thread(
|
||||
&self,
|
||||
id: &ThreadId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Thread>>> {
|
||||
let id = id.clone();
|
||||
let database_future = ThreadsDatabase::global_future(cx);
|
||||
let this = cx.weak_entity();
|
||||
window.spawn(cx, async move |cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
||||
let thread = database
|
||||
.try_find_thread(id.clone())
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;
|
||||
|
||||
let thread = this.update_in(cx, |this, window, cx| {
|
||||
let thread = this.update(cx, |this, cx| {
|
||||
cx.new(|cx| {
|
||||
Thread::deserialize(
|
||||
id.clone(),
|
||||
@@ -411,7 +408,6 @@ impl ThreadStore {
|
||||
this.tools.clone(),
|
||||
this.prompt_builder.clone(),
|
||||
this.project_context.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -466,15 +462,15 @@ impl ThreadStore {
|
||||
}
|
||||
|
||||
fn load_default_profile(&self, cx: &mut Context<Self>) {
|
||||
let assistant_settings = AssistantSettings::get_global(cx);
|
||||
let agent_settings = AgentSettings::get_global(cx);
|
||||
|
||||
self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);
|
||||
self.load_profile_by_id(agent_settings.default_profile.clone(), cx);
|
||||
}
|
||||
|
||||
pub fn load_profile_by_id(&self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
|
||||
let assistant_settings = AssistantSettings::get_global(cx);
|
||||
let agent_settings = AgentSettings::get_global(cx);
|
||||
|
||||
if let Some(profile) = assistant_settings.profiles.get(&profile_id) {
|
||||
if let Some(profile) = agent_settings.profiles.get(&profile_id) {
|
||||
self.load_profile(profile.clone(), cx);
|
||||
}
|
||||
}
|
||||
@@ -655,8 +651,6 @@ pub struct SerializedThread {
|
||||
pub exceeded_window_error: Option<ExceededWindowError>,
|
||||
#[serde(default)]
|
||||
pub model: Option<SerializedLanguageModel>,
|
||||
#[serde(default)]
|
||||
pub completion_mode: Option<CompletionMode>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -776,7 +770,6 @@ pub struct SerializedToolResult {
|
||||
pub tool_use_id: LanguageModelToolUseId,
|
||||
pub is_error: bool,
|
||||
pub content: Arc<str>,
|
||||
pub output: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -801,7 +794,6 @@ impl LegacySerializedThread {
|
||||
detailed_summary_state: DetailedSummaryState::default(),
|
||||
exceeded_window_error: None,
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_tool::{AnyToolCard, Tool, ToolResultOutput, ToolUseStatus, ToolWorkingSet};
|
||||
use assistant_tool::{AnyToolCard, Tool, ToolUseStatus, ToolWorkingSet};
|
||||
use collections::HashMap;
|
||||
use futures::FutureExt as _;
|
||||
use futures::future::Shared;
|
||||
@@ -10,8 +10,7 @@ use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelRequestMessage, LanguageModelToolResult,
|
||||
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
|
||||
};
|
||||
use project::Project;
|
||||
use ui::{IconName, Window};
|
||||
use ui::IconName;
|
||||
use util::truncate_lines_to_byte_limit;
|
||||
|
||||
use crate::thread::{MessageId, PromptId, ThreadId};
|
||||
@@ -55,9 +54,6 @@ impl ToolUseState {
|
||||
pub fn from_serialized_messages(
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
messages: &[SerializedMessage],
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let mut this = Self::new(tools);
|
||||
let mut tool_names_by_id = HashMap::default();
|
||||
@@ -97,23 +93,12 @@ impl ToolUseState {
|
||||
this.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_use_id,
|
||||
tool_name: tool_use.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: tool_result.content.clone(),
|
||||
output: tool_result.output.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(tool) = this.tools.read(cx).tool(tool_use, cx) {
|
||||
if let Some(output) = tool_result.output.clone() {
|
||||
if let Some(card) =
|
||||
tool.deserialize_card(output, project.clone(), window, cx)
|
||||
{
|
||||
this.tool_result_cards.insert(tool_use_id, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +124,6 @@ impl ToolUseState {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name: tool_use.name.clone(),
|
||||
content,
|
||||
output: None,
|
||||
is_error: true,
|
||||
},
|
||||
);
|
||||
@@ -375,7 +359,7 @@ impl ToolUseState {
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
output: Result<ToolResultOutput>,
|
||||
output: Result<String>,
|
||||
configured_model: Option<&ConfiguredModel>,
|
||||
) -> Option<PendingToolUse> {
|
||||
let metadata = self.tool_use_metadata_by_id.remove(&tool_use_id);
|
||||
@@ -395,8 +379,7 @@ impl ToolUseState {
|
||||
);
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
let tool_result = output.content;
|
||||
Ok(tool_result) => {
|
||||
const BYTES_PER_TOKEN_ESTIMATE: usize = 3;
|
||||
|
||||
// Protect from clearly large output
|
||||
@@ -423,7 +406,6 @@ impl ToolUseState {
|
||||
tool_name,
|
||||
content: tool_result.into(),
|
||||
is_error: false,
|
||||
output: output.output,
|
||||
},
|
||||
);
|
||||
self.pending_tool_uses_by_id.remove(&tool_use_id)
|
||||
@@ -436,7 +418,6 @@ impl ToolUseState {
|
||||
tool_name,
|
||||
content: err.to_string().into(),
|
||||
is_error: true,
|
||||
output: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -509,7 +490,6 @@ impl ToolUseState {
|
||||
} else {
|
||||
tool_result.content.clone()
|
||||
},
|
||||
output: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ mod agent_notification;
|
||||
mod animated_label;
|
||||
mod context_pill;
|
||||
mod max_mode_tooltip;
|
||||
mod onboarding_modal;
|
||||
pub mod preview;
|
||||
mod upsell;
|
||||
|
||||
@@ -10,4 +9,3 @@ pub use agent_notification::*;
|
||||
pub use animated_label::*;
|
||||
pub use context_pill::*;
|
||||
pub use max_mode_tooltip::*;
|
||||
pub use onboarding_modal::*;
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
use gpui::{
|
||||
ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render,
|
||||
};
|
||||
use ui::{TintColor, Vector, VectorName, prelude::*};
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::assistant_panel::AssistantPanel;
|
||||
|
||||
macro_rules! agent_onboarding_event {
|
||||
($name:expr) => {
|
||||
telemetry::event!($name, source = "Agent Onboarding");
|
||||
};
|
||||
($name:expr, $($key:ident $(= $value:expr)?),+ $(,)?) => {
|
||||
telemetry::event!($name, source = "Agent Onboarding", $($key $(= $value)?),+);
|
||||
};
|
||||
}
|
||||
|
||||
pub struct AgentOnboardingModal {
|
||||
focus_handle: FocusHandle,
|
||||
workspace: Entity<Workspace>,
|
||||
}
|
||||
|
||||
impl AgentOnboardingModal {
|
||||
pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
|
||||
let workspace_entity = cx.entity();
|
||||
workspace.toggle_modal(window, cx, |_window, cx| Self {
|
||||
workspace: workspace_entity,
|
||||
focus_handle: cx.focus_handle(),
|
||||
});
|
||||
}
|
||||
|
||||
fn open_panel(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.workspace.update(cx, |workspace, cx| {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
});
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
|
||||
agent_onboarding_event!("Open Panel Clicked");
|
||||
}
|
||||
|
||||
fn view_blog(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.open_url("http://zed.dev/blog/fastest-ai-code-editor");
|
||||
cx.notify();
|
||||
|
||||
agent_onboarding_event!("Blog Link Clicked");
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for AgentOnboardingModal {}
|
||||
|
||||
impl Focusable for AgentOnboardingModal {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for AgentOnboardingModal {}
|
||||
|
||||
impl Render for AgentOnboardingModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let window_height = window.viewport_size().height;
|
||||
let max_height = window_height - px(200.);
|
||||
|
||||
let base = v_flex()
|
||||
.id("agent-onboarding")
|
||||
.key_context("AgentOnboardingModal")
|
||||
.relative()
|
||||
.w(px(450.))
|
||||
.h_full()
|
||||
.max_h(max_height)
|
||||
.p_4()
|
||||
.gap_2()
|
||||
.elevation_3(cx)
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.overflow_hidden()
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(|_, _: &menu::Cancel, _window, cx| {
|
||||
agent_onboarding_event!("Canceled", trigger = "Action");
|
||||
cx.emit(DismissEvent);
|
||||
}))
|
||||
.on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, _cx| {
|
||||
this.focus_handle.focus(window);
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right(px(-1.0))
|
||||
.w(px(441.))
|
||||
.h(px(167.))
|
||||
.child(
|
||||
Vector::new(VectorName::Grid, rems_from_px(441.), rems_from_px(167.))
|
||||
.color(ui::Color::Custom(cx.theme().colors().text.alpha(0.1))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top(px(-8.0))
|
||||
.right_0()
|
||||
.w(px(400.))
|
||||
.h(px(92.))
|
||||
.child(
|
||||
Vector::new(VectorName::AiGrid, rems_from_px(400.), rems_from_px(92.))
|
||||
.color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new("Introducing")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Headline::new("Agentic Editing in Zed").size(HeadlineSize::Large)),
|
||||
)
|
||||
.child(h_flex().absolute().top_2().right_2().child(
|
||||
IconButton::new("cancel", IconName::X).on_click(cx.listener(
|
||||
|_, _: &ClickEvent, _window, cx| {
|
||||
agent_onboarding_event!("Cancelled", trigger = "X click");
|
||||
cx.emit(DismissEvent);
|
||||
},
|
||||
)),
|
||||
));
|
||||
|
||||
let open_panel_button = Button::new("open-panel", "Get Started with the Agent Panel")
|
||||
.icon_size(IconSize::Indicator)
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.full_width()
|
||||
.on_click(cx.listener(Self::open_panel));
|
||||
|
||||
let blog_post_button = Button::new("view-blog", "Check out the Blog Post")
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::Indicator)
|
||||
.icon_color(Color::Muted)
|
||||
.full_width()
|
||||
.on_click(cx.listener(Self::view_blog));
|
||||
|
||||
let copy = "Zed now natively supports agentic editing, enabling fluid collaboration between humans and AI.";
|
||||
|
||||
base.child(Label::new(copy).color(Color::Muted)).child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.mt_2()
|
||||
.gap_2()
|
||||
.child(open_panel_button)
|
||||
.child(blog_post_button),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -44,13 +44,13 @@ impl RenderOnce for UsageCallout {
|
||||
"Upgrade to continue, wait for the next reset, or switch to API key."
|
||||
.to_string(),
|
||||
"Upgrade",
|
||||
zed_urls::account_upgrade_url(cx),
|
||||
zed_urls::account_url(cx),
|
||||
),
|
||||
Plan::ZedProTrial => (
|
||||
"Out of trial prompts",
|
||||
"Upgrade to Zed Pro to continue, or switch to API key.".to_string(),
|
||||
"Upgrade",
|
||||
zed_urls::account_upgrade_url(cx),
|
||||
zed_urls::account_url(cx),
|
||||
),
|
||||
Plan::ZedPro => (
|
||||
"Out of included prompts",
|
||||
@@ -67,7 +67,7 @@ impl RenderOnce for UsageCallout {
|
||||
"{remaining} remaining - Upgrade to increase limit, or switch providers",
|
||||
),
|
||||
"Upgrade",
|
||||
zed_urls::account_upgrade_url(cx),
|
||||
zed_urls::account_url(cx),
|
||||
),
|
||||
Plan::ZedProTrial => (
|
||||
"Reaching trial limit soon",
|
||||
@@ -75,7 +75,7 @@ impl RenderOnce for UsageCallout {
|
||||
"{remaining} remaining - Upgrade to increase limit, or switch providers",
|
||||
),
|
||||
"Upgrade",
|
||||
zed_urls::account_upgrade_url(cx),
|
||||
zed_urls::account_url(cx),
|
||||
),
|
||||
_ => return div().into_any_element(),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "assistant_settings"
|
||||
name = "agent_settings"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
@@ -9,12 +9,12 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant_settings.rs"
|
||||
path = "src/agent_settings.rs"
|
||||
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
feature_flags.workspace = true
|
||||
gpui.workspace = true
|
||||
indexmap.workspace = true
|
||||
language_model.workspace = true
|
||||
@@ -27,7 +27,6 @@ schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
fs.workspace = true
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::IndexMap;
|
||||
use gpui::SharedString;
|
||||
use indexmap::IndexMap;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -24,7 +24,7 @@ pub struct GroupedAgentProfiles {
|
||||
}
|
||||
|
||||
impl GroupedAgentProfiles {
|
||||
pub fn from_settings(settings: &crate::AssistantSettings) -> Self {
|
||||
pub fn from_settings(settings: &crate::AgentSettings) -> Self {
|
||||
let mut builtin = IndexMap::default();
|
||||
let mut custom = IndexMap::default();
|
||||
|
||||
@@ -5,9 +5,10 @@ use std::sync::Arc;
|
||||
use ::open_ai::Model as OpenAiModel;
|
||||
use anthropic::Model as AnthropicModel;
|
||||
use anyhow::{Result, bail};
|
||||
use collections::IndexMap;
|
||||
use deepseek::Model as DeepseekModel;
|
||||
use gpui::{App, Pixels, SharedString};
|
||||
use feature_flags::{AgentStreamEditsFeatureFlag, Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
use gpui::{App, Pixels};
|
||||
use indexmap::IndexMap;
|
||||
use language_model::{CloudModel, LanguageModel};
|
||||
use lmstudio::Model as LmStudioModel;
|
||||
use ollama::Model as OllamaModel;
|
||||
@@ -17,13 +18,9 @@ use settings::{Settings, SettingsSources};
|
||||
|
||||
pub use crate::agent_profile::*;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
AssistantSettings::register(cx);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AssistantDockPosition {
|
||||
pub enum AgentDockPosition {
|
||||
Left,
|
||||
#[default]
|
||||
Right,
|
||||
@@ -73,10 +70,10 @@ pub enum AssistantProviderContentV1 {
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct AssistantSettings {
|
||||
pub struct AgentSettings {
|
||||
pub enabled: bool,
|
||||
pub button: bool,
|
||||
pub dock: AssistantDockPosition,
|
||||
pub dock: AgentDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_model: LanguageModelSelection,
|
||||
@@ -92,126 +89,84 @@ pub struct AssistantSettings {
|
||||
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
|
||||
pub stream_edits: bool,
|
||||
pub single_file_review: bool,
|
||||
pub model_parameters: Vec<LanguageModelParameters>,
|
||||
pub preferred_completion_mode: CompletionMode,
|
||||
}
|
||||
|
||||
impl AssistantSettings {
|
||||
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
||||
let settings = Self::get_global(cx);
|
||||
settings
|
||||
.model_parameters
|
||||
.iter()
|
||||
.rfind(|setting| setting.matches(model))
|
||||
.and_then(|m| m.temperature)
|
||||
impl AgentSettings {
|
||||
pub fn stream_edits(&self, cx: &App) -> bool {
|
||||
cx.has_flag::<AgentStreamEditsFeatureFlag>() || self.stream_edits
|
||||
}
|
||||
|
||||
pub fn stream_edits(&self, _cx: &App) -> bool {
|
||||
// TODO: Remove the `stream_edits` setting.
|
||||
true
|
||||
}
|
||||
pub fn are_live_diffs_enabled(&self, cx: &App) -> bool {
|
||||
if cx.has_flag::<Assistant2FeatureFlag>() {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn are_live_diffs_enabled(&self, _cx: &App) -> bool {
|
||||
false
|
||||
cx.is_staff() || self.enable_experimental_live_diffs
|
||||
}
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
self.inline_assistant_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
self.inline_assistant_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
|
||||
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
||||
self.commit_message_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
self.commit_message_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
|
||||
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
||||
self.thread_summary_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct LanguageModelParameters {
|
||||
pub provider: Option<LanguageModelProviderSetting>,
|
||||
pub model: Option<SharedString>,
|
||||
pub temperature: Option<f32>,
|
||||
}
|
||||
|
||||
impl LanguageModelParameters {
|
||||
pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
|
||||
if let Some(provider) = &self.provider {
|
||||
if provider.0 != model.provider_id().0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(setting_model) = &self.model {
|
||||
if *setting_model != model.id().0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
self.thread_summary_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
}
|
||||
|
||||
/// Assistant panel settings
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
pub struct AssistantSettingsContent {
|
||||
pub struct AgentSettingsContent {
|
||||
#[serde(flatten)]
|
||||
pub inner: Option<AssistantSettingsContentInner>,
|
||||
pub inner: Option<AgentSettingsContentInner>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum AssistantSettingsContentInner {
|
||||
Versioned(Box<VersionedAssistantSettingsContent>),
|
||||
pub enum AgentSettingsContentInner {
|
||||
Versioned(Box<VersionedAgentSettingsContent>),
|
||||
Legacy(LegacyAssistantSettingsContent),
|
||||
}
|
||||
|
||||
impl AssistantSettingsContentInner {
|
||||
fn for_v2(content: AssistantSettingsContentV2) -> Self {
|
||||
AssistantSettingsContentInner::Versioned(Box::new(VersionedAssistantSettingsContent::V2(
|
||||
content,
|
||||
)))
|
||||
impl AgentSettingsContentInner {
|
||||
fn for_v2(content: AgentSettingsContentV2) -> Self {
|
||||
AgentSettingsContentInner::Versioned(Box::new(VersionedAgentSettingsContent::V2(content)))
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for AssistantSettingsContent {
|
||||
impl JsonSchema for AgentSettingsContent {
|
||||
fn schema_name() -> String {
|
||||
VersionedAssistantSettingsContent::schema_name()
|
||||
VersionedAgentSettingsContent::schema_name()
|
||||
}
|
||||
|
||||
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
VersionedAssistantSettingsContent::json_schema(r#gen)
|
||||
VersionedAgentSettingsContent::json_schema(r#gen)
|
||||
}
|
||||
|
||||
fn is_referenceable() -> bool {
|
||||
VersionedAssistantSettingsContent::is_referenceable()
|
||||
VersionedAgentSettingsContent::is_referenceable()
|
||||
}
|
||||
}
|
||||
|
||||
impl AssistantSettingsContent {
|
||||
impl AgentSettingsContent {
|
||||
pub fn is_version_outdated(&self) -> bool {
|
||||
match &self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(_) => true,
|
||||
VersionedAssistantSettingsContent::V2(_) => false,
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(_) => true,
|
||||
VersionedAgentSettingsContent::V2(_) => false,
|
||||
},
|
||||
Some(AssistantSettingsContentInner::Legacy(_)) => true,
|
||||
Some(AgentSettingsContentInner::Legacy(_)) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> AssistantSettingsContentV2 {
|
||||
fn upgrade(&self) -> AgentSettingsContentV2 {
|
||||
match &self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(ref settings) => AssistantSettingsContentV2 {
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(ref settings) => AgentSettingsContentV2 {
|
||||
enabled: settings.enabled,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
@@ -223,37 +178,37 @@ impl AssistantSettingsContent {
|
||||
.and_then(|provider| match provider {
|
||||
AssistantProviderContentV1::ZedDotDev { default_model } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "zed.dev".into(),
|
||||
provider: "zed.dev".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::OpenAi { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "openai".into(),
|
||||
provider: "openai".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::Anthropic { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "anthropic".into(),
|
||||
provider: "anthropic".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::Ollama { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "ollama".into(),
|
||||
provider: "ollama".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::LmStudio { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "lmstudio".into(),
|
||||
provider: "lmstudio".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::DeepSeek { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "deepseek".into(),
|
||||
provider: "deepseek".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
@@ -269,19 +224,17 @@ impl AssistantSettingsContent {
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
},
|
||||
VersionedAssistantSettingsContent::V2(ref settings) => settings.clone(),
|
||||
VersionedAgentSettingsContent::V2(ref settings) => settings.clone(),
|
||||
},
|
||||
Some(AssistantSettingsContentInner::Legacy(settings)) => AssistantSettingsContentV2 {
|
||||
Some(AgentSettingsContentInner::Legacy(settings)) => AgentSettingsContentV2 {
|
||||
enabled: None,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
default_width: settings.default_width,
|
||||
default_height: settings.default_height,
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "openai".into(),
|
||||
provider: "openai".to_string(),
|
||||
model: settings
|
||||
.default_open_ai_model
|
||||
.clone()
|
||||
@@ -300,33 +253,29 @@ impl AssistantSettingsContent {
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
},
|
||||
None => AssistantSettingsContentV2::default(),
|
||||
None => AgentSettingsContentV2::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dock(&mut self, dock: AssistantDockPosition) {
|
||||
pub fn set_dock(&mut self, dock: AgentDockPosition) {
|
||||
match &mut self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(ref mut settings) => {
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(ref mut settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
VersionedAssistantSettingsContent::V2(ref mut settings) => {
|
||||
VersionedAgentSettingsContent::V2(ref mut settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
},
|
||||
Some(AssistantSettingsContentInner::Legacy(settings)) => {
|
||||
Some(AgentSettingsContentInner::Legacy(settings)) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
None => {
|
||||
self.inner = Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
dock: Some(dock),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
dock: Some(dock),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,115 +285,102 @@ impl AssistantSettingsContent {
|
||||
let provider = language_model.provider_id().0.to_string();
|
||||
|
||||
match &mut self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(ref mut settings) => {
|
||||
match provider.as_ref() {
|
||||
"zed.dev" => {
|
||||
log::warn!("attempted to set zed.dev model on outdated settings");
|
||||
}
|
||||
"anthropic" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Anthropic {
|
||||
default_model: AnthropicModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"ollama" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Ollama { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Ollama {
|
||||
default_model: Some(ollama::Model::new(
|
||||
&model,
|
||||
None,
|
||||
None,
|
||||
language_model.supports_tools(),
|
||||
)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"lmstudio" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::LmStudio { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::LmStudio {
|
||||
default_model: Some(lmstudio::Model::new(&model, None, None)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"openai" => {
|
||||
let (api_url, available_models) = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
available_models,
|
||||
..
|
||||
}) => (api_url.clone(), available_models.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::OpenAi {
|
||||
default_model: OpenAiModel::from_id(&model).ok(),
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(ref mut settings) => match provider.as_ref() {
|
||||
"zed.dev" => {
|
||||
log::warn!("attempted to set zed.dev model on outdated settings");
|
||||
}
|
||||
"anthropic" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Anthropic {
|
||||
default_model: AnthropicModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"ollama" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Ollama { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Ollama {
|
||||
default_model: Some(ollama::Model::new(
|
||||
&model,
|
||||
None,
|
||||
None,
|
||||
language_model.supports_tools(),
|
||||
)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"lmstudio" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::LmStudio { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::LmStudio {
|
||||
default_model: Some(lmstudio::Model::new(&model, None, None)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"openai" => {
|
||||
let (api_url, available_models) = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
available_models,
|
||||
});
|
||||
}
|
||||
"deepseek" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::DeepSeek { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::DeepSeek {
|
||||
default_model: DeepseekModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
..
|
||||
}) => (api_url.clone(), available_models.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::OpenAi {
|
||||
default_model: OpenAiModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
available_models,
|
||||
});
|
||||
}
|
||||
}
|
||||
VersionedAssistantSettingsContent::V2(ref mut settings) => {
|
||||
settings.default_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
"deepseek" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::DeepSeek { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::DeepSeek {
|
||||
default_model: DeepseekModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
VersionedAgentSettingsContent::V2(ref mut settings) => {
|
||||
settings.default_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
},
|
||||
Some(AssistantSettingsContentInner::Legacy(settings)) => {
|
||||
Some(AgentSettingsContentInner::Legacy(settings)) => {
|
||||
if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
|
||||
settings.default_open_ai_model = Some(model);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.inner = Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection { provider, model }),
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.inline_assistant_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
setting.inline_assistant_model = Some(LanguageModelSelection { provider, model });
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
@@ -452,10 +388,7 @@ impl AssistantSettingsContent {
|
||||
|
||||
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.commit_message_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
setting.commit_message_model = Some(LanguageModelSelection { provider, model });
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
@@ -463,15 +396,15 @@ impl AssistantSettingsContent {
|
||||
|
||||
pub fn v2_setting(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut AssistantSettingsContentV2) -> anyhow::Result<()>,
|
||||
f: impl FnOnce(&mut AgentSettingsContentV2) -> anyhow::Result<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
match self.inner.get_or_insert_with(|| {
|
||||
AssistantSettingsContentInner::for_v2(AssistantSettingsContentV2 {
|
||||
AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
..Default::default()
|
||||
})
|
||||
}) {
|
||||
AssistantSettingsContentInner::Versioned(boxed) => {
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
AgentSettingsContentInner::Versioned(boxed) => {
|
||||
if let VersionedAgentSettingsContent::V2(ref mut settings) = **boxed {
|
||||
f(settings)
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -483,10 +416,7 @@ impl AssistantSettingsContent {
|
||||
|
||||
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.thread_summary_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
setting.thread_summary_model = Some(LanguageModelSelection { provider, model });
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
@@ -555,16 +485,16 @@ impl AssistantSettingsContent {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "version")]
|
||||
pub enum VersionedAssistantSettingsContent {
|
||||
pub enum VersionedAgentSettingsContent {
|
||||
#[serde(rename = "1")]
|
||||
V1(AssistantSettingsContentV1),
|
||||
#[serde(rename = "2")]
|
||||
V2(AssistantSettingsContentV2),
|
||||
V2(AgentSettingsContentV2),
|
||||
}
|
||||
|
||||
impl Default for VersionedAssistantSettingsContent {
|
||||
impl Default for VersionedAgentSettingsContent {
|
||||
fn default() -> Self {
|
||||
Self::V2(AssistantSettingsContentV2 {
|
||||
Self::V2(AgentSettingsContentV2 {
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
@@ -582,37 +512,35 @@ impl Default for VersionedAssistantSettingsContent {
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||
pub struct AssistantSettingsContentV2 {
|
||||
/// Whether the Assistant is enabled.
|
||||
pub struct AgentSettingsContentV2 {
|
||||
/// Whether the agent is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
enabled: Option<bool>,
|
||||
/// Whether to show the assistant panel button in the status bar.
|
||||
/// Whether to show the agent panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
button: Option<bool>,
|
||||
/// Where to dock the assistant.
|
||||
/// Where to dock the agent.
|
||||
///
|
||||
/// Default: right
|
||||
dock: Option<AssistantDockPosition>,
|
||||
/// Default width in pixels when the assistant is docked to the left or right.
|
||||
dock: Option<AgentDockPosition>,
|
||||
/// Default width in pixels when the agent is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
default_width: Option<f32>,
|
||||
/// Default height in pixels when the assistant is docked to the bottom.
|
||||
/// Default height in pixels when the agent is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
default_height: Option<f32>,
|
||||
/// The default model to use when creating new chats and for other features when a specific model is not specified.
|
||||
default_model: Option<LanguageModelSelection>,
|
||||
/// Model to use for the inline assistant. Defaults to default_model when not specified.
|
||||
/// Model to use for the inline agent. Defaults to default_model when not specified.
|
||||
inline_assistant_model: Option<LanguageModelSelection>,
|
||||
/// Model to use for generating git commit messages. Defaults to default_model when not specified.
|
||||
commit_message_model: Option<LanguageModelSelection>,
|
||||
@@ -620,7 +548,7 @@ pub struct AssistantSettingsContentV2 {
|
||||
thread_summary_model: Option<LanguageModelSelection>,
|
||||
/// Additional models with which to generate alternatives when performing inline assists.
|
||||
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
||||
/// Enable experimental live diffs in the assistant panel.
|
||||
/// Enable experimental live diffs in the agent panel.
|
||||
///
|
||||
/// Default: false
|
||||
enable_experimental_live_diffs: Option<bool>,
|
||||
@@ -647,88 +575,37 @@ pub struct AssistantSettingsContentV2 {
|
||||
///
|
||||
/// Default: true
|
||||
single_file_review: Option<bool>,
|
||||
/// Additional parameters for language model requests. When making a request
|
||||
/// to a model, parameters will be taken from the last entry in this list
|
||||
/// that matches the model's provider and name. In each entry, both provider
|
||||
/// and model are optional, so that you can specify parameters for either
|
||||
/// one.
|
||||
///
|
||||
/// Default: []
|
||||
#[serde(default)]
|
||||
model_parameters: Vec<LanguageModelParameters>,
|
||||
|
||||
/// What completion mode to enable for new threads
|
||||
///
|
||||
/// Default: normal
|
||||
preferred_completion_mode: Option<CompletionMode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CompletionMode {
|
||||
#[default]
|
||||
Normal,
|
||||
Max,
|
||||
}
|
||||
|
||||
impl From<CompletionMode> for zed_llm_client::CompletionMode {
|
||||
fn from(value: CompletionMode) -> Self {
|
||||
match value {
|
||||
CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
|
||||
CompletionMode::Max => zed_llm_client::CompletionMode::Max,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct LanguageModelSelection {
|
||||
pub provider: LanguageModelProviderSetting,
|
||||
#[schemars(schema_with = "providers_schema")]
|
||||
pub provider: String,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct LanguageModelProviderSetting(pub String);
|
||||
|
||||
impl JsonSchema for LanguageModelProviderSetting {
|
||||
fn schema_name() -> String {
|
||||
"LanguageModelProviderSetting".into()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
"copilot_chat".into(),
|
||||
"deepseek".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for LanguageModelProviderSetting {
|
||||
fn from(provider: String) -> Self {
|
||||
Self(provider)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for LanguageModelProviderSetting {
|
||||
fn from(provider: &str) -> Self {
|
||||
Self(provider.to_string())
|
||||
fn providers_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
"copilot_chat".into(),
|
||||
"deepseek".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
impl Default for LanguageModelSelection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider: LanguageModelProviderSetting("openai".to_string()),
|
||||
provider: "openai".to_string(),
|
||||
model: "gpt-4".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -763,7 +640,7 @@ pub struct AssistantSettingsContentV1 {
|
||||
/// Where to dock the assistant.
|
||||
///
|
||||
/// Default: right
|
||||
dock: Option<AssistantDockPosition>,
|
||||
dock: Option<AgentDockPosition>,
|
||||
/// Default width in pixels when the assistant is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
@@ -788,7 +665,7 @@ pub struct LegacyAssistantSettingsContent {
|
||||
/// Where to dock the assistant.
|
||||
///
|
||||
/// Default: right
|
||||
pub dock: Option<AssistantDockPosition>,
|
||||
pub dock: Option<AgentDockPosition>,
|
||||
/// Default width in pixels when the assistant is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
@@ -807,20 +684,20 @@ pub struct LegacyAssistantSettingsContent {
|
||||
pub openai_api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Settings for AssistantSettings {
|
||||
impl Settings for AgentSettings {
|
||||
const KEY: Option<&'static str> = Some("agent");
|
||||
|
||||
const FALLBACK_KEY: Option<&'static str> = Some("assistant");
|
||||
|
||||
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
|
||||
|
||||
type FileContent = AssistantSettingsContent;
|
||||
type FileContent = AgentSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut settings = AssistantSettings::default();
|
||||
let mut settings = AgentSettings::default();
|
||||
|
||||
for value in sources.defaults_and_customizations() {
|
||||
if value.is_version_outdated() {
|
||||
@@ -865,14 +742,6 @@ impl Settings for AssistantSettings {
|
||||
merge(&mut settings.stream_edits, value.stream_edits);
|
||||
merge(&mut settings.single_file_review, value.single_file_review);
|
||||
merge(&mut settings.default_profile, value.default_profile);
|
||||
merge(
|
||||
&mut settings.preferred_completion_mode,
|
||||
value.preferred_completion_mode,
|
||||
);
|
||||
|
||||
settings
|
||||
.model_parameters
|
||||
.extend_from_slice(&value.model_parameters);
|
||||
|
||||
if let Some(profiles) = value.profiles {
|
||||
settings
|
||||
@@ -913,28 +782,25 @@ impl Settings for AssistantSettings {
|
||||
.and_then(|b| b.as_bool())
|
||||
{
|
||||
match &mut current.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(versioned)) => {
|
||||
match versioned.as_mut() {
|
||||
VersionedAssistantSettingsContent::V1(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
|
||||
VersionedAssistantSettingsContent::V2(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
Some(AgentSettingsContentInner::Versioned(versioned)) => match versioned.as_mut() {
|
||||
VersionedAgentSettingsContent::V1(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
}
|
||||
Some(AssistantSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
|
||||
|
||||
VersionedAgentSettingsContent::V2(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
},
|
||||
Some(AgentSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
|
||||
None => {
|
||||
current.inner = Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
current.inner =
|
||||
Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
enabled: Some(b),
|
||||
button: Some(b),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -956,7 +822,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deserialize_assistant_settings_with_version(cx: &mut TestAppContext) {
|
||||
async fn test_deserialize_agent_settings_with_version(cx: &mut TestAppContext) {
|
||||
let fs = fs::FakeFs::new(cx.executor().clone());
|
||||
fs.create_dir(paths::settings_file().parent().unwrap())
|
||||
.await
|
||||
@@ -965,13 +831,13 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
let test_settings = settings::SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
assert!(!AssistantSettings::get_global(cx).using_outdated_settings_version);
|
||||
assert!(!AgentSettings::get_global(cx).using_outdated_settings_version);
|
||||
assert_eq!(
|
||||
AssistantSettings::get_global(cx).default_model,
|
||||
AgentSettings::get_global(cx).default_model,
|
||||
LanguageModelSelection {
|
||||
provider: "zed.dev".into(),
|
||||
model: "claude-3-7-sonnet-latest".into(),
|
||||
@@ -980,36 +846,32 @@ mod tests {
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
settings::SettingsStore::global(cx).update_settings_file::<AssistantSettings>(
|
||||
settings::SettingsStore::global(cx).update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
|settings, _| {
|
||||
*settings = AssistantSettingsContent {
|
||||
inner: Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "test-provider".into(),
|
||||
model: "gpt-99".into(),
|
||||
}),
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
default_profile: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
},
|
||||
)),
|
||||
*settings = AgentSettingsContent {
|
||||
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "test-provider".into(),
|
||||
model: "gpt-99".into(),
|
||||
}),
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
default_profile: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
})),
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -1021,14 +883,14 @@ mod tests {
|
||||
assert!(raw_settings_value.contains(r#""version": "2""#));
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AssistantSettingsTest {
|
||||
agent: AssistantSettingsContent,
|
||||
struct AgentSettingsTest {
|
||||
agent: AgentSettingsContent,
|
||||
}
|
||||
|
||||
let assistant_settings: AssistantSettingsTest =
|
||||
let agent_settings: AgentSettingsTest =
|
||||
serde_json_lenient::from_str(&raw_settings_value).unwrap();
|
||||
|
||||
assert!(!assistant_settings.agent.is_version_outdated());
|
||||
assert!(!agent_settings.agent.is_version_outdated());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1053,29 +915,27 @@ mod tests {
|
||||
.set_user_settings(user_settings_content, cx)
|
||||
.unwrap();
|
||||
cx.set_global(test_settings);
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let assistant_settings = cx.update(|cx| AssistantSettings::get_global(cx).clone());
|
||||
assert!(assistant_settings.enabled);
|
||||
assert!(!assistant_settings.using_outdated_settings_version);
|
||||
assert_eq!(assistant_settings.default_model.model, "gpt-99");
|
||||
let agent_settings = cx.update(|cx| AgentSettings::get_global(cx).clone());
|
||||
assert!(agent_settings.enabled);
|
||||
assert!(!agent_settings.using_outdated_settings_version);
|
||||
assert_eq!(agent_settings.default_model.model, "gpt-99");
|
||||
|
||||
cx.update_global::<SettingsStore, _>(|settings_store, cx| {
|
||||
settings_store.update_user_settings::<AssistantSettings>(cx, |settings| {
|
||||
*settings = AssistantSettingsContent {
|
||||
inner: Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
enabled: Some(false),
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "xai".to_owned().into(),
|
||||
model: "grok".to_owned(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
settings_store.update_user_settings::<AgentSettings>(cx, |settings| {
|
||||
*settings = AgentSettingsContent {
|
||||
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
enabled: Some(false),
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "xai".to_owned(),
|
||||
model: "grok".to_owned(),
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1085,12 +945,12 @@ mod tests {
|
||||
let settings = cx.update(|cx| SettingsStore::global(cx).raw_user_settings().clone());
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AssistantSettingsTest {
|
||||
assistant: AssistantSettingsContent,
|
||||
struct AgentSettingsTest {
|
||||
assistant: AgentSettingsContent,
|
||||
agent: Option<serde_json_lenient::Value>,
|
||||
}
|
||||
|
||||
let assistant_settings: AssistantSettingsTest = serde_json::from_value(settings).unwrap();
|
||||
assert!(assistant_settings.agent.is_none());
|
||||
let agent_settings: AgentSettingsTest = serde_json::from_value(settings).unwrap();
|
||||
assert!(agent_settings.agent.is_none());
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ test-support = [
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
agent_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
|
||||
@@ -8,7 +8,7 @@ mod terminal_inline_assistant;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::{AssistantSettings, LanguageModelSelection};
|
||||
use agent_settings::{AgentSettings, LanguageModelSelection};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
@@ -97,7 +97,7 @@ pub fn init(
|
||||
cx: &mut App,
|
||||
) {
|
||||
cx.set_global(Assistant::default());
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
assistant_context_editor::init(client.clone(), cx);
|
||||
@@ -126,13 +126,13 @@ pub fn init(
|
||||
filter.hide_namespace(Assistant::NAMESPACE);
|
||||
});
|
||||
Assistant::update_global(cx, |assistant, cx| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
assistant.set_enabled(settings.enabled, cx);
|
||||
});
|
||||
cx.observe_global::<SettingsStore>(|cx| {
|
||||
Assistant::update_global(cx, |assistant, cx| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
assistant.set_enabled(settings.enabled, cx);
|
||||
});
|
||||
})
|
||||
@@ -159,11 +159,11 @@ fn init_language_model_settings(cx: &mut App) {
|
||||
}
|
||||
|
||||
fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
|
||||
language_model::SelectedModel {
|
||||
provider: LanguageModelProviderId::from(selection.provider.0.clone()),
|
||||
provider: LanguageModelProviderId::from(selection.provider.clone()),
|
||||
model: LanguageModelId::from(selection.model.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
|
||||
use crate::{
|
||||
DeployHistory, InlineAssistant, NewChat, terminal_inline_assistant::TerminalInlineAssistant,
|
||||
};
|
||||
use agent_settings::{AgentDockPosition, AgentSettings};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_context_editor::{
|
||||
AssistantContext, AssistantPanelDelegate, ContextEditor, ContextEditorToolbarItem,
|
||||
@@ -10,7 +11,6 @@ use assistant_context_editor::{
|
||||
DEFAULT_TAB_TITLE, InsertDraggedFiles, SlashCommandCompletionProvider,
|
||||
make_lsp_adapter_delegate,
|
||||
};
|
||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use client::{Client, Status, proto};
|
||||
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
|
||||
@@ -984,7 +984,7 @@ impl AssistantPanel {
|
||||
.map_or(true, |default| default.provider.id() != provider.id())
|
||||
{
|
||||
if let Some(model) = provider.default_model(cx) {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
this.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model),
|
||||
@@ -1231,10 +1231,10 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
|
||||
fn position(&self, _: &Window, cx: &App) -> DockPosition {
|
||||
match AssistantSettings::get_global(cx).dock {
|
||||
AssistantDockPosition::Left => DockPosition::Left,
|
||||
AssistantDockPosition::Bottom => DockPosition::Bottom,
|
||||
AssistantDockPosition::Right => DockPosition::Right,
|
||||
match AgentSettings::get_global(cx).dock {
|
||||
AgentDockPosition::Left => DockPosition::Left,
|
||||
AgentDockPosition::Bottom => DockPosition::Bottom,
|
||||
AgentDockPosition::Right => DockPosition::Right,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1243,22 +1243,18 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
|
||||
settings::update_settings_file::<AssistantSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => AssistantDockPosition::Left,
|
||||
DockPosition::Bottom => AssistantDockPosition::Bottom,
|
||||
DockPosition::Right => AssistantDockPosition::Right,
|
||||
};
|
||||
settings.set_dock(dock);
|
||||
},
|
||||
);
|
||||
settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => AgentDockPosition::Left,
|
||||
DockPosition::Bottom => AgentDockPosition::Bottom,
|
||||
DockPosition::Right => AgentDockPosition::Right,
|
||||
};
|
||||
settings.set_dock(dock);
|
||||
});
|
||||
}
|
||||
|
||||
fn size(&self, window: &Window, cx: &App) -> Pixels {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
match self.position(window, cx) {
|
||||
DockPosition::Left | DockPosition::Right => {
|
||||
self.width.unwrap_or(settings.default_width)
|
||||
@@ -1302,8 +1298,7 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
|
||||
fn icon(&self, _: &Window, cx: &App) -> Option<IconName> {
|
||||
(self.enabled(cx) && AssistantSettings::get_global(cx).button)
|
||||
.then_some(IconName::ZedAssistant)
|
||||
(self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _: &Window, _: &App) -> Option<&'static str> {
|
||||
|
||||
@@ -2,9 +2,9 @@ use crate::{
|
||||
Assistant, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist,
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_context_editor::{RequestType, humanize_token_count};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::{ErrorExt, telemetry::Telemetry};
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::{
|
||||
@@ -17,7 +17,9 @@ use editor::{
|
||||
ToDisplayPoint,
|
||||
},
|
||||
};
|
||||
use feature_flags::{FeatureFlagAppExt as _, ZedProFeatureFlag};
|
||||
use feature_flags::{
|
||||
Assistant2FeatureFlag, FeatureFlagAppExt as _, FeatureFlagViewExt as _, ZedProFeatureFlag,
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
||||
@@ -72,19 +74,25 @@ pub fn init(
|
||||
cx: &mut App,
|
||||
) {
|
||||
cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
|
||||
// Don't register now that the Agent is released.
|
||||
if false {
|
||||
cx.observe_new(|_, window, cx| {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
let workspace = cx.entity().clone();
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
inline_assistant.register_workspace(&workspace, window, cx)
|
||||
});
|
||||
cx.observe_new(|_, window, cx| {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
let workspace = cx.entity().clone();
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
inline_assistant.register_workspace(&workspace, window, cx)
|
||||
});
|
||||
|
||||
cx.observe_flag::<Assistant2FeatureFlag, _>(window, {
|
||||
|is_assistant2_enabled, _workspace, _window, cx| {
|
||||
InlineAssistant::update_global(cx, |inline_assistant, _cx| {
|
||||
inline_assistant.is_assistant2_enabled = is_assistant2_enabled;
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
const PROMPT_HISTORY_MAX_LEN: usize = 20;
|
||||
@@ -100,6 +108,7 @@ pub struct InlineAssistant {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
is_assistant2_enabled: bool,
|
||||
}
|
||||
|
||||
impl Global for InlineAssistant {}
|
||||
@@ -121,6 +130,7 @@ impl InlineAssistant {
|
||||
prompt_builder,
|
||||
telemetry,
|
||||
fs,
|
||||
is_assistant2_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +156,7 @@ impl InlineAssistant {
|
||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let enabled = AssistantSettings::get_global(cx).enabled;
|
||||
let enabled = AgentSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||
});
|
||||
@@ -189,7 +199,7 @@ impl InlineAssistant {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let is_assistant2_enabled = true;
|
||||
let is_assistant2_enabled = self.is_assistant2_enabled;
|
||||
|
||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
@@ -1751,7 +1761,7 @@ impl PromptEditor {
|
||||
LanguageModelSelector::new(
|
||||
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
@@ -2474,7 +2484,7 @@ impl InlineAssist {
|
||||
.read(cx)
|
||||
.active_context(cx)?
|
||||
.read(cx)
|
||||
.to_completion_request(None, RequestType::Chat, cx),
|
||||
.to_completion_request(RequestType::Chat, cx),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -2860,8 +2870,7 @@ impl CodegenAlternative {
|
||||
if let Some(ConfiguredModel { model, .. }) =
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
{
|
||||
let request =
|
||||
self.build_request(&model, user_prompt, assistant_panel_context.clone(), cx);
|
||||
let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx);
|
||||
match request {
|
||||
Ok(request) => {
|
||||
let total_count = model.count_tokens(request.clone(), cx);
|
||||
@@ -2906,8 +2915,7 @@ impl CodegenAlternative {
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request =
|
||||
self.build_request(&model, user_prompt, assistant_panel_context, cx)?;
|
||||
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
|
||||
self.request = Some(request.clone());
|
||||
|
||||
cx.spawn(async move |_, cx| model.stream_completion_text(request, &cx).await)
|
||||
@@ -2919,7 +2927,6 @@ impl CodegenAlternative {
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
user_prompt: String,
|
||||
assistant_panel_context: Option<LanguageModelRequest>,
|
||||
cx: &App,
|
||||
@@ -2974,7 +2981,7 @@ impl CodegenAlternative {
|
||||
messages,
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: AssistantSettings::temperature_for_model(&model, cx),
|
||||
temperature: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_context_editor::{RequestType, humanize_token_count};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::{
|
||||
@@ -261,7 +261,7 @@ impl TerminalInlineAssistant {
|
||||
.read(cx)
|
||||
.active_context(cx)?
|
||||
.read(cx)
|
||||
.to_completion_request(None, RequestType::Chat, cx),
|
||||
.to_completion_request(RequestType::Chat, cx),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
@@ -756,7 +756,7 @@ impl PromptEditor {
|
||||
LanguageModelSelector::new(
|
||||
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
|
||||
@@ -13,7 +13,7 @@ path = "src/assistant_context_editor.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
agent_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
chrono.workspace = true
|
||||
@@ -22,6 +22,7 @@ clock.workspace = true
|
||||
collections.workspace = true
|
||||
context_server.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
||||
@@ -3,7 +3,6 @@ mod context_tests;
|
||||
|
||||
use crate::patch::{AssistantEdit, AssistantPatch, AssistantPatchStatus};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_slash_command::{
|
||||
SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
@@ -1274,10 +1273,10 @@ impl AssistantContext {
|
||||
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut Context<Self>) {
|
||||
// Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit),
|
||||
// because otherwise you see in the UI that your empty message has a bunch of tokens already used.
|
||||
let request = self.to_completion_request(RequestType::Chat, cx);
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
|
||||
return;
|
||||
};
|
||||
let request = self.to_completion_request(Some(&model.model), RequestType::Chat, cx);
|
||||
let debounce = self.token_count.is_some();
|
||||
self.pending_token_count = cx.spawn(async move |this, cx| {
|
||||
async move {
|
||||
@@ -1423,7 +1422,7 @@ impl AssistantContext {
|
||||
}
|
||||
|
||||
let request = {
|
||||
let mut req = self.to_completion_request(Some(&model), RequestType::Chat, cx);
|
||||
let mut req = self.to_completion_request(RequestType::Chat, cx);
|
||||
// Skip the last message because it's likely to change and
|
||||
// therefore would be a waste to cache.
|
||||
req.messages.pop();
|
||||
@@ -2322,7 +2321,7 @@ impl AssistantContext {
|
||||
// Compute which messages to cache, including the last one.
|
||||
self.mark_cache_anchors(&model.cache_configuration(), false, cx);
|
||||
|
||||
let request = self.to_completion_request(Some(&model), request_type, cx);
|
||||
let request = self.to_completion_request(request_type, cx);
|
||||
|
||||
let assistant_message = self
|
||||
.insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
|
||||
@@ -2562,7 +2561,6 @@ impl AssistantContext {
|
||||
|
||||
pub fn to_completion_request(
|
||||
&self,
|
||||
model: Option<&Arc<dyn LanguageModel>>,
|
||||
request_type: RequestType,
|
||||
cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
@@ -2586,8 +2584,7 @@ impl AssistantContext {
|
||||
messages: Vec::new(),
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: model
|
||||
.and_then(|model| AssistantSettings::temperature_for_model(model, cx)),
|
||||
temperature: None,
|
||||
};
|
||||
for message in self.messages(cx) {
|
||||
if message.status != MessageStatus::Done {
|
||||
@@ -2984,7 +2981,7 @@ impl AssistantContext {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut request = self.to_completion_request(Some(&model.model), RequestType::Chat, cx);
|
||||
let mut request = self.to_completion_request(RequestType::Chat, cx);
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
|
||||
@@ -43,8 +43,9 @@ use workspace::Workspace;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_inserting_and_removing_messages(cx: &mut App) {
|
||||
init_test(cx);
|
||||
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@@ -181,8 +182,9 @@ fn test_inserting_and_removing_messages(cx: &mut App) {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_message_splitting(cx: &mut App) {
|
||||
init_test(cx);
|
||||
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
LanguageModelRegistry::test(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
@@ -283,8 +285,9 @@ fn test_message_splitting(cx: &mut App) {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_messages_for_offsets(cx: &mut App) {
|
||||
init_test(cx);
|
||||
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@@ -375,8 +378,10 @@ fn test_messages_for_offsets(cx: &mut App) {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
cx.update(init_test);
|
||||
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
cx.update(Project::init_settings);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
|
||||
fs.insert_tree(
|
||||
@@ -666,19 +671,22 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
cx.update(prompt_store::init);
|
||||
let mut settings_store = cx.update(SettingsStore::test);
|
||||
cx.update(|cx| {
|
||||
init_test(cx);
|
||||
cx.update_global(|settings_store: &mut SettingsStore, cx| {
|
||||
settings_store
|
||||
.set_user_settings(
|
||||
r#"{ "assistant": { "enable_experimental_live_diffs": true } }"#,
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
settings_store
|
||||
.set_user_settings(
|
||||
r#"{ "assistant": { "enable_experimental_live_diffs": true } }"#,
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
cx.set_global(settings_store);
|
||||
cx.update(language::init);
|
||||
cx.update(Project::init_settings);
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [Path::new("/root")], cx).await;
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
|
||||
@@ -1061,8 +1069,9 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_serialization(cx: &mut TestAppContext) {
|
||||
cx.update(init_test);
|
||||
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@@ -1138,8 +1147,6 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
cx.update(init_test);
|
||||
|
||||
let min_peers = env::var("MIN_PEERS")
|
||||
.map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
|
||||
.unwrap_or(2);
|
||||
@@ -1150,6 +1157,10 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(50);
|
||||
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
|
||||
let slash_commands = cx.update(SlashCommandRegistry::default_global);
|
||||
slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false);
|
||||
slash_commands.register_command(FakeSlashCommand("cmd-2".into()), false);
|
||||
@@ -1418,8 +1429,9 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
|
||||
#[gpui::test]
|
||||
fn test_mark_cache_anchors(cx: &mut App) {
|
||||
init_test(cx);
|
||||
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new(|cx| {
|
||||
@@ -1594,16 +1606,6 @@ fn messages_cache(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut App) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
prompt_store::init(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
language::init(cx);
|
||||
assistant_settings::init(cx);
|
||||
Project::init_settings(cx);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FakeSlashCommand(String);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
|
||||
use assistant_slash_commands::{
|
||||
DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand,
|
||||
@@ -18,6 +18,7 @@ use editor::{
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use editor::{FoldPlaceholder, display_map::CreaseId};
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt as _};
|
||||
use fs::Fs;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
@@ -302,7 +303,7 @@ impl ContextEditor {
|
||||
LanguageModelSelector::new(
|
||||
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
@@ -2394,11 +2395,19 @@ impl ContextEditor {
|
||||
.on_click({
|
||||
let focus_handle = self.focus_handle(cx).clone();
|
||||
move |_event, window, cx| {
|
||||
focus_handle.dispatch_action(
|
||||
&zed_actions::agent::OpenConfiguration,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
if cx.has_flag::<Assistant2FeatureFlag>() {
|
||||
focus_handle.dispatch_action(
|
||||
&zed_actions::agent::OpenConfiguration,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
focus_handle.dispatch_action(
|
||||
&zed_actions::assistant::ShowConfiguration,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
};
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -2453,7 +2462,7 @@ impl ContextEditor {
|
||||
})
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new(
|
||||
if AssistantSettings::get_global(cx).are_live_diffs_enabled(cx) {
|
||||
if AgentSettings::get_global(cx).are_live_diffs_enabled(cx) {
|
||||
"Chat"
|
||||
} else {
|
||||
"Send"
|
||||
@@ -2672,7 +2681,7 @@ impl ContextEditor {
|
||||
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|
||||
|this, _, _window, cx| {
|
||||
this.last_error = None;
|
||||
cx.open_url(&zed_urls::account_upgrade_url(cx));
|
||||
cx.open_url(&zed_urls::account_url(cx));
|
||||
cx.notify();
|
||||
},
|
||||
)))
|
||||
@@ -3140,7 +3149,7 @@ impl Render for ContextEditor {
|
||||
.w_full()
|
||||
.justify_end()
|
||||
.when(
|
||||
AssistantSettings::get_global(cx).are_live_diffs_enabled(cx),
|
||||
AgentSettings::get_global(cx).are_live_diffs_enabled(cx),
|
||||
|buttons| {
|
||||
buttons
|
||||
.items_center()
|
||||
|
||||
@@ -7,7 +7,6 @@ mod tool_working_set;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -62,34 +61,11 @@ impl ToolUseStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolResultOutput {
|
||||
pub content: String,
|
||||
pub output: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl From<String> for ToolResultOutput {
|
||||
fn from(value: String) -> Self {
|
||||
ToolResultOutput {
|
||||
content: value,
|
||||
output: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ToolResultOutput {
|
||||
type Target = String;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of running a tool, containing both the asynchronous output
|
||||
/// and an optional card view that can be rendered immediately.
|
||||
pub struct ToolResult {
|
||||
/// The asynchronous task that will eventually resolve to the tool's output
|
||||
pub output: Task<Result<ToolResultOutput>>,
|
||||
pub output: Task<Result<String>>,
|
||||
/// An optional view to present the output of the tool.
|
||||
pub card: Option<AnyToolCard>,
|
||||
}
|
||||
@@ -152,9 +128,9 @@ impl AnyToolCard {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Task<Result<ToolResultOutput>>> for ToolResult {
|
||||
impl From<Task<Result<String>>> for ToolResult {
|
||||
/// Convert from a task to a ToolResult with no card
|
||||
fn from(output: Task<Result<ToolResultOutput>>) -> Self {
|
||||
fn from(output: Task<Result<String>>) -> Self {
|
||||
Self { output, card: None }
|
||||
}
|
||||
}
|
||||
@@ -211,16 +187,6 @@ pub trait Tool: 'static + Send + Sync {
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult;
|
||||
|
||||
fn deserialize_card(
|
||||
self: Arc<Self>,
|
||||
_output: serde_json::Value,
|
||||
_project: Entity<Project>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Option<AnyToolCard> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for dyn Tool {
|
||||
|
||||
@@ -18,7 +18,7 @@ eval = []
|
||||
aho-corasick.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
agent_settings.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
|
||||
@@ -24,9 +24,10 @@ mod web_search_tool;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::AssistantSettings;
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_tool::ToolRegistry;
|
||||
use copy_path_tool::CopyPathTool;
|
||||
use feature_flags::{AgentStreamEditsFeatureFlag, FeatureFlagAppExt};
|
||||
use gpui::{App, Entity};
|
||||
use http_client::HttpClientWithUrl;
|
||||
use language_model::LanguageModelRegistry;
|
||||
@@ -76,6 +77,8 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
registry.register_tool(FetchTool::new(http_client));
|
||||
|
||||
register_edit_file_tool(cx);
|
||||
cx.observe_flag::<AgentStreamEditsFeatureFlag, _>(|_, cx| register_edit_file_tool(cx))
|
||||
.detach();
|
||||
cx.observe_global::<SettingsStore>(register_edit_file_tool)
|
||||
.detach();
|
||||
|
||||
@@ -111,7 +114,7 @@ fn register_edit_file_tool(cx: &mut App) {
|
||||
registry.unregister_tool(EditFileTool);
|
||||
registry.unregister_tool(StreamingEditFileTool);
|
||||
|
||||
if AssistantSettings::get_global(cx).stream_edits(cx) {
|
||||
if AgentSettings::get_global(cx).stream_edits(cx) {
|
||||
registry.register_tool(StreamingEditFileTool);
|
||||
} else {
|
||||
registry.register_tool(CreateFileTool);
|
||||
@@ -157,7 +160,7 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_builtin_tool_schema_compatibility(cx: &mut App) {
|
||||
settings::init(cx);
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
|
||||
let client = Client::new(
|
||||
Arc::new(FakeSystemClock::new()),
|
||||
|
||||
@@ -107,9 +107,10 @@ impl Tool for CopyPathTool {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
match copy_task.await {
|
||||
Ok(_) => Ok(
|
||||
format!("Copied {} to {}", input.source_path, input.destination_path).into(),
|
||||
),
|
||||
Ok(_) => Ok(format!(
|
||||
"Copied {} to {}",
|
||||
input.source_path, input.destination_path
|
||||
)),
|
||||
Err(err) => Err(anyhow!(
|
||||
"Failed to copy {} to {}: {}",
|
||||
input.source_path,
|
||||
|
||||
@@ -88,7 +88,7 @@ impl Tool for CreateDirectoryTool {
|
||||
.await
|
||||
.map_err(|err| anyhow!("Unable to create directory {destination_path}: {err}"))?;
|
||||
|
||||
Ok(format!("Created directory {destination_path}").into())
|
||||
Ok(format!("Created directory {destination_path}"))
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ impl Tool for CreateFileTool {
|
||||
.await
|
||||
.map_err(|err| anyhow!("Unable to save buffer for {destination_path}: {err}"))?;
|
||||
|
||||
Ok(format!("Created file {destination_path}").into())
|
||||
Ok(format!("Created file {destination_path}"))
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ impl Tool for DeletePathTool {
|
||||
|
||||
match delete {
|
||||
Some(deletion_task) => match deletion_task.await {
|
||||
Ok(()) => Ok(format!("Deleted {path_str}").into()),
|
||||
Ok(()) => Ok(format!("Deleted {path_str}")),
|
||||
Err(err) => Err(anyhow!("Failed to delete {path_str}: {err}")),
|
||||
},
|
||||
None => Err(anyhow!(
|
||||
|
||||
@@ -122,9 +122,9 @@ impl Tool for DiagnosticsTool {
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
Ok("File doesn't have errors or warnings!".to_string().into())
|
||||
Ok("File doesn't have errors or warnings!".to_string())
|
||||
} else {
|
||||
Ok(output.into())
|
||||
Ok(output)
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@@ -158,12 +158,10 @@ impl Tool for DiagnosticsTool {
|
||||
});
|
||||
|
||||
if has_diagnostics {
|
||||
Task::ready(Ok(output.into())).into()
|
||||
Task::ready(Ok(output)).into()
|
||||
} else {
|
||||
Task::ready(Ok("No errors or warnings found in the project."
|
||||
.to_string()
|
||||
.into()))
|
||||
.into()
|
||||
Task::ready(Ok("No errors or warnings found in the project.".to_string()))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,7 +895,6 @@ fn tool_result(
|
||||
tool_name: name.into(),
|
||||
is_error: false,
|
||||
content: result.into(),
|
||||
output: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use crate::{
|
||||
replace::{replace_exact, replace_with_flexible_indent},
|
||||
schema::json_schema_for,
|
||||
streaming_edit_file_tool::StreamingEditFileToolOutput,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{
|
||||
ActionLog, AnyToolCard, Tool, ToolCard, ToolResult, ToolResultOutput, ToolUseStatus,
|
||||
};
|
||||
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolCard, ToolResult, ToolUseStatus};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorElement, EditorMode, EditorStyle, MultiBuffer, PathKey};
|
||||
use gpui::{
|
||||
@@ -156,7 +153,7 @@ impl Tool for EditFileTool {
|
||||
});
|
||||
|
||||
let card_clone = card.clone();
|
||||
let task: Task<Result<ToolResultOutput, _>> = cx.spawn(async move |cx: &mut AsyncApp| {
|
||||
let task = cx.spawn(async move |cx: &mut AsyncApp| {
|
||||
let project_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.find_project_path(&input.path, cx)
|
||||
@@ -284,29 +281,16 @@ impl Tool for EditFileTool {
|
||||
|
||||
if let Some(card) = card_clone {
|
||||
card.update(cx, |card, cx| {
|
||||
card.set_diff(
|
||||
project_path.path.clone(),
|
||||
old_text.clone(),
|
||||
new_text.clone(),
|
||||
cx,
|
||||
);
|
||||
card.set_diff(project_path.path.clone(), old_text, new_text, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
Ok(ToolResultOutput {
|
||||
content: format!(
|
||||
"Edited {}:\n\n```diff\n{}\n```",
|
||||
input.path.display(),
|
||||
diff_str
|
||||
),
|
||||
output: serde_json::to_value(StreamingEditFileToolOutput {
|
||||
original_path: input.path,
|
||||
new_text,
|
||||
old_text,
|
||||
})
|
||||
.ok(),
|
||||
})
|
||||
Ok(format!(
|
||||
"Edited {}:\n\n```diff\n{}\n```",
|
||||
input.path.display(),
|
||||
diff_str
|
||||
))
|
||||
});
|
||||
|
||||
ToolResult {
|
||||
@@ -314,32 +298,6 @@ impl Tool for EditFileTool {
|
||||
card: card.map(AnyToolCard::from),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_card(
|
||||
self: Arc<Self>,
|
||||
output: serde_json::Value,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<AnyToolCard> {
|
||||
let output = match serde_json::from_value::<StreamingEditFileToolOutput>(output) {
|
||||
Ok(output) => output,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let card = cx.new(|cx| {
|
||||
let mut card = EditFileToolCard::new(output.original_path.clone(), project, window, cx);
|
||||
card.set_diff(
|
||||
output.original_path.into(),
|
||||
output.old_text,
|
||||
output.new_text,
|
||||
cx,
|
||||
);
|
||||
card
|
||||
});
|
||||
|
||||
Some(card.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditFileToolCard {
|
||||
@@ -623,18 +581,18 @@ impl ToolCard for EditFileToolCard {
|
||||
(IconName::ChevronDown, "Expand Code Block")
|
||||
};
|
||||
|
||||
let gradient_overlay =
|
||||
div()
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.left_0()
|
||||
.w_full()
|
||||
.h_2_5()
|
||||
.bg(gpui::linear_gradient(
|
||||
0.,
|
||||
gpui::linear_color_stop(cx.theme().colors().editor_background, 0.),
|
||||
gpui::linear_color_stop(cx.theme().colors().editor_background.opacity(0.), 1.),
|
||||
));
|
||||
let gradient_overlay = div()
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.left_0()
|
||||
.w_full()
|
||||
.h_2_5()
|
||||
.rounded_b_lg()
|
||||
.bg(gpui::linear_gradient(
|
||||
0.,
|
||||
gpui::linear_color_stop(cx.theme().colors().editor_background, 0.),
|
||||
gpui::linear_color_stop(cx.theme().colors().editor_background.opacity(0.), 1.),
|
||||
));
|
||||
|
||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||
|
||||
@@ -652,9 +610,8 @@ impl ToolCard for EditFileToolCard {
|
||||
|
||||
let mut container = v_flex()
|
||||
.p_3()
|
||||
.gap_1()
|
||||
.gap_1p5()
|
||||
.border_t_1()
|
||||
.rounded_md()
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background);
|
||||
|
||||
@@ -669,7 +626,7 @@ impl ToolCard for EditFileToolCard {
|
||||
_ => div().w_1_2(),
|
||||
}
|
||||
.id("loading_div")
|
||||
.h_1()
|
||||
.h_2()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().element_active)
|
||||
.with_animation(
|
||||
@@ -691,7 +648,7 @@ impl ToolCard for EditFileToolCard {
|
||||
.border_1()
|
||||
.when(failed, |card| card.border_dashed())
|
||||
.border_color(border_color)
|
||||
.rounded_md()
|
||||
.rounded_lg()
|
||||
.overflow_hidden()
|
||||
.child(codeblock_header)
|
||||
.when(failed && self.error_expanded, |card| {
|
||||
@@ -745,8 +702,8 @@ impl ToolCard for EditFileToolCard {
|
||||
|editor_container| editor_container.child(gradient_overlay),
|
||||
),
|
||||
)
|
||||
.when(is_collapsible, |card| {
|
||||
card.child(
|
||||
.when(is_collapsible, |editor_container| {
|
||||
editor_container.child(
|
||||
h_flex()
|
||||
.id(("expand-button", self.editor_unique_id))
|
||||
.flex_none()
|
||||
@@ -754,7 +711,6 @@ impl ToolCard for EditFileToolCard {
|
||||
.h_5()
|
||||
.justify_center()
|
||||
.border_t_1()
|
||||
.rounded_b_md()
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.hover(|style| {
|
||||
|
||||
@@ -166,7 +166,7 @@ impl Tool for FetchTool {
|
||||
bail!("no textual content found");
|
||||
}
|
||||
|
||||
Ok(text.into())
|
||||
Ok(text)
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Tool for FindPathTool {
|
||||
sender.send(paginated_matches.to_vec()).log_err();
|
||||
|
||||
if matches.is_empty() {
|
||||
Ok("No matches found".to_string().into())
|
||||
Ok("No matches found".to_string())
|
||||
} else {
|
||||
let mut message = format!("Found {} total matches.", matches.len());
|
||||
if matches.len() > RESULTS_PER_PAGE {
|
||||
@@ -113,7 +113,7 @@ impl Tool for FindPathTool {
|
||||
for mat in matches.into_iter().skip(offset).take(RESULTS_PER_PAGE) {
|
||||
write!(&mut message, "\n{}", mat.display()).unwrap();
|
||||
}
|
||||
Ok(message.into())
|
||||
Ok(message)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -260,16 +260,16 @@ impl Tool for GrepTool {
|
||||
}
|
||||
|
||||
if matches_found == 0 {
|
||||
Ok("No matches found".to_string().into())
|
||||
Ok("No matches found".to_string())
|
||||
} else if has_more_matches {
|
||||
Ok(format!(
|
||||
"Showing matches {}-{} (there were more matches found; use offset: {} to see next page):\n{output}",
|
||||
input.offset + 1,
|
||||
input.offset + matches_found,
|
||||
input.offset + RESULTS_PER_PAGE,
|
||||
).into())
|
||||
))
|
||||
} else {
|
||||
Ok(format!("Found {matches_found} matches:\n{output}").into())
|
||||
Ok(format!("Found {matches_found} matches:\n{output}"))
|
||||
}
|
||||
}).into()
|
||||
}
|
||||
@@ -748,9 +748,9 @@ mod tests {
|
||||
match task.output.await {
|
||||
Ok(result) => {
|
||||
if cfg!(windows) {
|
||||
result.content.replace("root\\", "root/")
|
||||
result.replace("root\\", "root/")
|
||||
} else {
|
||||
result.content
|
||||
result
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("Failed to run grep tool: {}", e),
|
||||
|
||||
@@ -102,7 +102,7 @@ impl Tool for ListDirectoryTool {
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
return Task::ready(Ok(output.into())).into();
|
||||
return Task::ready(Ok(output)).into();
|
||||
}
|
||||
|
||||
let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else {
|
||||
@@ -134,8 +134,8 @@ impl Tool for ListDirectoryTool {
|
||||
.unwrap();
|
||||
}
|
||||
if output.is_empty() {
|
||||
return Task::ready(Ok(format!("{} is empty.", input.path).into())).into();
|
||||
return Task::ready(Ok(format!("{} is empty.", input.path))).into();
|
||||
}
|
||||
Task::ready(Ok(output.into())).into()
|
||||
Task::ready(Ok(output)).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +117,10 @@ impl Tool for MovePathTool {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
match rename_task.await {
|
||||
Ok(_) => {
|
||||
Ok(format!("Moved {} to {}", input.source_path, input.destination_path).into())
|
||||
}
|
||||
Ok(_) => Ok(format!(
|
||||
"Moved {} to {}",
|
||||
input.source_path, input.destination_path
|
||||
)),
|
||||
Err(err) => Err(anyhow!(
|
||||
"Failed to move {} to {}: {}",
|
||||
input.source_path,
|
||||
|
||||
@@ -73,6 +73,6 @@ impl Tool for NowTool {
|
||||
};
|
||||
let text = format!("The current datetime is {now}.");
|
||||
|
||||
Task::ready(Ok(text.into())).into()
|
||||
Task::ready(Ok(text)).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ impl Tool for OpenTool {
|
||||
}
|
||||
.context("Failed to open URL or file path")?;
|
||||
|
||||
Ok(format!("Successfully opened {}", input.path_or_url).into())
|
||||
Ok(format!("Successfully opened {}", input.path_or_url))
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -145,9 +145,9 @@ impl Tool for ReadFileTool {
|
||||
let lines = text.split('\n').skip(start_row as usize);
|
||||
if let Some(end) = input.end_line {
|
||||
let count = end.saturating_sub(start).saturating_add(1); // Ensure at least 1 line
|
||||
Itertools::intersperse(lines.take(count as usize), "\n").collect::<String>().into()
|
||||
Itertools::intersperse(lines.take(count as usize), "\n").collect()
|
||||
} else {
|
||||
Itertools::intersperse(lines, "\n").collect::<String>().into()
|
||||
Itertools::intersperse(lines, "\n").collect()
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -180,7 +180,7 @@ impl Tool for ReadFileTool {
|
||||
log.buffer_read(buffer, cx);
|
||||
})?;
|
||||
|
||||
Ok(result.into())
|
||||
Ok(result)
|
||||
} else {
|
||||
// File is too big, so return the outline
|
||||
// and a suggestion to read again with line numbers.
|
||||
@@ -192,7 +192,7 @@ impl Tool for ReadFileTool {
|
||||
|
||||
Using the line numbers in this outline, you can call this tool again while specifying
|
||||
the start_line and end_line fields to see the implementations of symbols in the outline."
|
||||
}.into())
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -258,7 +258,7 @@ mod test {
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap().content, "This is a small file content");
|
||||
assert_eq!(result.unwrap(), "This is a small file content");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -358,7 +358,7 @@ mod test {
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap().content, "Line 2\nLine 3\nLine 4");
|
||||
assert_eq!(result.unwrap(), "Line 2\nLine 3\nLine 4");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -389,7 +389,7 @@ mod test {
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap().content, "Line 1\nLine 2");
|
||||
assert_eq!(result.unwrap(), "Line 1\nLine 2");
|
||||
|
||||
// end_line of 0 should result in at least 1 line
|
||||
let result = cx
|
||||
@@ -404,7 +404,7 @@ mod test {
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap().content, "Line 1");
|
||||
assert_eq!(result.unwrap(), "Line 1");
|
||||
|
||||
// when start_line > end_line, should still return at least 1 line
|
||||
let result = cx
|
||||
@@ -419,7 +419,7 @@ mod test {
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap().content, "Line 3");
|
||||
assert_eq!(result.unwrap(), "Line 3");
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
schema::json_schema_for,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolResult, ToolResultOutput};
|
||||
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolResult};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task};
|
||||
use indoc::formatdoc;
|
||||
@@ -67,13 +67,6 @@ pub struct StreamingEditFileToolInput {
|
||||
pub create_or_overwrite: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct StreamingEditFileToolOutput {
|
||||
pub original_path: PathBuf,
|
||||
pub new_text: String,
|
||||
pub old_text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
struct PartialInput {
|
||||
#[serde(default)]
|
||||
@@ -255,12 +248,6 @@ impl Tool for StreamingEditFileTool {
|
||||
});
|
||||
let (new_text, diff) = futures::join!(new_text, diff);
|
||||
|
||||
let output = StreamingEditFileToolOutput {
|
||||
original_path: project_path.path.to_path_buf(),
|
||||
new_text: new_text.clone(),
|
||||
old_text: old_text.clone(),
|
||||
};
|
||||
|
||||
if let Some(card) = card_clone {
|
||||
card.update(cx, |card, cx| {
|
||||
card.set_diff(project_path.path.clone(), old_text, new_text, cx);
|
||||
@@ -277,13 +264,10 @@ impl Tool for StreamingEditFileTool {
|
||||
I can perform the requested edits.
|
||||
"}))
|
||||
} else {
|
||||
Ok("No edits were made.".to_string().into())
|
||||
Ok("No edits were made.".to_string())
|
||||
}
|
||||
} else {
|
||||
Ok(ToolResultOutput {
|
||||
content: format!("Edited {}:\n\n```diff\n{}\n```", input_path, diff),
|
||||
output: serde_json::to_value(output).ok(),
|
||||
})
|
||||
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input_path, diff))
|
||||
}
|
||||
});
|
||||
|
||||
@@ -292,32 +276,6 @@ impl Tool for StreamingEditFileTool {
|
||||
card: card.map(AnyToolCard::from),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_card(
|
||||
self: Arc<Self>,
|
||||
output: serde_json::Value,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<AnyToolCard> {
|
||||
let output = match serde_json::from_value::<StreamingEditFileToolOutput>(output) {
|
||||
Ok(output) => output,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let card = cx.new(|cx| {
|
||||
let mut card = EditFileToolCard::new(output.original_path.clone(), project, window, cx);
|
||||
card.set_diff(
|
||||
output.original_path.into(),
|
||||
output.old_text,
|
||||
output.new_text,
|
||||
cx,
|
||||
);
|
||||
card
|
||||
});
|
||||
|
||||
Some(card.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -178,7 +178,7 @@ impl Tool for TerminalTool {
|
||||
let exit_status = child.wait()?;
|
||||
let (processed_content, _) =
|
||||
process_content(content, &input.command, Some(exit_status));
|
||||
Ok(processed_content.into())
|
||||
Ok(processed_content)
|
||||
});
|
||||
return ToolResult {
|
||||
output: task,
|
||||
@@ -266,7 +266,7 @@ impl Tool for TerminalTool {
|
||||
card.elapsed_time = Some(card.start_instant.elapsed());
|
||||
});
|
||||
|
||||
Ok(processed_content.into())
|
||||
Ok(processed_content)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -661,7 +661,7 @@ mod tests {
|
||||
)
|
||||
});
|
||||
|
||||
let output = result.output.await.log_err().map(|output| output.content);
|
||||
let output = result.output.await.log_err();
|
||||
assert_eq!(output, Some("Command executed successfully.".into()));
|
||||
}
|
||||
|
||||
@@ -693,11 +693,7 @@ mod tests {
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_| {
|
||||
let output = headless_result
|
||||
.output
|
||||
.await
|
||||
.log_err()
|
||||
.map(|output| output.content);
|
||||
let output = headless_result.output.await.log_err();
|
||||
assert_eq!(output, expected);
|
||||
})
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ impl Tool for ThinkingTool {
|
||||
) -> ToolResult {
|
||||
// This tool just "thinks out loud" and doesn't perform any actions.
|
||||
Task::ready(match serde_json::from_value::<ThinkingToolInput>(input) {
|
||||
Ok(_input) => Ok("Finished thinking.".to_string().into()),
|
||||
Ok(_input) => Ok("Finished thinking.".to_string()),
|
||||
Err(err) => Err(anyhow!(err)),
|
||||
})
|
||||
.into()
|
||||
|
||||
@@ -72,9 +72,7 @@ impl Tool for WebSearchTool {
|
||||
let search_task = search_task.clone();
|
||||
async move {
|
||||
let response = search_task.await.map_err(|err| anyhow!(err))?;
|
||||
serde_json::to_string(&response)
|
||||
.context("Failed to serialize search results")
|
||||
.map(Into::into)
|
||||
serde_json::to_string(&response).context("Failed to serialize search results")
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -17,8 +17,3 @@ fn server_url(cx: &App) -> &str {
|
||||
pub fn account_url(cx: &App) -> String {
|
||||
format!("{server_url}/account", server_url = server_url(cx))
|
||||
}
|
||||
|
||||
/// Returns the URL to the page to upgrade a trial or start a new zed pro subscription.
|
||||
pub fn account_upgrade_url(cx: &App) -> String {
|
||||
format!("{server_url}/account/upgrade", server_url = server_url(cx))
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ zed_llm_client.workspace = true
|
||||
[dev-dependencies]
|
||||
assistant = { workspace = true, features = ["test-support"] }
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
||||
@@ -1023,6 +1023,29 @@ async fn handle_customer_subscription_event(
|
||||
.get_billing_subscription_by_stripe_subscription_id(&subscription.id)
|
||||
.await?
|
||||
{
|
||||
let llm_db = app
|
||||
.llm_db
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("LLM DB not initialized"))?;
|
||||
|
||||
let new_period_start_at =
|
||||
chrono::DateTime::from_timestamp(subscription.current_period_start, 0)
|
||||
.ok_or_else(|| anyhow!("No subscription period start"))?;
|
||||
let new_period_end_at =
|
||||
chrono::DateTime::from_timestamp(subscription.current_period_end, 0)
|
||||
.ok_or_else(|| anyhow!("No subscription period end"))?;
|
||||
|
||||
llm_db
|
||||
.transfer_existing_subscription_usage(
|
||||
billing_customer.user_id,
|
||||
&existing_subscription,
|
||||
subscription_kind,
|
||||
subscription.status.into(),
|
||||
new_period_start_at,
|
||||
new_period_end_at,
|
||||
)
|
||||
.await?;
|
||||
|
||||
app.db
|
||||
.update_billing_subscription(
|
||||
existing_subscription.id,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use chrono::Timelike;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use crate::db::UserId;
|
||||
use crate::db::billing_subscription::{StripeSubscriptionStatus, SubscriptionKind};
|
||||
use crate::db::{UserId, billing_subscription};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -24,6 +26,62 @@ pub fn convert_chrono_to_time(datetime: DateTimeUtc) -> anyhow::Result<Primitive
|
||||
}
|
||||
|
||||
impl LlmDatabase {
|
||||
pub async fn create_subscription_usage(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
period_start_at: DateTimeUtc,
|
||||
period_end_at: DateTimeUtc,
|
||||
plan: SubscriptionKind,
|
||||
model_requests: i32,
|
||||
edit_predictions: i32,
|
||||
) -> Result<subscription_usage::Model> {
|
||||
self.transaction(|tx| async move {
|
||||
self.create_subscription_usage_in_tx(
|
||||
user_id,
|
||||
period_start_at,
|
||||
period_end_at,
|
||||
plan,
|
||||
model_requests,
|
||||
edit_predictions,
|
||||
&tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn create_subscription_usage_in_tx(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
period_start_at: DateTimeUtc,
|
||||
period_end_at: DateTimeUtc,
|
||||
plan: SubscriptionKind,
|
||||
model_requests: i32,
|
||||
edit_predictions: i32,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<subscription_usage::Model> {
|
||||
// Clear out the nanoseconds so that these timestamps are comparable with Unix timestamps.
|
||||
let period_start_at = period_start_at.with_nanosecond(0).unwrap();
|
||||
let period_end_at = period_end_at.with_nanosecond(0).unwrap();
|
||||
|
||||
let period_start_at = convert_chrono_to_time(period_start_at)?;
|
||||
let period_end_at = convert_chrono_to_time(period_end_at)?;
|
||||
|
||||
Ok(
|
||||
subscription_usage::Entity::insert(subscription_usage::ActiveModel {
|
||||
id: ActiveValue::set(Uuid::now_v7()),
|
||||
user_id: ActiveValue::set(user_id),
|
||||
period_start_at: ActiveValue::set(period_start_at),
|
||||
period_end_at: ActiveValue::set(period_end_at),
|
||||
plan: ActiveValue::set(plan),
|
||||
model_requests: ActiveValue::set(model_requests),
|
||||
edit_predictions: ActiveValue::set(edit_predictions),
|
||||
})
|
||||
.exec_with_returning(tx)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_subscription_usage_for_period(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
@@ -56,4 +114,54 @@ impl LlmDatabase {
|
||||
.one(tx)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn transfer_existing_subscription_usage(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
existing_subscription: &billing_subscription::Model,
|
||||
new_subscription_kind: Option<SubscriptionKind>,
|
||||
new_subscription_status: StripeSubscriptionStatus,
|
||||
new_period_start_at: DateTimeUtc,
|
||||
new_period_end_at: DateTimeUtc,
|
||||
) -> Result<Option<subscription_usage::Model>> {
|
||||
self.transaction(|tx| async move {
|
||||
match (existing_subscription.kind, new_subscription_status) {
|
||||
(Some(SubscriptionKind::ZedProTrial), StripeSubscriptionStatus::Active) => {
|
||||
let trial_period_start_at = existing_subscription
|
||||
.current_period_start_at()
|
||||
.ok_or_else(|| anyhow!("No trial subscription period start"))?;
|
||||
let trial_period_end_at = existing_subscription
|
||||
.current_period_end_at()
|
||||
.ok_or_else(|| anyhow!("No trial subscription period end"))?;
|
||||
|
||||
let existing_usage = self
|
||||
.get_subscription_usage_for_period_in_tx(
|
||||
user_id,
|
||||
trial_period_start_at,
|
||||
trial_period_end_at,
|
||||
&tx,
|
||||
)
|
||||
.await?;
|
||||
if let Some(existing_usage) = existing_usage {
|
||||
return Ok(Some(
|
||||
self.create_subscription_usage_in_tx(
|
||||
user_id,
|
||||
new_period_start_at,
|
||||
new_period_end_at,
|
||||
new_subscription_kind.unwrap_or(existing_usage.plan),
|
||||
existing_usage.model_requests,
|
||||
existing_usage.edit_predictions,
|
||||
&tx,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod provider_tests;
|
||||
mod subscription_usage_tests;
|
||||
|
||||
use gpui::BackgroundExecutor;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
119
crates/collab/src/llm/db/tests/subscription_usage_tests.rs
Normal file
119
crates/collab/src/llm/db/tests/subscription_usage_tests.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use chrono::{Duration, Utc};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::db::billing_subscription::{StripeSubscriptionStatus, SubscriptionKind};
|
||||
use crate::db::{UserId, billing_subscription};
|
||||
use crate::llm::db::LlmDatabase;
|
||||
use crate::test_llm_db;
|
||||
|
||||
test_llm_db!(
|
||||
test_transfer_existing_subscription_usage,
|
||||
test_transfer_existing_subscription_usage_postgres
|
||||
);
|
||||
|
||||
async fn test_transfer_existing_subscription_usage(db: &mut LlmDatabase) {
|
||||
// Test when an existing Zed Pro trial subscription is upgraded to Zed Pro.
|
||||
{
|
||||
let user_id = UserId(1);
|
||||
|
||||
let now = Utc::now();
|
||||
|
||||
let trial_period_start_at = now - Duration::days(14);
|
||||
let trial_period_end_at = now;
|
||||
|
||||
let new_period_start_at = now;
|
||||
let new_period_end_at = now + Duration::days(30);
|
||||
|
||||
let existing_subscription = billing_subscription::Model {
|
||||
kind: Some(SubscriptionKind::ZedProTrial),
|
||||
stripe_current_period_start: Some(trial_period_start_at.timestamp()),
|
||||
stripe_current_period_end: Some(trial_period_end_at.timestamp()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let existing_usage = db
|
||||
.create_subscription_usage(
|
||||
user_id,
|
||||
trial_period_start_at,
|
||||
trial_period_end_at,
|
||||
SubscriptionKind::ZedProTrial,
|
||||
25,
|
||||
1_000,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let transferred_usage = db
|
||||
.transfer_existing_subscription_usage(
|
||||
user_id,
|
||||
&existing_subscription,
|
||||
Some(SubscriptionKind::ZedPro),
|
||||
StripeSubscriptionStatus::Active,
|
||||
new_period_start_at,
|
||||
new_period_end_at,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
transferred_usage.is_some(),
|
||||
"subscription usage not transferred successfully"
|
||||
);
|
||||
let transferred_usage = transferred_usage.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
transferred_usage.model_requests,
|
||||
existing_usage.model_requests
|
||||
);
|
||||
assert_eq!(
|
||||
transferred_usage.edit_predictions,
|
||||
existing_usage.edit_predictions
|
||||
);
|
||||
}
|
||||
|
||||
// Test when an existing Zed Pro trial subscription is canceled.
|
||||
{
|
||||
let user_id = UserId(2);
|
||||
|
||||
let now = Utc::now();
|
||||
|
||||
let trial_period_start_at = now - Duration::days(14);
|
||||
let trial_period_end_at = now;
|
||||
|
||||
let existing_subscription = billing_subscription::Model {
|
||||
kind: Some(SubscriptionKind::ZedProTrial),
|
||||
stripe_current_period_start: Some(trial_period_start_at.timestamp()),
|
||||
stripe_current_period_end: Some(trial_period_end_at.timestamp()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let _existing_usage = db
|
||||
.create_subscription_usage(
|
||||
user_id,
|
||||
trial_period_start_at,
|
||||
trial_period_end_at,
|
||||
SubscriptionKind::ZedProTrial,
|
||||
25,
|
||||
1_000,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let transferred_usage = db
|
||||
.transfer_existing_subscription_usage(
|
||||
user_id,
|
||||
&existing_subscription,
|
||||
Some(SubscriptionKind::ZedPro),
|
||||
StripeSubscriptionStatus::Canceled,
|
||||
trial_period_start_at,
|
||||
trial_period_end_at,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
transferred_usage.is_none(),
|
||||
"subscription usage was transferred when it should not have been"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -307,7 +307,6 @@ impl TestServer {
|
||||
);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
assistant_context_editor::init(client.clone(), cx);
|
||||
assistant_settings::init(cx);
|
||||
});
|
||||
|
||||
client
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::persistence::DebuggerPaneItem;
|
||||
use crate::session::DebugSession;
|
||||
use crate::{
|
||||
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
|
||||
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart, StepBack,
|
||||
StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence,
|
||||
ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, FocusBreakpointList,
|
||||
FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,
|
||||
Pause, Restart, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
|
||||
persistence,
|
||||
};
|
||||
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||
use anyhow::Result;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use dap::adapters::DebugAdapterName;
|
||||
@@ -109,7 +110,7 @@ impl DebugPanel {
|
||||
|
||||
let filter = CommandPaletteFilter::global_mut(cx);
|
||||
let debugger_action_types = [
|
||||
TypeId::of::<Detach>(),
|
||||
TypeId::of::<Disconnect>(),
|
||||
TypeId::of::<Stop>(),
|
||||
TypeId::of::<ToggleIgnoreBreakpoints>(),
|
||||
];
|
||||
@@ -608,19 +609,52 @@ impl DebugPanel {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
|
||||
let div = if is_side { v_flex() } else { h_flex() };
|
||||
let weak_panel = cx.weak_entity();
|
||||
|
||||
let new_session_button = || {
|
||||
IconButton::new("debug-new-session", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
move |_, window, cx| window.dispatch_action(crate::Start.boxed_clone(), cx)
|
||||
let workspace = self.workspace.clone();
|
||||
let weak_panel = weak_panel.clone();
|
||||
let past_debug_definition = self.past_debug_definition.clone();
|
||||
move |_, window, cx| {
|
||||
let weak_panel = weak_panel.clone();
|
||||
let past_debug_definition = past_debug_definition.clone();
|
||||
let workspace = workspace.clone();
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let task_contexts = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
tasks_ui::task_contexts(workspace, window, cx)
|
||||
})?
|
||||
.await;
|
||||
|
||||
workspace.update_in(cx, |this, window, cx| {
|
||||
this.toggle_modal(window, cx, |window, cx| {
|
||||
NewSessionModal::new(
|
||||
past_debug_definition,
|
||||
weak_panel,
|
||||
workspace.clone(),
|
||||
None,
|
||||
task_contexts,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})?;
|
||||
|
||||
Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Start Debug Session",
|
||||
&crate::Start,
|
||||
"New Debug Session",
|
||||
&CreateDebuggingSession,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -886,28 +920,6 @@ impl DebugPanel {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-disconnect", IconName::DebugDetach)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _, cx| {
|
||||
this.detach_client(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Detach",
|
||||
&Detach,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -1258,7 +1270,10 @@ impl Render for DebugPanel {
|
||||
Button::new("spawn-new-session-empty-state", "New Session")
|
||||
.size(ButtonSize::Large)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(crate::Start.boxed_clone(), cx);
|
||||
window.dispatch_action(
|
||||
CreateDebuggingSession.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -24,7 +24,7 @@ actions!(
|
||||
[
|
||||
Start,
|
||||
Continue,
|
||||
Detach,
|
||||
Disconnect,
|
||||
Pause,
|
||||
Restart,
|
||||
StepInto,
|
||||
@@ -34,6 +34,7 @@ actions!(
|
||||
Stop,
|
||||
ToggleIgnoreBreakpoints,
|
||||
ClearAllBreakpoints,
|
||||
CreateDebuggingSession,
|
||||
FocusConsole,
|
||||
FocusVariables,
|
||||
FocusBreakpointList,
|
||||
@@ -146,6 +147,38 @@ pub fn init(cx: &mut App) {
|
||||
})
|
||||
},
|
||||
)
|
||||
.register_action(
|
||||
|workspace: &mut Workspace, _: &CreateDebuggingSession, window, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
let weak_panel = debug_panel.downgrade();
|
||||
let weak_workspace = cx.weak_entity();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let task_contexts = this
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
tasks_ui::task_contexts(workspace, window, cx)
|
||||
})?
|
||||
.await;
|
||||
this.update_in(cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
NewSessionModal::new(
|
||||
debug_panel.read(cx).past_debug_definition.clone(),
|
||||
weak_panel,
|
||||
weak_workspace,
|
||||
None,
|
||||
task_contexts,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})?;
|
||||
|
||||
Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
},
|
||||
)
|
||||
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
|
||||
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
|
||||
let weak_panel = debug_panel.downgrade();
|
||||
|
||||
@@ -629,7 +629,9 @@ impl Render for NewSessionModal {
|
||||
),
|
||||
)
|
||||
.justify_between()
|
||||
.children(self.adapter_drop_down_menu(window, cx))
|
||||
.when(!matches!(self.mode, NewSessionMode::Scenario(_)), |this| {
|
||||
this.children(self.adapter_drop_down_menu(window, cx))
|
||||
})
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.border_b_1(),
|
||||
)
|
||||
@@ -642,15 +644,7 @@ impl Render for NewSessionModal {
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.border_t_1()
|
||||
.w_full()
|
||||
.child(
|
||||
matches!(self.mode, NewSessionMode::Scenario(_))
|
||||
.not()
|
||||
.then(|| {
|
||||
self.debug_config_drop_down_menu(window, cx)
|
||||
.into_any_element()
|
||||
})
|
||||
.unwrap_or_else(|| v_flex().w_full().into_any_element()),
|
||||
)
|
||||
.child(self.debug_config_drop_down_menu(window, cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
|
||||
@@ -103,14 +103,14 @@ impl DebugSession {
|
||||
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
|
||||
let label = self.label(cx);
|
||||
|
||||
let is_terminated = self
|
||||
.running_state
|
||||
.read(cx)
|
||||
.session()
|
||||
.read(cx)
|
||||
.is_terminated();
|
||||
let icon = {
|
||||
if is_terminated {
|
||||
if self
|
||||
.running_state
|
||||
.read(cx)
|
||||
.session()
|
||||
.read(cx)
|
||||
.is_terminated()
|
||||
{
|
||||
Some(Indicator::dot().color(Color::Error))
|
||||
} else {
|
||||
match self
|
||||
@@ -131,7 +131,7 @@ impl DebugSession {
|
||||
.gap_2()
|
||||
.when_some(icon, |this, indicator| this.child(indicator))
|
||||
.justify_between()
|
||||
.child(Label::new(label).when(is_terminated, |this| this.strikethrough()))
|
||||
.child(Label::new(label))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,9 @@ use task::{
|
||||
use terminal_view::TerminalView;
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
|
||||
Disableable, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement,
|
||||
IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div,
|
||||
h_flex, v_flex,
|
||||
DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement,
|
||||
Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement,
|
||||
Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use variable_list::VariableList;
|
||||
@@ -1421,7 +1420,11 @@ impl RunningState {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn detach_client(&self, cx: &mut Context<Self>) {
|
||||
#[expect(
|
||||
unused,
|
||||
reason = "Support for disconnecting a client is not wired through yet"
|
||||
)]
|
||||
pub fn disconnect_client(&self, cx: &mut Context<Self>) {
|
||||
self.session().update(cx, |state, cx| {
|
||||
state.disconnect_client(cx);
|
||||
});
|
||||
@@ -1439,7 +1442,6 @@ impl RunningState {
|
||||
cx: &mut Context<'_, RunningState>,
|
||||
) -> DropdownMenu {
|
||||
let state = cx.entity();
|
||||
let session_terminated = self.session.read(cx).is_terminated();
|
||||
let threads = self.session.update(cx, |this, cx| this.threads(cx));
|
||||
let selected_thread_name = threads
|
||||
.iter()
|
||||
@@ -1462,7 +1464,6 @@ impl RunningState {
|
||||
this
|
||||
}),
|
||||
)
|
||||
.disabled(session_terminated)
|
||||
}
|
||||
|
||||
fn default_pane_layout(
|
||||
|
||||
@@ -20,7 +20,7 @@ path = "src/explorer.rs"
|
||||
[dependencies]
|
||||
agent.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
agent_settings.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
assistant_tools.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
||||
@@ -11,8 +11,8 @@ use crate::{
|
||||
assertions::{AssertionsReport, RanAssertion, RanAssertionResult},
|
||||
};
|
||||
use agent::{ContextLoadResult, Thread, ThreadEvent};
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::HashMap;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion, LanguageServer};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
use markdown::PathWithRange;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion};
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use assistant_tools::StreamingEditFileToolInput;
|
||||
use async_trait::async_trait;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use assistant_tools::FindPathToolInput;
|
||||
use async_trait::async_trait;
|
||||
use regex::Regex;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use agent_settings::AgentProfileId;
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use assistant_tool::Tool;
|
||||
use assistant_tools::{OpenTool, TerminalTool};
|
||||
use async_trait::async_trait;
|
||||
|
||||
@@ -56,6 +56,41 @@ pub trait FeatureFlag {
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls the values of various feature flags for the Agent launch.
|
||||
///
|
||||
/// Change this to `true` when we're ready to build the release candidate.
|
||||
const AGENT_LAUNCH: bool = false;
|
||||
|
||||
pub struct Assistant2FeatureFlag;
|
||||
|
||||
impl FeatureFlag for Assistant2FeatureFlag {
|
||||
const NAME: &'static str = "assistant2";
|
||||
|
||||
fn enabled_for_all() -> bool {
|
||||
AGENT_LAUNCH
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AgentStreamEditsFeatureFlag;
|
||||
|
||||
impl FeatureFlag for AgentStreamEditsFeatureFlag {
|
||||
const NAME: &'static str = "agent-stream-edits";
|
||||
|
||||
fn enabled_for_all() -> bool {
|
||||
AGENT_LAUNCH
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NewBillingFeatureFlag;
|
||||
|
||||
impl FeatureFlag for NewBillingFeatureFlag {
|
||||
const NAME: &'static str = "new-billing";
|
||||
|
||||
fn enabled_for_all() -> bool {
|
||||
AGENT_LAUNCH
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PredictEditsRateCompletionsFeatureFlag;
|
||||
impl FeatureFlag for PredictEditsRateCompletionsFeatureFlag {
|
||||
const NAME: &'static str = "predict-edits-rate-completions";
|
||||
|
||||
@@ -19,7 +19,7 @@ test-support = ["multi_buffer/test-support"]
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
askpass.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
agent_settings.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
|
||||
@@ -9,9 +9,9 @@ use crate::{branch_picker, picker_prompt, render_remote_button};
|
||||
use crate::{
|
||||
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::Result;
|
||||
use askpass::AskPassDelegate;
|
||||
use assistant_settings::AssistantSettings;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
|
||||
use editor::{
|
||||
@@ -481,10 +481,10 @@ impl GitPanel {
|
||||
hide_task: None,
|
||||
};
|
||||
|
||||
let mut assistant_enabled = AssistantSettings::get_global(cx).enabled;
|
||||
let mut assistant_enabled = AgentSettings::get_global(cx).enabled;
|
||||
let _settings_subscription = cx.observe_global::<SettingsStore>(move |_, cx| {
|
||||
if assistant_enabled != AssistantSettings::get_global(cx).enabled {
|
||||
assistant_enabled = AssistantSettings::get_global(cx).enabled;
|
||||
if assistant_enabled != AgentSettings::get_global(cx).enabled {
|
||||
assistant_enabled = AgentSettings::get_global(cx).enabled;
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
@@ -1735,8 +1735,6 @@ impl GitPanel {
|
||||
}
|
||||
});
|
||||
|
||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
||||
|
||||
self.generate_commit_message_task = Some(cx.spawn(async move |this, cx| {
|
||||
async move {
|
||||
let _defer = cx.on_drop(&this, |this, _cx| {
|
||||
@@ -1775,7 +1773,7 @@ impl GitPanel {
|
||||
}],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
temperature: None,
|
||||
};
|
||||
|
||||
let stream = model.stream_completion_text(request, &cx);
|
||||
@@ -4055,7 +4053,7 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
fn current_language_model(cx: &Context<'_, GitPanel>) -> Option<Arc<dyn LanguageModel>> {
|
||||
assistant_settings::AssistantSettings::get_global(cx)
|
||||
agent_settings::AgentSettings::get_global(cx)
|
||||
.enabled
|
||||
.then(|| {
|
||||
let ConfiguredModel { provider, model } =
|
||||
@@ -4780,7 +4778,7 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
AssistantSettings::register(cx);
|
||||
AgentSettings::register(cx);
|
||||
WorktreeSettings::register(cx);
|
||||
workspace::init_settings(cx);
|
||||
theme::init(LoadThemes::JustBase, cx);
|
||||
|
||||
@@ -10,7 +10,7 @@ use git::{
|
||||
status::{FileStatus, StatusCode, UnmergedStatus, UnmergedStatusCode},
|
||||
};
|
||||
use git_panel_settings::GitPanelSettings;
|
||||
use gpui::{Action, App, FocusHandle, actions};
|
||||
use gpui::{App, FocusHandle, actions};
|
||||
use onboarding::GitOnboardingModal;
|
||||
use project_diff::ProjectDiff;
|
||||
use ui::prelude::*;
|
||||
@@ -115,7 +115,7 @@ pub fn init(cx: &mut App) {
|
||||
},
|
||||
);
|
||||
workspace.register_action(move |_, _: &ResetOnboarding, window, cx| {
|
||||
window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
|
||||
cx.dispatch_action(&workspace::RestoreBanner);
|
||||
window.refresh();
|
||||
});
|
||||
workspace.register_action(|workspace, _action: &git::Init, window, cx| {
|
||||
|
||||
@@ -81,7 +81,7 @@ pub enum IconName {
|
||||
DebugContinue,
|
||||
DebugDisabledBreakpoint,
|
||||
DebugDisabledLogBreakpoint,
|
||||
DebugDetach,
|
||||
DebugDisconnect,
|
||||
DebugIgnoreBreakpoints,
|
||||
DebugLogBreakpoint,
|
||||
DebugPause,
|
||||
|
||||
@@ -131,7 +131,6 @@ pub struct LanguageModelToolResult {
|
||||
pub tool_name: Arc<str>,
|
||||
pub is_error: bool,
|
||||
pub content: Arc<str>,
|
||||
pub output: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::{HashSet, IndexMap};
|
||||
use feature_flags::ZedProFeatureFlag;
|
||||
use feature_flags::{Assistant2FeatureFlag, ZedProFeatureFlag};
|
||||
use gpui::{
|
||||
Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, Subscription, Task, WeakEntity, action_with_deprecated_aliases,
|
||||
@@ -597,10 +597,13 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::agent::OpenConfiguration.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
let configure_action = if cx.has_flag::<Assistant2FeatureFlag>() {
|
||||
zed_actions::agent::OpenConfiguration.boxed_clone()
|
||||
} else {
|
||||
zed_actions::assistant::ShowConfiguration.boxed_clone()
|
||||
};
|
||||
|
||||
window.dispatch_action(configure_action, cx);
|
||||
}),
|
||||
)
|
||||
.into_any(),
|
||||
|
||||
@@ -87,8 +87,8 @@ pub struct AllLanguageModelSettingsContent {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum AnthropicSettingsContent {
|
||||
Versioned(VersionedAnthropicSettingsContent),
|
||||
Legacy(LegacyAnthropicSettingsContent),
|
||||
Versioned(VersionedAnthropicSettingsContent),
|
||||
}
|
||||
|
||||
impl AnthropicSettingsContent {
|
||||
@@ -197,8 +197,8 @@ pub struct MistralSettingsContent {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum OpenAiSettingsContent {
|
||||
Versioned(VersionedOpenAiSettingsContent),
|
||||
Legacy(LegacyOpenAiSettingsContent),
|
||||
Versioned(VersionedOpenAiSettingsContent),
|
||||
}
|
||||
|
||||
impl OpenAiSettingsContent {
|
||||
|
||||
@@ -3530,7 +3530,7 @@ impl LspStore {
|
||||
)
|
||||
.detach();
|
||||
} else {
|
||||
log::debug!("No extension events global found. Skipping JSON schema auto-reload setup");
|
||||
log::info!("No extension events global found. Skipping JSON schema auto-reload setup");
|
||||
}
|
||||
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
|
||||
.detach();
|
||||
|
||||
@@ -605,7 +605,7 @@ pub struct TerminalContent {
|
||||
pub scrolled_to_bottom: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub struct HoveredWord {
|
||||
pub word: String,
|
||||
pub word_match: RangeInclusive<AlacPoint>,
|
||||
|
||||
@@ -730,38 +730,29 @@ impl Element for TerminalElement {
|
||||
|
||||
let background_color = theme.colors().terminal_background;
|
||||
|
||||
let (last_hovered_word, hover_tooltip) =
|
||||
self.terminal.update(cx, |terminal, cx| {
|
||||
terminal.set_size(dimensions);
|
||||
terminal.sync(window, cx);
|
||||
let (last_hovered_word, hover_target) = self.terminal.update(cx, |terminal, cx| {
|
||||
terminal.set_size(dimensions);
|
||||
terminal.sync(window, cx);
|
||||
|
||||
if window.modifiers().secondary()
|
||||
&& bounds.contains(&window.mouse_position())
|
||||
&& self.terminal_view.read(cx).hover.is_some()
|
||||
{
|
||||
let registered_hover = self.terminal_view.read(cx).hover.as_ref();
|
||||
if terminal.last_content.last_hovered_word.as_ref()
|
||||
== registered_hover.map(|hover| &hover.hovered_word)
|
||||
{
|
||||
(
|
||||
terminal.last_content.last_hovered_word.clone(),
|
||||
registered_hover.map(|hover| hover.tooltip.clone()),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
});
|
||||
if window.modifiers().secondary()
|
||||
&& bounds.contains(&window.mouse_position())
|
||||
&& self.terminal_view.read(cx).hover_target_tooltip.is_some()
|
||||
{
|
||||
let hover_target = self.terminal_view.read(cx).hover_target_tooltip.clone();
|
||||
let last_hovered_word = terminal.last_content.last_hovered_word.clone();
|
||||
(last_hovered_word, hover_target)
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
});
|
||||
|
||||
let scroll_top = self.terminal_view.read(cx).scroll_top;
|
||||
let hyperlink_tooltip = hover_tooltip.map(|hover_tooltip| {
|
||||
let hyperlink_tooltip = hover_target.as_ref().map(|hover_target| {
|
||||
let offset = bounds.origin + point(gutter, px(0.)) - point(px(0.), scroll_top);
|
||||
let mut element = div()
|
||||
.size_full()
|
||||
.id("terminal-element")
|
||||
.tooltip(Tooltip::text(hover_tooltip))
|
||||
.tooltip(Tooltip::text(hover_target.clone()))
|
||||
.into_any_element();
|
||||
element.prepaint_as_root(offset, bounds.size.into(), window, cx);
|
||||
element
|
||||
@@ -931,7 +922,7 @@ impl Element for TerminalElement {
|
||||
self.register_mouse_listeners(layout.mode, &layout.hitbox, window);
|
||||
if window.modifiers().secondary()
|
||||
&& bounds.contains(&window.mouse_position())
|
||||
&& self.terminal_view.read(cx).hover.is_some()
|
||||
&& self.terminal_view.read(cx).hover_target_tooltip.is_some()
|
||||
{
|
||||
window.set_cursor_style(gpui::CursorStyle::PointingHand, Some(&layout.hitbox));
|
||||
} else {
|
||||
|
||||
@@ -15,9 +15,9 @@ use persistence::TERMINAL_DB;
|
||||
use project::{Entry, Metadata, Project, search::SearchQuery, terminals::TerminalKind};
|
||||
use schemars::JsonSchema;
|
||||
use terminal::{
|
||||
Clear, Copy, Event, HoveredWord, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp,
|
||||
ScrollPageDown, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskState,
|
||||
TaskStatus, Terminal, TerminalBounds, ToggleViMode,
|
||||
Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown,
|
||||
ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskState, TaskStatus,
|
||||
Terminal, TerminalBounds, ToggleViMode,
|
||||
alacritty_terminal::{
|
||||
index::Point,
|
||||
term::{TermMode, search::RegexSearch},
|
||||
@@ -112,7 +112,7 @@ pub struct TerminalView {
|
||||
cwd_serialized: bool,
|
||||
blinking_paused: bool,
|
||||
blink_epoch: usize,
|
||||
hover: Option<HoverTarget>,
|
||||
hover_target_tooltip: Option<String>,
|
||||
hover_tooltip_update: Task<()>,
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
show_breadcrumbs: bool,
|
||||
@@ -126,12 +126,6 @@ pub struct TerminalView {
|
||||
_terminal_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HoverTarget {
|
||||
tooltip: String,
|
||||
hovered_word: HoveredWord,
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for TerminalView {}
|
||||
impl EventEmitter<ItemEvent> for TerminalView {}
|
||||
impl EventEmitter<SearchEvent> for TerminalView {}
|
||||
@@ -202,7 +196,7 @@ impl TerminalView {
|
||||
blinking_terminal_enabled: false,
|
||||
blinking_paused: false,
|
||||
blink_epoch: 0,
|
||||
hover: None,
|
||||
hover_target_tooltip: None,
|
||||
hover_tooltip_update: Task::ready(()),
|
||||
embedded,
|
||||
workspace_id,
|
||||
@@ -887,79 +881,54 @@ fn subscribe_for_terminal_events(
|
||||
}
|
||||
|
||||
Event::NewNavigationTarget(maybe_navigation_target) => {
|
||||
match maybe_navigation_target
|
||||
.as_ref()
|
||||
.zip(terminal.read(cx).last_content.last_hovered_word.as_ref())
|
||||
{
|
||||
Some((MaybeNavigationTarget::Url(url), hovered_word)) => {
|
||||
if Some(hovered_word)
|
||||
!= terminal_view
|
||||
.hover
|
||||
.as_ref()
|
||||
.map(|hover| &hover.hovered_word)
|
||||
{
|
||||
terminal_view.hover = Some(HoverTarget {
|
||||
tooltip: url.clone(),
|
||||
hovered_word: hovered_word.clone(),
|
||||
});
|
||||
terminal_view.hover_tooltip_update = Task::ready(());
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
Some((MaybeNavigationTarget::PathLike(path_like_target), hovered_word)) => {
|
||||
if Some(hovered_word)
|
||||
!= terminal_view
|
||||
.hover
|
||||
.as_ref()
|
||||
.map(|hover| &hover.hovered_word)
|
||||
{
|
||||
let valid_files_to_open_task = possible_open_target(
|
||||
&workspace,
|
||||
&path_like_target.terminal_dir,
|
||||
&path_like_target.maybe_path,
|
||||
cx,
|
||||
);
|
||||
let hovered_word = hovered_word.clone();
|
||||
|
||||
terminal_view.hover = None;
|
||||
terminal_view.hover_tooltip_update =
|
||||
cx.spawn(async move |terminal_view, cx| {
|
||||
let file_to_open = valid_files_to_open_task.await;
|
||||
terminal_view
|
||||
.update(cx, |terminal_view, _| match file_to_open {
|
||||
Some(
|
||||
OpenTarget::File(path, _)
|
||||
| OpenTarget::Worktree(path, _),
|
||||
) => {
|
||||
terminal_view.hover = Some(HoverTarget {
|
||||
tooltip: path.to_string(|path| {
|
||||
path.to_string_lossy().to_string()
|
||||
}),
|
||||
hovered_word,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
terminal_view.hover = None;
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
match maybe_navigation_target.as_ref() {
|
||||
None => {
|
||||
terminal_view.hover = None;
|
||||
terminal_view.hover_target_tooltip = None;
|
||||
terminal_view.hover_tooltip_update = Task::ready(());
|
||||
cx.notify();
|
||||
}
|
||||
Some(MaybeNavigationTarget::Url(url)) => {
|
||||
terminal_view.hover_target_tooltip = Some(url.clone());
|
||||
terminal_view.hover_tooltip_update = Task::ready(());
|
||||
}
|
||||
Some(MaybeNavigationTarget::PathLike(path_like_target)) => {
|
||||
let valid_files_to_open_task = possible_open_target(
|
||||
&workspace,
|
||||
&path_like_target.terminal_dir,
|
||||
&path_like_target.maybe_path,
|
||||
cx,
|
||||
);
|
||||
|
||||
terminal_view.hover_tooltip_update =
|
||||
cx.spawn(async move |terminal_view, cx| {
|
||||
let file_to_open = valid_files_to_open_task.await;
|
||||
terminal_view
|
||||
.update(cx, |terminal_view, _| match file_to_open {
|
||||
Some(
|
||||
OpenTarget::File(path, _)
|
||||
| OpenTarget::Worktree(path, _),
|
||||
) => {
|
||||
terminal_view.hover_target_tooltip =
|
||||
Some(path.to_string(|path| {
|
||||
path.to_string_lossy().to_string()
|
||||
}));
|
||||
}
|
||||
None => {
|
||||
terminal_view.hover_target_tooltip = None;
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
Event::Open(maybe_navigation_target) => match maybe_navigation_target {
|
||||
MaybeNavigationTarget::Url(url) => cx.open_url(url),
|
||||
|
||||
MaybeNavigationTarget::PathLike(path_like_target) => {
|
||||
if terminal_view.hover.is_none() {
|
||||
if terminal_view.hover_target_tooltip.is_none() {
|
||||
return;
|
||||
}
|
||||
let task_workspace = workspace.clone();
|
||||
|
||||
@@ -32,6 +32,7 @@ call.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
feature_flags.workspace = true
|
||||
gpui.workspace = true
|
||||
notifications.workspace = true
|
||||
project.workspace = true
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use call::ActiveCall;
|
||||
use client::{Client, UserStore};
|
||||
use feature_flags::{FeatureFlagAppExt, NewBillingFeatureFlag};
|
||||
use gpui::{
|
||||
Action, AnyElement, App, Context, Corner, Decorations, Element, Entity, InteractiveElement,
|
||||
Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
|
||||
@@ -315,11 +316,11 @@ impl TitleBar {
|
||||
|
||||
let banner = cx.new(|cx| {
|
||||
OnboardingBanner::new(
|
||||
"Agentic Onboarding",
|
||||
IconName::ZedAssistant,
|
||||
"Agentic Editing",
|
||||
"Git Onboarding",
|
||||
IconName::GitBranchSmall,
|
||||
"Git Support",
|
||||
None,
|
||||
zed_actions::agent::OpenOnboardingModal.boxed_clone(),
|
||||
zed_actions::OpenGitIntegrationOnboarding.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -677,20 +678,22 @@ impl TitleBar {
|
||||
PopoverMenu::new("user-menu")
|
||||
.anchor(Corner::TopRight)
|
||||
.menu(move |window, cx| {
|
||||
ContextMenu::build(window, cx, |menu, _, _cx| {
|
||||
menu.action(
|
||||
format!(
|
||||
"Current Plan: {}",
|
||||
match plan {
|
||||
None => "",
|
||||
Some(proto::Plan::Free) => "Free",
|
||||
Some(proto::Plan::ZedPro) => "Pro",
|
||||
Some(proto::Plan::ZedProTrial) => "Pro (Trial)",
|
||||
}
|
||||
),
|
||||
zed_actions::OpenAccountSettings.boxed_clone(),
|
||||
)
|
||||
.separator()
|
||||
ContextMenu::build(window, cx, |menu, _, cx| {
|
||||
menu.when(cx.has_flag::<NewBillingFeatureFlag>(), |menu| {
|
||||
menu.action(
|
||||
format!(
|
||||
"Current Plan: {}",
|
||||
match plan {
|
||||
None => "",
|
||||
Some(proto::Plan::Free) => "Free",
|
||||
Some(proto::Plan::ZedPro) => "Pro",
|
||||
Some(proto::Plan::ZedProTrial) => "Pro (Trial)",
|
||||
}
|
||||
),
|
||||
zed_actions::OpenAccountSettings.boxed_clone(),
|
||||
)
|
||||
.separator()
|
||||
})
|
||||
.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
|
||||
.action(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user