Compare commits
119 Commits
v0.192.7
...
vim_contex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5e93c08d1 | ||
|
|
fc1fc264ec | ||
|
|
800b925fd7 | ||
|
|
95cf153ad7 | ||
|
|
7be57baef0 | ||
|
|
39dc4b9040 | ||
|
|
deb2564b31 | ||
|
|
13f134448d | ||
|
|
94735aef69 | ||
|
|
0d70bcb88c | ||
|
|
360d73c763 | ||
|
|
668d5eef3b | ||
|
|
4cd4d28531 | ||
|
|
786e724684 | ||
|
|
24c94d474e | ||
|
|
21f985a018 | ||
|
|
2283ec5de2 | ||
|
|
371b7355d3 | ||
|
|
95f10fd187 | ||
|
|
324cbecb74 | ||
|
|
32df9256c3 | ||
|
|
36eebb7ba8 | ||
|
|
d34d4f2ef1 | ||
|
|
c610ebfb03 | ||
|
|
aabfea4c10 | ||
|
|
51059b6f50 | ||
|
|
6b0325b059 | ||
|
|
c9bd409732 | ||
|
|
a067c16c82 | ||
|
|
1a6c1b2159 | ||
|
|
8718019b52 | ||
|
|
a32505fcc2 | ||
|
|
980917bb7c | ||
|
|
bd8471bb40 | ||
|
|
16f1da1b7e | ||
|
|
272fc672af | ||
|
|
e68b95c61b | ||
|
|
28f56ad7ae | ||
|
|
7f44e4b89c | ||
|
|
e5ad2c2518 | ||
|
|
67ac80bd19 | ||
|
|
ac30a8b0df | ||
|
|
6b4c607331 | ||
|
|
595f61f0d6 | ||
|
|
1047d8adec | ||
|
|
21fd5c24bf | ||
|
|
336d2c41fa | ||
|
|
5244085bd0 | ||
|
|
af8f26dd34 | ||
|
|
3b9f504d75 | ||
|
|
0579bf73b0 | ||
|
|
534475d7aa | ||
|
|
8d05f6fdd3 | ||
|
|
3cb4342a76 | ||
|
|
6fb1081b61 | ||
|
|
0ed6b4ef1a | ||
|
|
d75e210e73 | ||
|
|
2b3e453d2f | ||
|
|
cb50f07d23 | ||
|
|
a713c66a9d | ||
|
|
76e3136369 | ||
|
|
834cdc1271 | ||
|
|
dfdd2b9558 | ||
|
|
b9838efaaa | ||
|
|
db8acfa9ff | ||
|
|
5c1dc8b2cc | ||
|
|
7e801dccb0 | ||
|
|
e0c0b6f95d | ||
|
|
ad76db7244 | ||
|
|
9f2c541ab0 | ||
|
|
9597c73f2b | ||
|
|
7812985d3c | ||
|
|
b0f192ec2e | ||
|
|
c9e5ff21a1 | ||
|
|
c02e249ecb | ||
|
|
a7bbbc0552 | ||
|
|
f8a0eb5a8c | ||
|
|
d97a58b20e | ||
|
|
10f0aabec6 | ||
|
|
2624950472 | ||
|
|
ca3f1d624a | ||
|
|
00fe195416 | ||
|
|
bca1a9145c | ||
|
|
b89ddf3a6e | ||
|
|
5c7e90d494 | ||
|
|
61abfd5930 | ||
|
|
ddaa8b3d02 | ||
|
|
3b31db1b1f | ||
|
|
1f736ed693 | ||
|
|
0b228ad12c | ||
|
|
e914d84f00 | ||
|
|
ec0f2fa79a | ||
|
|
1bd49a77e0 | ||
|
|
c8d49408d3 | ||
|
|
e202981f0c | ||
|
|
dec7baeb97 | ||
|
|
2839c2e492 | ||
|
|
0e94ca2a1a | ||
|
|
804b91aa8c | ||
|
|
526faf287d | ||
|
|
6e64628858 | ||
|
|
522d92fbe2 | ||
|
|
cec19aec7b | ||
|
|
c34b24b5fb | ||
|
|
05f944b83a | ||
|
|
629bd42276 | ||
|
|
ab189b898d | ||
|
|
d2ca68bd5d | ||
|
|
72a3292f8d | ||
|
|
ccb4644365 | ||
|
|
fdd307cf7a | ||
|
|
c1d0d72db9 | ||
|
|
db99d7131e | ||
|
|
99215f7660 | ||
|
|
73fee01c85 | ||
|
|
74aa227c09 | ||
|
|
d0e909e58d | ||
|
|
aa9dacad28 | ||
|
|
48491fa487 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@
|
||||
.flatpak-builder
|
||||
.idea
|
||||
.netrc
|
||||
*.pyc
|
||||
.pytest_cache
|
||||
.swiftpm
|
||||
.swiftpm/config/registries.json
|
||||
|
||||
4
.rules
4
.rules
@@ -100,9 +100,7 @@ Often event handlers will want to update the entity that's in the current `Conte
|
||||
|
||||
Actions are dispatched via user keyboard interaction or in code via `window.dispatch_action(SomeAction.boxed_clone(), cx)` or `focus_handle.dispatch_action(&SomeAction, window, cx)`.
|
||||
|
||||
Actions which have no data inside are created and registered with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call.
|
||||
|
||||
Actions that do have data must implement `Clone, Default, PartialEq, Deserialize, JsonSchema` and can be registered with an `impl_actions!(some_namespace, [SomeActionWithData])` macro call.
|
||||
Actions with no data defined with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call. Otherwise the `Action` derive macro is used. Doc comments on actions are displayed to the user.
|
||||
|
||||
Action handlers can be registered on an element via the event handler `.on_action(|action, window, cx| ...)`. Like other event handlers, this is often used with `cx.listener`.
|
||||
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
{
|
||||
"label": "Debug Zed (CodeLLDB)",
|
||||
"adapter": "CodeLLDB",
|
||||
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
|
||||
"build": {
|
||||
"label": "Build Zed",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Debug Zed (GDB)",
|
||||
"adapter": "GDB",
|
||||
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
|
||||
}
|
||||
"build": {
|
||||
"label": "Build Zed",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
"ensure_final_newline_on_save": true,
|
||||
"file_scan_exclusions": [
|
||||
"crates/assistant_tools/src/evals/fixtures",
|
||||
"crates/assistant_tools/src/edit_agent/evals/fixtures",
|
||||
"crates/eval/worktrees/",
|
||||
"crates/eval/repos/",
|
||||
"**/.git",
|
||||
|
||||
162
Cargo.lock
generated
162
Cargo.lock
generated
@@ -55,49 +55,29 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_context_editor",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"assistant_context",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"audio",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
"client",
|
||||
"collections",
|
||||
"component",
|
||||
"context_server",
|
||||
"convert_case 0.8.0",
|
||||
"db",
|
||||
"editor",
|
||||
"extension",
|
||||
"feature_flags",
|
||||
"file_icons",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"git",
|
||||
"gpui",
|
||||
"heed",
|
||||
"html_to_markdown",
|
||||
"http_client",
|
||||
"indexed_docs",
|
||||
"icons",
|
||||
"indoc",
|
||||
"inventory",
|
||||
"itertools 0.14.0",
|
||||
"jsonschema",
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
"menu",
|
||||
"multi_buffer",
|
||||
"notifications",
|
||||
"ordered-float 2.10.1",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"picker",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
@@ -105,36 +85,22 @@ dependencies = [
|
||||
"proto",
|
||||
"rand 0.8.5",
|
||||
"ref-cast",
|
||||
"release_channel",
|
||||
"rope",
|
||||
"rules_library",
|
||||
"schemars",
|
||||
"search",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"smol",
|
||||
"sqlez",
|
||||
"streaming_diff",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"text",
|
||||
"theme",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"time_format",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"urlencoding",
|
||||
"util",
|
||||
"uuid",
|
||||
"watch",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zed_actions",
|
||||
"zed_llm_client",
|
||||
"zstd",
|
||||
]
|
||||
@@ -165,6 +131,92 @@ dependencies = [
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent",
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_context",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"audio",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
"client",
|
||||
"collections",
|
||||
"component",
|
||||
"context_server",
|
||||
"db",
|
||||
"editor",
|
||||
"extension",
|
||||
"extension_host",
|
||||
"feature_flags",
|
||||
"file_icons",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"html_to_markdown",
|
||||
"http_client",
|
||||
"indexed_docs",
|
||||
"indoc",
|
||||
"inventory",
|
||||
"itertools 0.14.0",
|
||||
"jsonschema",
|
||||
"language",
|
||||
"language_model",
|
||||
"languages",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
"menu",
|
||||
"multi_buffer",
|
||||
"notifications",
|
||||
"ordered-float 2.10.1",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"proto",
|
||||
"rand 0.8.5",
|
||||
"release_channel",
|
||||
"rope",
|
||||
"rules_library",
|
||||
"schemars",
|
||||
"search",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"smol",
|
||||
"streaming_diff",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"text",
|
||||
"theme",
|
||||
"time",
|
||||
"time_format",
|
||||
"tree-sitter-md",
|
||||
"ui",
|
||||
"unindent",
|
||||
"urlencoding",
|
||||
"util",
|
||||
"uuid",
|
||||
"watch",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zed_actions",
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -508,7 +560,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_context_editor"
|
||||
name = "assistant_context"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
@@ -520,31 +572,23 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"context_server",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indexed_docs",
|
||||
"indoc",
|
||||
"language",
|
||||
"language_model",
|
||||
"languages",
|
||||
"log",
|
||||
"multi_buffer",
|
||||
"open_ai",
|
||||
"ordered-float 2.10.1",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"proto",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rope",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -553,15 +597,12 @@ dependencies = [
|
||||
"smol",
|
||||
"telemetry_events",
|
||||
"text",
|
||||
"theme",
|
||||
"tree-sitter-md",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"uuid",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zed_actions",
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
@@ -2648,6 +2689,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"language",
|
||||
"log",
|
||||
"postage",
|
||||
"rand 0.8.5",
|
||||
"release_channel",
|
||||
"rpc",
|
||||
@@ -2821,6 +2863,7 @@ dependencies = [
|
||||
"cocoa 0.26.0",
|
||||
"collections",
|
||||
"credentials_provider",
|
||||
"derive_more",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
@@ -2859,6 +2902,7 @@ dependencies = [
|
||||
"windows 0.61.1",
|
||||
"workspace-hack",
|
||||
"worktree",
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2979,7 +3023,7 @@ version = "0.44.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_context_editor",
|
||||
"assistant_context",
|
||||
"assistant_slash_command",
|
||||
"async-stripe",
|
||||
"async-trait",
|
||||
@@ -4117,7 +4161,6 @@ dependencies = [
|
||||
"paths",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"task",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
@@ -4305,6 +4348,7 @@ dependencies = [
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"tree-sitter",
|
||||
"tree-sitter-go",
|
||||
"tree-sitter-json",
|
||||
"ui",
|
||||
"unindent",
|
||||
@@ -5061,6 +5105,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent",
|
||||
"agent_settings",
|
||||
"agent_ui",
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
@@ -8159,12 +8204,11 @@ dependencies = [
|
||||
name = "inline_completion"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"gpui",
|
||||
"language",
|
||||
"project",
|
||||
"workspace-hack",
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11192,9 +11236,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pathfinder_simd"
|
||||
version = "0.5.4"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2"
|
||||
checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
@@ -13610,6 +13654,7 @@ dependencies = [
|
||||
"serde",
|
||||
"settings",
|
||||
"theme",
|
||||
"title_bar",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
@@ -17386,8 +17431,11 @@ name = "vercel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.31",
|
||||
"http_client",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.27.1",
|
||||
"workspace-hack",
|
||||
]
|
||||
@@ -19874,16 +19922,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.192.7"
|
||||
version = "0.193.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"agent",
|
||||
"agent_settings",
|
||||
"agent_ui",
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
"askpass",
|
||||
"assets",
|
||||
"assistant_context_editor",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"audio",
|
||||
@@ -19930,6 +19978,7 @@ dependencies = [
|
||||
"inline_completion_button",
|
||||
"inspector_ui",
|
||||
"install_cli",
|
||||
"itertools 0.14.0",
|
||||
"jj_ui",
|
||||
"journal",
|
||||
"language",
|
||||
@@ -19954,6 +20003,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"profiling",
|
||||
"project",
|
||||
"project_panel",
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -2,12 +2,13 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/agent_ui",
|
||||
"crates/agent",
|
||||
"crates/agent_settings",
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
"crates/assets",
|
||||
"crates/assistant_context_editor",
|
||||
"crates/assistant_context",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_slash_commands",
|
||||
"crates/assistant_tool",
|
||||
@@ -65,6 +66,7 @@ members = [
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui_tokio",
|
||||
|
||||
"crates/html_to_markdown",
|
||||
"crates/http_client",
|
||||
"crates/http_client_tls",
|
||||
@@ -214,12 +216,13 @@ edition = "2024"
|
||||
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
agent = { path = "crates/agent" }
|
||||
agent_ui = { path = "crates/agent_ui" }
|
||||
agent_settings = { path = "crates/agent_settings" }
|
||||
ai = { path = "crates/ai" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
askpass = { path = "crates/askpass" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||
assistant_context = { path = "crates/assistant_context" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||
assistant_tool = { path = "crates/assistant_tool" }
|
||||
@@ -376,6 +379,7 @@ util_macros = { path = "crates/util_macros" }
|
||||
vercel = { path = "crates/vercel" }
|
||||
vim = { path = "crates/vim" }
|
||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||
|
||||
watch = { path = "crates/watch" }
|
||||
web_search = { path = "crates/web_search" }
|
||||
web_search_providers = { path = "crates/web_search_providers" }
|
||||
@@ -455,7 +459,6 @@ futures-batch = "0.6.1"
|
||||
futures-lite = "1.13"
|
||||
git2 = { version = "0.20.1", default-features = false }
|
||||
globset = "0.4"
|
||||
hashbrown = "0.15.3"
|
||||
handlebars = "4.3"
|
||||
heck = "0.5"
|
||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||
@@ -483,7 +486,6 @@ log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "c9c189f1c5dd53c624a419ce35bc77ad6a908d18" }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
metal = "0.29"
|
||||
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
||||
moka = { version = "0.12.10", features = ["sync"] }
|
||||
naga = { version = "25.0", features = ["wgsl-in"] }
|
||||
nanoid = "0.4"
|
||||
@@ -518,7 +520,6 @@ rand = "0.8.5"
|
||||
rayon = "1.8"
|
||||
ref-cast = "1.0.24"
|
||||
regex = "1.5"
|
||||
repair_json = "0.1.0"
|
||||
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c770a32f1998d6e999cef3e59e0013e6c4415", default-features = false, features = [
|
||||
"charset",
|
||||
"http2",
|
||||
@@ -550,7 +551,6 @@ serde_repr = "0.1"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
signal-hook = "0.3.17"
|
||||
simplelog = "0.12.2"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "2.0"
|
||||
|
||||
3
assets/icons/arrow_up_alt.svg
Normal file
3
assets/icons/arrow_up_alt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 3.5L12.5 8M8 3.5L3.5 8M8 3.5V12.5" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 233 B |
3
assets/icons/file_icons/cairo.svg
Normal file
3
assets/icons/file_icons/cairo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.51298 2C5.19374 2 2.5 4.6945 2.5 8.01298C2.5 9.41828 2.99491 10.7205 3.80143 11.7485C4.45214 10.8243 5.15479 9.95214 6.03997 9.23651C6.06594 9.21054 6.13086 9.17159 6.20876 9.11965C6.57307 8.84623 6.8335 8.44297 6.89842 7.98702V7.97403C7.11991 6.50306 7.71869 5.99593 9.3974 5.99593C9.54099 5.99593 9.70978 5.99593 9.86558 6.00891C10.7248 6.04786 11.2189 6.29532 11.2709 6.42515C11.3098 6.49007 11.2968 6.56874 11.2839 6.64664L11.2189 6.63366C10.6851 6.56874 9.87856 6.72454 9.76171 7.31034C9.69679 7.63569 9.77469 8 9.80066 8.32536C9.83961 8.6637 9.86558 9.01502 9.86558 9.35336C9.86558 9.37933 9.83961 9.50916 9.86558 9.52215C8.95443 8.64995 6.84572 9.74364 6.18203 10.2248C6.24695 10.1988 6.31263 10.1729 6.39053 10.1469C7.02826 9.92541 8.94145 9.35336 9.68304 9.99109C10.3078 10.7587 9.74796 12.1777 9.27979 12.8803C9.00636 13.2966 8.66802 13.6739 8.29073 14H8.51222C11.8315 14 14.5252 11.3055 14.5252 7.98702C14.5252 4.66853 11.8452 2 8.51298 2ZM9.20265 5.25356C8.55193 5.25356 8.01808 4.7197 8.01808 4.06899C8.01808 3.41828 8.55193 2.88442 9.20265 2.88442C9.85336 2.88442 10.3872 3.41828 10.3872 4.06899C10.3872 4.7197 9.86634 5.25356 9.20265 5.25356Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
4
assets/icons/zed_mcp_custom.svg
Normal file
4
assets/icons/zed_mcp_custom.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.5 KiB |
4
assets/icons/zed_mcp_extension.svg
Normal file
4
assets/icons/zed_mcp_extension.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M9.62109 4.65039C9.80393 4.65039 9.98678 4.70637 10.1279 4.83203C10.2722 4.96055 10.3496 5.14167 10.3496 5.33691C10.3496 5.53215 10.2723 5.71329 10.1279 5.8418C9.98678 5.96744 9.80392 6.02344 9.62109 6.02344H7.1416V7.24902H9.46289C9.6423 7.24902 9.82303 7.30362 9.96387 7.42773C10.1085 7.55531 10.1875 7.73591 10.1875 7.93164C10.1874 8.12674 10.1072 8.30592 9.96484 8.43262C9.82536 8.55656 9.64499 8.61426 9.46289 8.61426H7.1416V9.97656H9.62109C9.80392 9.97656 9.98678 10.0326 10.1279 10.1582C10.2723 10.2867 10.3496 10.4678 10.3496 10.6631C10.3496 10.8583 10.2722 11.0394 10.1279 11.168C9.98678 11.2936 9.80393 11.3496 9.62109 11.3496H6.39648C6.20308 11.3496 6.00965 11.289 5.86328 11.1465C5.7159 11.0028 5.65039 10.8095 5.65039 10.6133V5.38672C5.65039 5.19054 5.7159 4.99723 5.86328 4.85352C6.00965 4.71101 6.20308 4.65039 6.39648 4.65039H9.62109ZM9.70215 10.9941C9.72618 10.9903 9.74882 10.9838 9.77051 10.9766C9.74884 10.9838 9.72616 10.9903 9.70215 10.9941ZM9.78809 10.9688C9.80381 10.9626 9.81877 10.9561 9.83301 10.9482C9.81877 10.9561 9.80381 10.9626 9.78809 10.9688ZM9.85059 10.9375C9.86364 10.9292 9.87617 10.9209 9.8877 10.9111C9.87616 10.9209 9.86365 10.9293 9.85059 10.9375ZM6.1123 10.8994C6.12346 10.9099 6.1358 10.9188 6.14844 10.9277C6.1358 10.9188 6.12346 10.9099 6.1123 10.8994ZM9.90039 10.8984C9.91122 10.8882 9.92149 10.8778 9.93066 10.8662C9.92147 10.8778 9.91123 10.8882 9.90039 10.8984ZM6.07129 10.8516C6.0792 10.8626 6.08754 10.8729 6.09668 10.8828C6.08754 10.8729 6.0792 10.8626 6.07129 10.8516ZM9.94434 10.8477C9.95104 10.8377 9.95638 10.8272 9.96191 10.8164C9.95637 10.8271 9.95106 10.8377 9.94434 10.8477ZM6.03418 10.7842C6.04066 10.7995 6.04735 10.8143 6.05566 10.8281C6.04735 10.8143 6.04066 10.7995 6.03418 10.7842ZM9.97559 10.7891C9.97984 10.7783 9.98321 10.7673 9.98633 10.7559C9.98321 10.7673 9.97984 10.7783 9.97559 10.7891ZM6.01367 10.7236C6.0173 10.7388 6.02121 10.7535 6.02637 10.7676C6.02121 10.7535 6.0173 10.7388 6.01367 10.7236ZM9.62109 10.3262C9.67716 10.3262 9.72942 10.3346 9.77539 10.3506C9.72927 10.3345 9.67733 10.3262 9.62109 10.3262ZM9.56641 8.25098C9.5802 8.24792 9.59348 8.24449 9.60645 8.24023C9.59348 8.2445 9.58021 8.24792 9.56641 8.25098ZM9.625 8.2334C9.64073 8.22735 9.65566 8.2207 9.66992 8.21289C9.65565 8.22072 9.64074 8.22733 9.625 8.2334ZM9.68848 8.20117C9.69832 8.19495 9.70781 8.18872 9.7168 8.18164C9.70781 8.18874 9.69832 8.19493 9.68848 8.20117ZM9.74121 8.16016C9.7511 8.15059 9.76008 8.14059 9.76855 8.12988C9.76009 8.1406 9.75109 8.15057 9.74121 8.16016ZM9.77734 8.11914C9.78753 8.10499 9.79605 8.09004 9.80371 8.07422C9.79606 8.09005 9.78751 8.10498 9.77734 8.11914ZM9.79883 5.6377C9.80686 5.6343 9.81463 5.63082 9.82227 5.62695C9.81463 5.63084 9.80687 5.63429 9.79883 5.6377ZM9.93457 5.53516C9.91124 5.56582 9.88273 5.59214 9.84863 5.61328C9.88277 5.5922 9.91121 5.56576 9.93457 5.53516ZM9.94434 5.52246C9.95363 5.50886 9.96171 5.4946 9.96875 5.47949C9.96172 5.4946 9.9536 5.50885 9.94434 5.52246ZM6.02637 5.23145C6.02118 5.24552 6.01733 5.26024 6.01367 5.27539C6.01734 5.26023 6.02118 5.24552 6.02637 5.23145ZM6.05566 5.17188C6.04766 5.18525 6.04049 5.19914 6.03418 5.21387C6.04049 5.19914 6.04765 5.18525 6.05566 5.17188ZM6.09961 5.11328C6.08938 5.124 6.08003 5.13538 6.07129 5.14746C6.08003 5.13538 6.08938 5.12399 6.09961 5.11328ZM6.14844 5.07129C6.13601 5.08003 6.12428 5.08938 6.11328 5.09961C6.12428 5.08938 6.13601 5.08003 6.14844 5.07129ZM6.23242 5.02637C6.21195 5.03382 6.19287 5.04334 6.1748 5.05371C6.19287 5.04335 6.21195 5.03382 6.23242 5.02637ZM6.1748 10.9453C6.19195 10.9552 6.21017 10.9635 6.22949 10.9707C6.21017 10.9635 6.19195 10.9552 6.1748 10.9453ZM6.30957 5.00684C6.28239 5.01136 6.25651 5.01759 6.23242 5.02637C6.25651 5.01759 6.28239 5.01136 6.30957 5.00684Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -894,7 +894,10 @@
|
||||
"right": "variable_list::ExpandSelectedEntry",
|
||||
"enter": "variable_list::EditVariable",
|
||||
"ctrl-c": "variable_list::CopyVariableValue",
|
||||
"ctrl-alt-c": "variable_list::CopyVariableName"
|
||||
"ctrl-alt-c": "variable_list::CopyVariableName",
|
||||
"delete": "variable_list::RemoveWatch",
|
||||
"backspace": "variable_list::RemoveWatch",
|
||||
"alt-enter": "variable_list::AddWatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -942,7 +945,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"context": "FileFinder || (FileFinder > Picker > Editor)",
|
||||
"bindings": {
|
||||
"ctrl-shift-a": "file_finder::ToggleSplitMenu",
|
||||
"ctrl-shift-i": "file_finder::ToggleFilterMenu"
|
||||
@@ -1037,7 +1040,8 @@
|
||||
"context": "DebugConsole > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "menu::Confirm"
|
||||
"enter": "menu::Confirm",
|
||||
"alt-enter": "console::WatchExpression"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -864,7 +864,10 @@
|
||||
"right": "variable_list::ExpandSelectedEntry",
|
||||
"enter": "variable_list::EditVariable",
|
||||
"cmd-c": "variable_list::CopyVariableValue",
|
||||
"cmd-alt-c": "variable_list::CopyVariableName"
|
||||
"cmd-alt-c": "variable_list::CopyVariableName",
|
||||
"delete": "variable_list::RemoveWatch",
|
||||
"backspace": "variable_list::RemoveWatch",
|
||||
"alt-enter": "variable_list::AddWatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1008,7 +1011,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"context": "FileFinder || (FileFinder > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-a": "file_finder::ToggleSplitMenu",
|
||||
@@ -1135,7 +1138,8 @@
|
||||
"context": "DebugConsole > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "menu::Confirm"
|
||||
"enter": "menu::Confirm",
|
||||
"alt-enter": "console::WatchExpression"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase" // editor:lower-case
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-l": "language_selector::Toggle", // grammar-selector:show
|
||||
"ctrl-|": "pane::RevealInProjectPanel", // tree-view:reveal-active-file
|
||||
@@ -19,25 +26,20 @@
|
||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||
"alt-shift-down": "editor::AddSelectionBelow", // editor:add-selection-below
|
||||
"alt-shift-up": "editor::AddSelectionAbove", // editor:add-selection-above
|
||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase", // editor:lower-case
|
||||
"ctrl-j": "editor::JoinLines", // editor:join-lines
|
||||
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
|
||||
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
|
||||
"ctrl-down": "editor::MoveLineDown", // editor:move-line-down
|
||||
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide" // markdown-preview:toggle
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide", // markdown-preview:toggle
|
||||
"ctrl-r": "outline::Toggle" // symbols-view:toggle-project-symbols
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected
|
||||
"ctrl-shift-f3": "search::SelectPreviousMatch" // find-and-replace:find-previous-selected
|
||||
}
|
||||
|
||||
@@ -90,6 +90,13 @@
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ContextMenuPrevious",
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -9,6 +9,14 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-l": "language_selector::Toggle",
|
||||
"cmd-|": "pane::RevealInProjectPanel",
|
||||
@@ -19,26 +27,20 @@
|
||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase",
|
||||
"alt-enter": "editor::Newline",
|
||||
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||
"ctrl-cmd-up": "editor::MoveLineUp",
|
||||
"ctrl-cmd-down": "editor::MoveLineDown",
|
||||
"cmd-\\": "workspace::ToggleLeftDock",
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-r": "outline::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||
"cmd-f3": "search::SelectNextMatch",
|
||||
"cmd-shift-f3": "search::SelectPreviousMatch"
|
||||
}
|
||||
|
||||
@@ -90,6 +90,13 @@
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ContextMenuPrevious",
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -85,10 +85,10 @@
|
||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
|
||||
"] )": ["vim::UnmatchedForward", { "char": ")" }],
|
||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
|
||||
"f": ["vim::PushFindForward", { "before": false }],
|
||||
"t": ["vim::PushFindForward", { "before": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true }],
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": false }],
|
||||
"t": ["vim::PushFindForward", { "before": true, "multiline": false }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": false }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": false }],
|
||||
"m": "vim::PushMark",
|
||||
"'": ["vim::PushJump", { "line": true }],
|
||||
"`": ["vim::PushJump", { "line": false }],
|
||||
@@ -368,6 +368,10 @@
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
"left": "vim::WrappingLeft",
|
||||
"right": "vim::WrappingRight",
|
||||
"h": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": "editor::Copy",
|
||||
@@ -385,6 +389,10 @@
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
|
||||
"r": "vim::PushReplace",
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
@@ -826,6 +834,24 @@
|
||||
"g g": "menu::SelectFirst"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "menu",
|
||||
"bindings": {
|
||||
"j": "menu::SelectNext",
|
||||
"k": "menu::SelectPrevious",
|
||||
"shift-g": "menu::SelectLast",
|
||||
"g g": "menu::SelectFirst"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_code_actions",
|
||||
"bindings": {
|
||||
"j": "editor::ContextMenuNext",
|
||||
"k": "editor::ContextMenuPrevious",
|
||||
"shift-g": "editor::ContextMenuLast",
|
||||
"g g": "editor::ContextMenuFirst"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"inactive_opacity": 1.0
|
||||
},
|
||||
// Layout mode of the bottom dock. Defaults to "contained"
|
||||
// choices: contained, full, left_aligned, right_aligned
|
||||
"bottom_dock_layout": "contained",
|
||||
// The direction that you want to split panes horizontally. Defaults to "up"
|
||||
"pane_split_direction_horizontal": "up",
|
||||
@@ -94,11 +95,9 @@
|
||||
// workspace when the centered layout is used.
|
||||
"right_padding": 0.2
|
||||
},
|
||||
// All settings related to the image viewer.
|
||||
// Image viewer settings
|
||||
"image_viewer": {
|
||||
// The unit for image file sizes.
|
||||
// By default we're setting it to binary.
|
||||
// The second option is decimal.
|
||||
// The unit for image file sizes: "binary" (KiB, MiB) or decimal (KB, MB)
|
||||
"unit": "binary"
|
||||
},
|
||||
// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier.
|
||||
@@ -110,6 +109,8 @@
|
||||
"multi_cursor_modifier": "alt",
|
||||
// Whether to enable vim modes and key bindings.
|
||||
"vim_mode": false,
|
||||
// Whether to enable helix mode and key bindings.
|
||||
"helix_mode": false,
|
||||
// Whether to show the informational hover box when moving the mouse
|
||||
// over symbols in the editor.
|
||||
"hover_popover_enabled": true,
|
||||
@@ -117,7 +118,14 @@
|
||||
"hover_popover_delay": 300,
|
||||
// Whether to confirm before quitting Zed.
|
||||
"confirm_quit": false,
|
||||
// Whether to restore last closed project when fresh Zed instance is opened.
|
||||
// Whether to restore last closed project when fresh Zed instance is opened
|
||||
// May take 3 values:
|
||||
// 1. All workspaces open during last session
|
||||
// "restore_on_startup": "last_session"
|
||||
// 2. The workspace opened
|
||||
// "restore_on_startup": "last_workspace",
|
||||
// 3. Do not restore previous workspaces
|
||||
// "restore_on_startup": "none",
|
||||
"restore_on_startup": "last_session",
|
||||
// Whether to attempt to restore previous file's state when opening it again.
|
||||
// The state is stored per pane.
|
||||
@@ -130,7 +138,9 @@
|
||||
"restore_on_file_reopen": true,
|
||||
// Whether to automatically close files that have been deleted on disk.
|
||||
"close_on_file_delete": false,
|
||||
// Size of the drop target in the editor.
|
||||
// Relative size of the drop target in the editor that will open dropped file as a split pane (0-0.5)
|
||||
// E.g. 0.25 == If you drop onto the top/bottom quarter of the pane a new vertical split will be used
|
||||
// If you drop onto the left/right quarter of the pane a new horizontal split will be used
|
||||
"drop_target_size": 0.2,
|
||||
// Whether the window should be closed when using 'close active item' on a window with no tabs.
|
||||
// May take 3 values:
|
||||
@@ -433,7 +443,9 @@
|
||||
// 1. `null` to inherit the editor `current_line_highlight` setting (default)
|
||||
// 2. "line" or "all" to highlight the current line in the minimap.
|
||||
// 3. "gutter" or "none" to not highlight the current line in the minimap.
|
||||
"current_line_highlight": null
|
||||
"current_line_highlight": null,
|
||||
// Maximum number of columns to display in the minimap.
|
||||
"max_width_columns": 80
|
||||
},
|
||||
// Enable middle-click paste on Linux.
|
||||
"middle_click_paste": true,
|
||||
@@ -481,7 +493,7 @@
|
||||
},
|
||||
// Whether the editor will scroll beyond the last line.
|
||||
"scroll_beyond_last_line": "one_page",
|
||||
// The number of lines to keep above/below the cursor when scrolling.
|
||||
// The number of lines to keep above/below the cursor when scrolling with the keyboard
|
||||
"vertical_scroll_margin": 3,
|
||||
// Whether to scroll when clicking near the edge of the visible text area.
|
||||
"autoscroll_on_clicks": false,
|
||||
@@ -697,23 +709,27 @@
|
||||
"default_width": 360,
|
||||
// Style of the git status indicator in the panel.
|
||||
//
|
||||
// Choices: label_color, icon
|
||||
// Default: icon
|
||||
"status_style": "icon",
|
||||
// What branch name to use if init.defaultBranch
|
||||
// is not set
|
||||
// What branch name to use if `init.defaultBranch` is not set
|
||||
//
|
||||
// Default: main
|
||||
"fallback_branch_name": "main",
|
||||
// Whether to sort entries in the panel by path
|
||||
// or by status (the default).
|
||||
// Whether to sort entries in the panel by path or by status (the default).
|
||||
//
|
||||
// Default: false
|
||||
"sort_by_path": false,
|
||||
// Whether to collapse untracked files in the diff panel.
|
||||
//
|
||||
// Default: false
|
||||
"collapse_untracked_diff": false,
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the git panel.
|
||||
//
|
||||
// Choices: always, auto, never, system
|
||||
// Default: inherits editor scrollbar settings
|
||||
"show": null
|
||||
// "show": null
|
||||
}
|
||||
},
|
||||
"message_editor": {
|
||||
@@ -991,8 +1007,7 @@
|
||||
// Removes any lines containing only whitespace at the end of the file and
|
||||
// ensures just one newline at the end.
|
||||
"ensure_final_newline_on_save": true,
|
||||
// Whether or not to perform a buffer format before saving
|
||||
//
|
||||
// Whether or not to perform a buffer format before saving: [on, off, prettier, language_server]
|
||||
// Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
|
||||
"format_on_save": "on",
|
||||
// How to perform a buffer format. This setting can take 4 values:
|
||||
@@ -1173,6 +1188,12 @@
|
||||
// 2. Display predictions inline only when holding a modifier key (alt by default).
|
||||
// "mode": "subtle"
|
||||
"mode": "eager",
|
||||
// Copilot-specific settings
|
||||
// "copilot": {
|
||||
// "enterprise_uri": "",
|
||||
// "proxy": "",
|
||||
// "proxy_no_verify": false
|
||||
// },
|
||||
// Whether edit predictions are enabled when editing text threads.
|
||||
// This setting has no effect if globally disabled.
|
||||
"enabled_in_text_threads": true
|
||||
@@ -1338,6 +1359,8 @@
|
||||
// the terminal will default to matching the buffer's font fallbacks.
|
||||
// This will be merged with the platform's default font fallbacks
|
||||
// "font_fallbacks": ["FiraCode Nerd Fonts"],
|
||||
// The weight of the editor font in standard CSS units from 100 to 900.
|
||||
// "font_weight": 400
|
||||
// Sets the maximum number of lines in the terminal's scrollback buffer.
|
||||
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
|
||||
// Existing terminals will not pick up this change until they are recreated.
|
||||
@@ -1711,7 +1734,6 @@
|
||||
"default_mode": "normal",
|
||||
"toggle_relative_line_numbers": false,
|
||||
"use_system_clipboard": "always",
|
||||
"use_multiline_find": false,
|
||||
"use_smartcase_find": false,
|
||||
"highlight_on_yank_duration": 200,
|
||||
"custom_digraphs": {},
|
||||
@@ -1794,6 +1816,7 @@
|
||||
"debugger": {
|
||||
"stepping_granularity": "line",
|
||||
"save_breakpoints": true,
|
||||
"dock": "bottom",
|
||||
"button": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,7 +601,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#669f59ff",
|
||||
"color": "#c18401ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -21,94 +21,58 @@ test-support = [
|
||||
[dependencies]
|
||||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
audio.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
component.workspace = true
|
||||
context_server.workspace = true
|
||||
convert_case.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
heed.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
icons.workspace = true
|
||||
indoc.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
inventory.workspace = true
|
||||
itertools.workspace = true
|
||||
jsonschema.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
notifications.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
ref-cast.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rules_library.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
sqlez.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
ui_input.workspace = true
|
||||
urlencoding.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
watch.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
zstd.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assistant_tools.workspace = true
|
||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
|
||||
@@ -1,294 +1,20 @@
|
||||
mod active_thread;
|
||||
mod agent_configuration;
|
||||
mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod agent_profile;
|
||||
mod buffer_codegen;
|
||||
mod context;
|
||||
mod context_picker;
|
||||
mod context_server_configuration;
|
||||
mod context_server_tool;
|
||||
mod context_store;
|
||||
mod context_strip;
|
||||
mod debug;
|
||||
mod history_store;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command_settings;
|
||||
mod terminal_codegen;
|
||||
mod terminal_inline_assistant;
|
||||
mod thread;
|
||||
mod thread_history;
|
||||
mod thread_store;
|
||||
mod tool_compatibility;
|
||||
mod tool_use;
|
||||
mod ui;
|
||||
pub mod agent_profile;
|
||||
pub mod context;
|
||||
pub mod context_server_tool;
|
||||
pub mod context_store;
|
||||
pub mod history_store;
|
||||
pub mod thread;
|
||||
pub mod thread_store;
|
||||
pub mod tool_use;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity, actions, impl_actions};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
|
||||
};
|
||||
use prompt_store::PromptBuilder;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use thread::ThreadId;
|
||||
|
||||
pub use crate::active_thread::ActiveThread;
|
||||
use crate::agent_configuration::{AddContextServerModal, ManageProfilesModal};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::context::{ContextLoadResult, LoadedContext};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent};
|
||||
pub use crate::thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use context::{AgentContext, ContextId, ContextLoadResult};
|
||||
pub use context_store::ContextStore;
|
||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||
pub use thread::{
|
||||
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
|
||||
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
|
||||
};
|
||||
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
||||
|
||||
actions!(
|
||||
agent,
|
||||
[
|
||||
NewTextThread,
|
||||
ToggleContextPicker,
|
||||
ToggleNavigationMenu,
|
||||
ToggleOptionsMenu,
|
||||
DeleteRecentlyOpenThread,
|
||||
ToggleProfileSelector,
|
||||
RemoveAllContext,
|
||||
ExpandMessageEditor,
|
||||
OpenHistory,
|
||||
AddContextServer,
|
||||
RemoveSelectedThread,
|
||||
Chat,
|
||||
ChatWithFollow,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist,
|
||||
FocusUp,
|
||||
FocusDown,
|
||||
FocusLeft,
|
||||
FocusRight,
|
||||
RemoveFocusedContext,
|
||||
AcceptSuggestedContext,
|
||||
OpenActiveThreadAsMarkdown,
|
||||
OpenAgentDiff,
|
||||
Keep,
|
||||
Reject,
|
||||
RejectAll,
|
||||
KeepAll,
|
||||
Follow,
|
||||
ResetTrialUpsell,
|
||||
ResetTrialEndUpsell,
|
||||
ContinueThread,
|
||||
ContinueWithBurnMode,
|
||||
ToggleBurnMode,
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema)]
|
||||
pub struct NewThread {
|
||||
#[serde(default)]
|
||||
from_thread_id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
||||
pub struct ManageProfiles {
|
||||
#[serde(default)]
|
||||
pub customize_tools: Option<AgentProfileId>,
|
||||
}
|
||||
|
||||
impl ManageProfiles {
|
||||
pub fn customize_tools(profile_id: AgentProfileId) -> Self {
|
||||
Self {
|
||||
customize_tools: Some(profile_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_actions!(agent, [NewThread, ManageProfiles]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ModelUsageContext {
|
||||
Thread(Entity<Thread>),
|
||||
InlineAssistant,
|
||||
}
|
||||
|
||||
impl ModelUsageContext {
|
||||
pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
||||
match self {
|
||||
Self::Thread(thread) => thread.read(cx).configured_model(),
|
||||
Self::InlineAssistant => {
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
self.configured_model(cx)
|
||||
.map(|configured_model| configured_model.model)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the `agent` crate.
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
is_eval: bool,
|
||||
cx: &mut App,
|
||||
) {
|
||||
AgentSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
assistant_context_editor::init(client.clone(), cx);
|
||||
rules_library::init(cx);
|
||||
if !is_eval {
|
||||
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||
// we're not running inside of the eval.
|
||||
init_language_model_settings(cx);
|
||||
}
|
||||
assistant_slash_command::init(cx);
|
||||
pub fn init(cx: &mut gpui::App) {
|
||||
thread_store::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry, fs.clone(), cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
terminal_inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
indexed_docs::init(cx);
|
||||
cx.observe_new(AddContextServerModal::register).detach();
|
||||
cx.observe_new(ManageProfilesModal::register).detach();
|
||||
}
|
||||
|
||||
fn init_language_model_settings(cx: &mut App) {
|
||||
update_active_language_model_from_settings(cx);
|
||||
|
||||
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
|
||||
.detach();
|
||||
cx.subscribe(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
|_, event: &language_model::Event, cx| match event {
|
||||
language_model::Event::ProviderStateChanged
|
||||
| language_model::Event::AddedProvider(_)
|
||||
| language_model::Event::RemovedProvider(_) => {
|
||||
update_active_language_model_from_settings(cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
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()),
|
||||
model: LanguageModelId::from(selection.model.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
let default = to_selected_model(&settings.default_model);
|
||||
let inline_assistant = settings
|
||||
.inline_assistant_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let commit_message = settings
|
||||
.commit_message_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let thread_summary = settings
|
||||
.thread_summary_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let inline_alternatives = settings
|
||||
.inline_alternatives
|
||||
.iter()
|
||||
.map(to_selected_model)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_default_model(Some(&default), cx);
|
||||
registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
|
||||
registry.select_commit_message_model(commit_message.as_ref(), cx);
|
||||
registry.select_thread_summary_model(thread_summary.as_ref(), cx);
|
||||
registry.select_inline_alternative_models(inline_alternatives, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn register_slash_commands(cx: &mut App) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
|
||||
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
|
||||
|
||||
cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry.register_command(
|
||||
assistant_slash_commands::StreamingExampleSlashCommand,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
update_slash_commands_from_settings(cx);
|
||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_slash_commands_from_settings(cx: &mut App) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
let settings = SlashCommandSettings::get_global(cx);
|
||||
|
||||
if settings.docs.enabled {
|
||||
slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
|
||||
}
|
||||
|
||||
if settings.cargo_workspace.enabled {
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry
|
||||
.unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
use context_server::ContextServerCommand;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
||||
use project::project_settings::{ContextServerSettings, ProjectSettings};
|
||||
use settings::update_settings_file;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::AddContextServer;
|
||||
|
||||
pub struct AddContextServerModal {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
name_editor: Entity<SingleLineInput>,
|
||||
command_editor: Entity<SingleLineInput>,
|
||||
}
|
||||
|
||||
impl AddContextServerModal {
|
||||
pub fn register(
|
||||
workspace: &mut Workspace,
|
||||
_window: Option<&mut Window>,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.register_action(|workspace, _: &AddContextServer, window, cx| {
|
||||
let workspace_handle = cx.entity().downgrade();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
Self::new(workspace_handle, window, cx)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let name_editor =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "my-custom-server").label("Name"));
|
||||
let command_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, "Command").label("Command to run the MCP server")
|
||||
});
|
||||
|
||||
Self {
|
||||
name_editor,
|
||||
command_editor,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut Context<Self>) {
|
||||
let name = self
|
||||
.name_editor
|
||||
.read(cx)
|
||||
.editor()
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
let command = self
|
||||
.command_editor
|
||||
.read(cx)
|
||||
.editor()
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if name.is_empty() || command.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut command_parts = command.split(' ').map(|part| part.trim().to_string());
|
||||
let Some(path) = command_parts.next() else {
|
||||
return;
|
||||
};
|
||||
let args = command_parts.collect::<Vec<_>>();
|
||||
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
update_settings_file::<ProjectSettings>(fs.clone(), cx, |settings, _| {
|
||||
settings.context_servers.insert(
|
||||
name.into(),
|
||||
ContextServerSettings::Custom {
|
||||
command: ContextServerCommand {
|
||||
path,
|
||||
args,
|
||||
env: None,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for AddContextServerModal {}
|
||||
|
||||
impl Focusable for AddContextServerModal {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.name_editor.focus_handle(cx).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for AddContextServerModal {}
|
||||
|
||||
impl Render for AddContextServerModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_name_empty = self.name_editor.read(cx).is_empty(cx);
|
||||
let is_command_empty = self.command_editor.read(cx).is_empty(cx);
|
||||
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(34.))
|
||||
.key_context("AddContextServerModal")
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Confirm, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
}),
|
||||
)
|
||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||
this.focus_handle(cx).focus(window);
|
||||
}))
|
||||
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
||||
.child(
|
||||
Modal::new("add-context-server", None)
|
||||
.header(ModalHeader::new().headline("Add MCP Server"))
|
||||
.section(
|
||||
Section::new().child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(self.name_editor.clone())
|
||||
.child(self.command_editor.clone()),
|
||||
),
|
||||
)
|
||||
.footer(
|
||||
ModalFooter::new().end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("cancel", "Cancel")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.cancel(&menu::Cancel, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("add-server", "Add Server")
|
||||
.disabled(is_name_empty || is_command_empty)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.map(|button| {
|
||||
if is_name_empty {
|
||||
button.tooltip(Tooltip::text("Name is required"))
|
||||
} else if is_command_empty {
|
||||
button.tooltip(Tooltip::text("Command is required"))
|
||||
} else {
|
||||
button
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
})),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,549 +0,0 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use context_server::ContextServerId;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{
|
||||
Animation, AnimationExt, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task,
|
||||
TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, percentage,
|
||||
};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{ContextServerStatus, ContextServerStore},
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
pub(crate) struct ConfigureContextServerModal {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
context_servers_to_setup: Vec<ContextServerSetup>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
}
|
||||
|
||||
enum Configuration {
|
||||
NotAvailable,
|
||||
Required(ConfigurationRequiredState),
|
||||
}
|
||||
|
||||
struct ConfigurationRequiredState {
|
||||
installation_instructions: Entity<markdown::Markdown>,
|
||||
settings_validator: Option<jsonschema::Validator>,
|
||||
settings_editor: Entity<Editor>,
|
||||
last_error: Option<SharedString>,
|
||||
waiting_for_context_server: bool,
|
||||
}
|
||||
|
||||
struct ContextServerSetup {
|
||||
id: ContextServerId,
|
||||
repository_url: Option<SharedString>,
|
||||
configuration: Configuration,
|
||||
}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
pub fn new(
|
||||
configurations: impl Iterator<Item = crate::context_server_configuration::Configuration>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
jsonc_language: Option<Arc<Language>>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let context_servers_to_setup = configurations
|
||||
.map(|config| match config {
|
||||
crate::context_server_configuration::Configuration::NotAvailable(
|
||||
context_server_id,
|
||||
repository_url,
|
||||
) => ContextServerSetup {
|
||||
id: context_server_id,
|
||||
repository_url,
|
||||
configuration: Configuration::NotAvailable,
|
||||
},
|
||||
crate::context_server_configuration::Configuration::Required(
|
||||
context_server_id,
|
||||
repository_url,
|
||||
config,
|
||||
) => {
|
||||
let jsonc_language = jsonc_language.clone();
|
||||
let settings_validator = jsonschema::validator_for(&config.settings_schema)
|
||||
.context("Failed to load JSON schema for context server settings")
|
||||
.log_err();
|
||||
let state = ConfigurationRequiredState {
|
||||
installation_instructions: cx.new(|cx| {
|
||||
Markdown::new(
|
||||
config.installation_instructions.clone().into(),
|
||||
Some(language_registry.clone()),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
settings_validator,
|
||||
settings_editor: cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(1, 16, window, cx);
|
||||
editor.set_text(config.default_settings.trim(), window, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_soft_wrap_mode(
|
||||
language::language_settings::SoftWrap::None,
|
||||
cx,
|
||||
);
|
||||
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(jsonc_language, cx)
|
||||
})
|
||||
}
|
||||
editor
|
||||
}),
|
||||
waiting_for_context_server: false,
|
||||
last_error: None,
|
||||
};
|
||||
ContextServerSetup {
|
||||
id: context_server_id,
|
||||
repository_url,
|
||||
configuration: Configuration::Required(state),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Self {
|
||||
workspace,
|
||||
focus_handle: cx.focus_handle(),
|
||||
context_servers_to_setup,
|
||||
context_server_store,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
pub fn confirm(&mut self, cx: &mut Context<Self>) {
|
||||
if self.context_servers_to_setup.is_empty() {
|
||||
self.dismiss(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let id = self.context_servers_to_setup[0].id.clone();
|
||||
let configuration = match &mut self.context_servers_to_setup[0].configuration {
|
||||
Configuration::NotAvailable => {
|
||||
self.context_servers_to_setup.remove(0);
|
||||
if self.context_servers_to_setup.is_empty() {
|
||||
self.dismiss(cx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Configuration::Required(state) => state,
|
||||
};
|
||||
|
||||
configuration.last_error.take();
|
||||
if configuration.waiting_for_context_server {
|
||||
return;
|
||||
}
|
||||
|
||||
let settings_value = match serde_json_lenient::from_str::<serde_json::Value>(
|
||||
&configuration.settings_editor.read(cx).text(cx),
|
||||
) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
configuration.last_error = Some(error.to_string().into());
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(validator) = configuration.settings_validator.as_ref() {
|
||||
if let Err(error) = validator.validate(&settings_value) {
|
||||
configuration.last_error = Some(error.to_string().into());
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let id = id.clone();
|
||||
|
||||
let settings_changed = ProjectSettings::get_global(cx)
|
||||
.context_servers
|
||||
.get(&id.0)
|
||||
.map_or(true, |settings| match settings {
|
||||
ContextServerSettings::Custom { .. } => false,
|
||||
ContextServerSettings::Extension { settings } => settings != &settings_value,
|
||||
});
|
||||
|
||||
let is_running = self.context_server_store.read(cx).status_for_server(&id)
|
||||
== Some(ContextServerStatus::Running);
|
||||
|
||||
if !settings_changed && is_running {
|
||||
self.complete_setup(id, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
configuration.waiting_for_context_server = true;
|
||||
|
||||
let task = wait_for_context_server(&self.context_server_store, id.clone(), cx);
|
||||
cx.spawn({
|
||||
let id = id.clone();
|
||||
async move |this, cx| {
|
||||
let result = task.await;
|
||||
this.update(cx, |this, cx| match result {
|
||||
Ok(_) => {
|
||||
this.complete_setup(id, cx);
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(setup) = this.context_servers_to_setup.get_mut(0) {
|
||||
match &mut setup.configuration {
|
||||
Configuration::NotAvailable => {}
|
||||
Configuration::Required(state) => {
|
||||
state.last_error = Some(err.into());
|
||||
state.waiting_for_context_server = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.dismiss(cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// When we write the settings to the file, the context server will be restarted.
|
||||
update_settings_file::<ProjectSettings>(workspace.read(cx).app_state().fs.clone(), cx, {
|
||||
let id = id.clone();
|
||||
|settings, _| {
|
||||
settings.context_servers.insert(
|
||||
id.0,
|
||||
ContextServerSettings::Extension {
|
||||
settings: settings_value,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn complete_setup(&mut self, id: ContextServerId, cx: &mut Context<Self>) {
|
||||
self.context_servers_to_setup.remove(0);
|
||||
cx.notify();
|
||||
|
||||
if !self.context_servers_to_setup.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.workspace
|
||||
.update(cx, {
|
||||
|workspace, cx| {
|
||||
let status_toast = StatusToast::new(
|
||||
format!("{} configured successfully.", id),
|
||||
cx,
|
||||
|this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Hammer).color(Color::Muted))
|
||||
.action("Dismiss", |_, _| {})
|
||||
},
|
||||
);
|
||||
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
|
||||
self.dismiss(cx);
|
||||
}
|
||||
|
||||
fn dismiss(&self, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_context_server(
|
||||
context_server_store: &Entity<ContextServerStore>,
|
||||
context_server_id: ContextServerId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(), Arc<str>>> {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let tx = Arc::new(Mutex::new(Some(tx)));
|
||||
|
||||
let subscription = cx.subscribe(context_server_store, move |_, event, _cx| match event {
|
||||
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
|
||||
match status {
|
||||
ContextServerStatus::Running => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Stopped => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err("Context server stopped running".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Error(error) => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err(error.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(async move |_cx| {
|
||||
let result = rx.await.unwrap();
|
||||
drop(subscription);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
impl Render for ConfigureContextServerModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let Some(setup) = self.context_servers_to_setup.first() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(42.))
|
||||
.key_context("ConfigureContextServerModal")
|
||||
.track_focus(&focus_handle)
|
||||
.on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx)))
|
||||
.on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.dismiss(cx)))
|
||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||
this.focus_handle(cx).focus(window);
|
||||
}))
|
||||
.child(
|
||||
Modal::new("configure-context-server", None)
|
||||
.header(ModalHeader::new().headline(format!("Configure {}", setup.id)))
|
||||
.section(match &setup.configuration {
|
||||
Configuration::NotAvailable => Section::new().child(
|
||||
Label::new(
|
||||
"No configuration options available for this context server. Visit the Repository for any further instructions.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
Configuration::Required(configuration) => Section::new()
|
||||
.child(div().pb_2().text_sm().child(MarkdownElement::new(
|
||||
configuration.installation_instructions.clone(),
|
||||
default_markdown_style(window, cx),
|
||||
)))
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.gap_1()
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(
|
||||
settings.buffer_line_height.value(),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
&configuration.settings_editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.when_some(configuration.last_error.clone(), |this, error| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
.child(
|
||||
div().w_full().child(
|
||||
Label::new(error)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(configuration.waiting_for_context_server, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(
|
||||
percentage(delta),
|
||||
))
|
||||
},
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Label::new("Waiting for Context Server")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
}),
|
||||
})
|
||||
.footer(
|
||||
ModalFooter::new()
|
||||
.when_some(setup.repository_url.clone(), |this, repository_url| {
|
||||
this.start_slot(
|
||||
h_flex().w_full().child(
|
||||
Button::new("open-repository", "Open Repository")
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip({
|
||||
let repository_url = repository_url.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Open Repository",
|
||||
None,
|
||||
repository_url.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, _, cx| cx.open_url(&repository_url)),
|
||||
),
|
||||
)
|
||||
})
|
||||
.end_slot(match &setup.configuration {
|
||||
Configuration::NotAvailable => Button::new("dismiss", "Dismiss")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(|this, _event, _window, cx| this.dismiss(cx)),
|
||||
)
|
||||
.into_any_element(),
|
||||
Configuration::Required(state) => h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("cancel", "Cancel")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.dismiss(cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("configure-server", "Configure MCP")
|
||||
.disabled(state.waiting_for_context_server)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.confirm(cx)
|
||||
})),
|
||||
)
|
||||
.into_any_element(),
|
||||
}),
|
||||
),
|
||||
).into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let colors = cx.theme().colors();
|
||||
let mut text_style = window.text_style();
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||
font_features: Some(theme_settings.ui_font.features.clone()),
|
||||
font_size: Some(TextSize::XSmall.rems(cx).into()),
|
||||
color: Some(colors.text_muted),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(colors.text_accent.opacity(0.5)),
|
||||
thickness: px(1.),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for ConfigureContextServerModal {}
|
||||
impl EventEmitter<DismissEvent> for ConfigureContextServerModal {}
|
||||
impl Focusable for ConfigureContextServerModal {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
if let Some(current) = self.context_servers_to_setup.first() {
|
||||
match ¤t.configuration {
|
||||
Configuration::NotAvailable => self.focus_handle.clone(),
|
||||
Configuration::Required(configuration) => {
|
||||
configuration.settings_editor.read(cx).focus_handle(cx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,8 @@ use assistant_tool::{Tool, ToolSource, ToolWorkingSet};
|
||||
use collections::IndexMap;
|
||||
use convert_case::{Case, Casing};
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity};
|
||||
use gpui::{App, Entity, SharedString};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use ui::SharedString;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@@ -108,11 +107,11 @@ mod tests {
|
||||
use agent_settings::ContextServerPreset;
|
||||
use assistant_tool::ToolRegistry;
|
||||
use collections::IndexMap;
|
||||
use gpui::SharedString;
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use http_client::FakeHttpClient;
|
||||
use project::Project;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use ui::SharedString;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -302,7 +301,7 @@ mod tests {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn icon(&self) -> ui::IconName {
|
||||
fn icon(&self) -> icons::IconName {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,25 @@
|
||||
use std::fmt::{self, Display, Formatter, Write as _};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
|
||||
use assistant_context_editor::AssistantContext;
|
||||
use crate::thread::Thread;
|
||||
use assistant_context::AssistantContext;
|
||||
use assistant_tool::outline;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::display_map::CreaseId;
|
||||
use editor::{Addon, Editor};
|
||||
use collections::HashSet;
|
||||
use futures::future;
|
||||
use futures::{FutureExt, future::Shared};
|
||||
use gpui::{App, AppContext as _, Entity, SharedString, Subscription, Task};
|
||||
use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
|
||||
use icons::IconName;
|
||||
use language::{Buffer, ParseStatus};
|
||||
use language_model::{LanguageModelImage, LanguageModelRequestMessage, MessageContent};
|
||||
use project::{Project, ProjectEntryId, ProjectPath, Worktree};
|
||||
use prompt_store::{PromptStore, UserPromptId};
|
||||
use ref_cast::RefCast;
|
||||
use rope::Point;
|
||||
use std::fmt::{self, Display, Formatter, Write as _};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
use text::{Anchor, OffsetRangeExt as _};
|
||||
use ui::{Context, ElementId, IconName};
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
use util::{ResultExt as _, post_inc};
|
||||
|
||||
use crate::context_store::{ContextStore, ContextStoreEvent};
|
||||
use crate::thread::Thread;
|
||||
|
||||
pub const RULES_ICON: IconName = IconName::Context;
|
||||
|
||||
pub enum ContextKind {
|
||||
@@ -1117,69 +1112,6 @@ impl Hash for AgentContextKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContextCreasesAddon {
|
||||
creases: HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>>,
|
||||
_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl Addon for ContextCreasesAddon {
|
||||
fn to_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextCreasesAddon {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
creases: HashMap::default(),
|
||||
_subscription: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_creases(
|
||||
&mut self,
|
||||
context_store: &Entity<ContextStore>,
|
||||
key: AgentContextKey,
|
||||
creases: impl IntoIterator<Item = (CreaseId, SharedString)>,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.creases.entry(key).or_default().extend(creases);
|
||||
self._subscription = Some(cx.subscribe(
|
||||
&context_store,
|
||||
|editor, _, event, cx| match event {
|
||||
ContextStoreEvent::ContextRemoved(key) => {
|
||||
let Some(this) = editor.addon_mut::<Self>() else {
|
||||
return;
|
||||
};
|
||||
let (crease_ids, replacement_texts): (Vec<_>, Vec<_>) = this
|
||||
.creases
|
||||
.remove(key)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let ranges = editor
|
||||
.remove_creases(crease_ids, cx)
|
||||
.into_iter()
|
||||
.map(|(_, range)| range)
|
||||
.collect::<Vec<_>>();
|
||||
editor.unfold_ranges(&ranges, false, false, cx);
|
||||
editor.edit(ranges.into_iter().zip(replacement_texts), cx);
|
||||
cx.notify();
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>> {
|
||||
self.creases
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use context_server::ContextServerId;
|
||||
use extension::{ContextServerConfiguration, ExtensionManifest};
|
||||
use fs::Fs;
|
||||
use gpui::Task;
|
||||
use language::LanguageRegistry;
|
||||
use project::{
|
||||
context_server_store::registry::ContextServerDescriptorRegistry,
|
||||
project_settings::ProjectSettings,
|
||||
};
|
||||
use settings::update_settings_file;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::agent_configuration::ConfigureContextServerModal;
|
||||
|
||||
pub(crate) fn init(language_registry: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
cx.observe_new(move |_: &mut Workspace, window, cx| {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
|
||||
cx.subscribe_in(extension_events, window, {
|
||||
let language_registry = language_registry.clone();
|
||||
let fs = fs.clone();
|
||||
move |workspace, _, event, window, cx| match event {
|
||||
extension::Event::ExtensionInstalled(manifest) => {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
extension::Event::ExtensionUninstalled(manifest) => {
|
||||
remove_context_server_settings(
|
||||
manifest.context_servers.keys().cloned().collect(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
extension::Event::ConfigureExtensionRequested(manifest) => {
|
||||
if !manifest.context_servers.is_empty() {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
log::info!(
|
||||
"No extension events global found. Skipping context server configuration wizard"
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn remove_context_server_settings(
|
||||
context_server_ids: Vec<Arc<str>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
update_settings_file::<ProjectSettings>(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.retain(|server_id, _| !context_server_ids.contains(server_id));
|
||||
});
|
||||
}
|
||||
|
||||
pub enum Configuration {
|
||||
NotAvailable(ContextServerId, Option<SharedString>),
|
||||
Required(
|
||||
ContextServerId,
|
||||
Option<SharedString>,
|
||||
ContextServerConfiguration,
|
||||
),
|
||||
}
|
||||
|
||||
fn show_configure_mcp_modal(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Workspace>,
|
||||
) {
|
||||
if !window.is_window_active() {
|
||||
return;
|
||||
}
|
||||
|
||||
let context_server_store = workspace.project().read(cx).context_server_store();
|
||||
let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());
|
||||
|
||||
let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
|
||||
let worktree_store = workspace.project().read(cx).worktree_store();
|
||||
let configuration_tasks = manifest
|
||||
.context_servers
|
||||
.keys()
|
||||
.cloned()
|
||||
.map({
|
||||
|key| {
|
||||
let Some(descriptor) = registry.context_server_descriptor(&key) else {
|
||||
return Task::ready(Configuration::NotAvailable(
|
||||
ContextServerId(key),
|
||||
repository.clone(),
|
||||
));
|
||||
};
|
||||
cx.spawn({
|
||||
let repository_url = repository.clone();
|
||||
let worktree_store = worktree_store.clone();
|
||||
async move |_, cx| {
|
||||
let configuration = descriptor
|
||||
.configuration(worktree_store.clone(), &cx)
|
||||
.await
|
||||
.context("Failed to resolve context server configuration")
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
match configuration {
|
||||
Some(config) => Configuration::Required(
|
||||
ContextServerId(key),
|
||||
repository_url,
|
||||
config,
|
||||
),
|
||||
None => {
|
||||
Configuration::NotAvailable(ContextServerId(key), repository_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let jsonc_language = language_registry.language_for_name("jsonc");
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let configurations = futures::future::join_all(configuration_tasks).await;
|
||||
let jsonc_language = jsonc_language.await.ok();
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let workspace = cx.entity().downgrade();
|
||||
this.toggle_modal(window, cx, |window, cx| {
|
||||
ConfigureContextServerModal::new(
|
||||
configurations.into_iter(),
|
||||
context_server_store,
|
||||
jsonc_language,
|
||||
language_registry,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -4,9 +4,9 @@ use anyhow::{Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
|
||||
use context_server::{ContextServerId, types};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use icons::IconName;
|
||||
use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat};
|
||||
use project::{Project, context_server_store::ContextServerStore};
|
||||
use ui::IconName;
|
||||
|
||||
pub struct ContextServerTool {
|
||||
store: Entity<ContextServerStore>,
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
context::{
|
||||
AgentContextHandle, AgentContextKey, ContextId, ContextKind, DirectoryContextHandle,
|
||||
FetchedUrlContext, FileContextHandle, ImageContext, RulesContextHandle,
|
||||
SelectionContextHandle, SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
|
||||
},
|
||||
thread::{MessageId, Thread, ThreadId},
|
||||
thread_store::ThreadStore,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_context_editor::AssistantContext;
|
||||
use assistant_context::AssistantContext;
|
||||
use collections::{HashSet, IndexSet};
|
||||
use futures::{self, FutureExt};
|
||||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||
use language::{Buffer, File as _};
|
||||
use language_model::LanguageModelImage;
|
||||
use project::image_store::is_image_file;
|
||||
use project::{Project, ProjectItem, ProjectPath, Symbol};
|
||||
use project::{Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file};
|
||||
use prompt_store::UserPromptId;
|
||||
use ref_cast::RefCast as _;
|
||||
use text::{Anchor, OffsetRangeExt};
|
||||
|
||||
use crate::ThreadStore;
|
||||
use crate::context::{
|
||||
AgentContextHandle, AgentContextKey, ContextId, DirectoryContextHandle, FetchedUrlContext,
|
||||
FileContextHandle, ImageContext, RulesContextHandle, SelectionContextHandle,
|
||||
SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
|
||||
use std::{
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use crate::context_strip::SuggestedContext;
|
||||
use crate::thread::{MessageId, Thread, ThreadId};
|
||||
use text::{Anchor, OffsetRangeExt};
|
||||
|
||||
pub struct ContextStore {
|
||||
project: WeakEntity<Project>,
|
||||
@@ -561,6 +561,49 @@ impl ContextStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SuggestedContext {
|
||||
File {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakEntity<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
name: SharedString,
|
||||
thread: WeakEntity<Thread>,
|
||||
},
|
||||
TextThread {
|
||||
name: SharedString,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SuggestedContext {
|
||||
pub fn name(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { name, .. } => name,
|
||||
Self::Thread { name, .. } => name,
|
||||
Self::TextThread { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_path(&self) -> Option<SharedString> {
|
||||
match self {
|
||||
Self::File { icon_path, .. } => icon_path.clone(),
|
||||
Self::Thread { .. } => None,
|
||||
Self::TextThread { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ContextKind {
|
||||
match self {
|
||||
Self::File { .. } => ContextKind::File,
|
||||
Self::Thread { .. } => ContextKind::Thread,
|
||||
Self::TextThread { .. } => ContextKind::TextThread,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FileInclusion {
|
||||
Direct,
|
||||
InDirectory { full_path: PathBuf },
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
use std::{collections::VecDeque, path::Path, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
ThreadId,
|
||||
thread_store::{SerializedThreadMetadata, ThreadStore},
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_context_editor::SavedContextMetadata;
|
||||
use assistant_context::SavedContextMetadata;
|
||||
use chrono::{DateTime, Utc};
|
||||
use gpui::{AsyncApp, Entity, SharedString, Task, prelude::*};
|
||||
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
|
||||
use itertools::Itertools;
|
||||
use paths::contexts_dir;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use ui::App;
|
||||
use std::{collections::VecDeque, path::Path, sync::Arc, time::Duration};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{
|
||||
thread::ThreadId,
|
||||
thread_store::{SerializedThreadMetadata, ThreadStore},
|
||||
};
|
||||
|
||||
const MAX_RECENTLY_OPENED_ENTRIES: usize = 6;
|
||||
const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
|
||||
const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
@@ -66,7 +62,7 @@ enum SerializedRecentOpen {
|
||||
|
||||
pub struct HistoryStore {
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
context_store: Entity<assistant_context::ContextStore>,
|
||||
recently_opened_entries: VecDeque<HistoryEntryId>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
_save_recently_opened_entries_task: Task<()>,
|
||||
@@ -75,7 +71,7 @@ pub struct HistoryStore {
|
||||
impl HistoryStore {
|
||||
pub fn new(
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
context_store: Entity<assistant_context::ContextStore>,
|
||||
initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
|
||||
@@ -1,53 +1,49 @@
|
||||
use std::io::Write;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::{
|
||||
agent_profile::AgentProfile,
|
||||
context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext},
|
||||
thread_store::{
|
||||
SerializedCrease, SerializedLanguageModel, SerializedMessage, SerializedMessageSegment,
|
||||
SerializedThread, SerializedToolResult, SerializedToolUse, SharedProjectContext,
|
||||
ThreadStore,
|
||||
},
|
||||
tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState},
|
||||
};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
|
||||
use chrono::{DateTime, Utc};
|
||||
use client::{ModelRequestUsage, RequestUsage};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::display_map::CreaseMetadata;
|
||||
use feature_flags::{self, FeatureFlagAppExt};
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt, StreamExt as _};
|
||||
use futures::{FutureExt, StreamExt as _, future::Shared};
|
||||
use git::repository::DiffType;
|
||||
use gpui::{
|
||||
AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task,
|
||||
WeakEntity,
|
||||
WeakEntity, Window,
|
||||
};
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelKnownError, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
|
||||
LanguageModelToolResultContent, LanguageModelToolUseId, MessageContent,
|
||||
ModelRequestLimitReachedError, PaymentRequiredError, RequestUsage, Role, SelectedModel,
|
||||
StopReason, TokenUsage,
|
||||
ModelRequestLimitReachedError, PaymentRequiredError, Role, SelectedModel, StopReason,
|
||||
TokenUsage,
|
||||
};
|
||||
use postage::stream::Stream as _;
|
||||
use project::Project;
|
||||
use project::git_store::{GitStore, GitStoreCheckpoint, RepositoryState};
|
||||
use project::{
|
||||
Project,
|
||||
git_store::{GitStore, GitStoreCheckpoint, RepositoryState},
|
||||
};
|
||||
use prompt_store::{ModelContext, PromptBuilder};
|
||||
use proto::Plan;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::{io::Write, ops::Range, sync::Arc, time::Instant};
|
||||
use thiserror::Error;
|
||||
use ui::Window;
|
||||
use util::{ResultExt as _, post_inc};
|
||||
|
||||
use uuid::Uuid;
|
||||
use zed_llm_client::{CompletionIntent, CompletionRequestStatus};
|
||||
|
||||
use crate::ThreadStore;
|
||||
use crate::agent_profile::AgentProfile;
|
||||
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
|
||||
use crate::thread_store::{
|
||||
SerializedCrease, SerializedLanguageModel, SerializedMessage, SerializedMessageSegment,
|
||||
SerializedThread, SerializedToolResult, SerializedToolUse, SharedProjectContext,
|
||||
};
|
||||
use crate::tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState};
|
||||
use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema,
|
||||
@@ -97,13 +93,18 @@ impl MessageId {
|
||||
fn post_inc(&mut self) -> Self {
|
||||
Self(post_inc(&mut self.0))
|
||||
}
|
||||
|
||||
pub fn as_usize(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Stored information that can be used to resurrect a context crease when creating an editor for a past message.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MessageCrease {
|
||||
pub range: Range<usize>,
|
||||
pub metadata: CreaseMetadata,
|
||||
pub icon_path: SharedString,
|
||||
pub label: SharedString,
|
||||
/// None for a deserialized message, Some otherwise.
|
||||
pub context: Option<AgentContextHandle>,
|
||||
}
|
||||
@@ -354,7 +355,6 @@ pub struct Thread {
|
||||
request_token_usage: Vec<TokenUsage>,
|
||||
cumulative_token_usage: TokenUsage,
|
||||
exceeded_window_error: Option<ExceededWindowError>,
|
||||
last_usage: Option<RequestUsage>,
|
||||
tool_use_limit_reached: bool,
|
||||
feedback: Option<ThreadFeedback>,
|
||||
message_feedback: HashMap<MessageId, ThreadFeedback>,
|
||||
@@ -447,7 +447,6 @@ impl Thread {
|
||||
request_token_usage: Vec::new(),
|
||||
cumulative_token_usage: TokenUsage::default(),
|
||||
exceeded_window_error: None,
|
||||
last_usage: None,
|
||||
tool_use_limit_reached: false,
|
||||
feedback: None,
|
||||
message_feedback: HashMap::default(),
|
||||
@@ -545,10 +544,8 @@ impl Thread {
|
||||
.into_iter()
|
||||
.map(|crease| MessageCrease {
|
||||
range: crease.start..crease.end,
|
||||
metadata: CreaseMetadata {
|
||||
icon_path: crease.icon_path,
|
||||
label: crease.label,
|
||||
},
|
||||
icon_path: crease.icon_path,
|
||||
label: crease.label,
|
||||
context: None,
|
||||
})
|
||||
.collect(),
|
||||
@@ -572,7 +569,6 @@ impl Thread {
|
||||
request_token_usage: serialized.request_token_usage,
|
||||
cumulative_token_usage: serialized.cumulative_token_usage,
|
||||
exceeded_window_error: None,
|
||||
last_usage: None,
|
||||
tool_use_limit_reached: serialized.tool_use_limit_reached,
|
||||
feedback: None,
|
||||
message_feedback: HashMap::default(),
|
||||
@@ -879,10 +875,6 @@ impl Thread {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn last_usage(&self) -> Option<RequestUsage> {
|
||||
self.last_usage
|
||||
}
|
||||
|
||||
pub fn tool_use_limit_reached(&self) -> bool {
|
||||
self.tool_use_limit_reached
|
||||
}
|
||||
@@ -1180,8 +1172,8 @@ impl Thread {
|
||||
.map(|crease| SerializedCrease {
|
||||
start: crease.range.start,
|
||||
end: crease.range.end,
|
||||
icon_path: crease.metadata.icon_path.clone(),
|
||||
label: crease.metadata.label.clone(),
|
||||
icon_path: crease.icon_path.clone(),
|
||||
label: crease.label.clone(),
|
||||
})
|
||||
.collect(),
|
||||
is_hidden: message.is_hidden,
|
||||
@@ -1505,27 +1497,76 @@ impl Thread {
|
||||
thread.update(cx, |thread, cx| {
|
||||
let event = match event {
|
||||
Ok(event) => event,
|
||||
Err(LanguageModelCompletionError::BadInputJson {
|
||||
id,
|
||||
tool_name,
|
||||
raw_input: invalid_input_json,
|
||||
json_parse_error,
|
||||
}) => {
|
||||
thread.receive_invalid_tool_json(
|
||||
id,
|
||||
tool_name,
|
||||
invalid_input_json,
|
||||
json_parse_error,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Err(LanguageModelCompletionError::Other(error)) => {
|
||||
return Err(error);
|
||||
}
|
||||
Err(err @ LanguageModelCompletionError::RateLimit(..)) => {
|
||||
return Err(err.into());
|
||||
Err(error) => {
|
||||
match error {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after } => {
|
||||
anyhow::bail!(LanguageModelKnownError::RateLimitExceeded { retry_after });
|
||||
}
|
||||
LanguageModelCompletionError::Overloaded => {
|
||||
anyhow::bail!(LanguageModelKnownError::Overloaded);
|
||||
}
|
||||
LanguageModelCompletionError::ApiInternalServerError =>{
|
||||
anyhow::bail!(LanguageModelKnownError::ApiInternalServerError);
|
||||
}
|
||||
LanguageModelCompletionError::PromptTooLarge { tokens } => {
|
||||
let tokens = tokens.unwrap_or_else(|| {
|
||||
// We didn't get an exact token count from the API, so fall back on our estimate.
|
||||
thread.total_token_usage()
|
||||
.map(|usage| usage.total)
|
||||
.unwrap_or(0)
|
||||
// We know the context window was exceeded in practice, so if our estimate was
|
||||
// lower than max tokens, the estimate was wrong; return that we exceeded by 1.
|
||||
.max(model.max_token_count().saturating_add(1))
|
||||
});
|
||||
|
||||
anyhow::bail!(LanguageModelKnownError::ContextWindowLimitExceeded { tokens })
|
||||
}
|
||||
LanguageModelCompletionError::ApiReadResponseError(io_error) => {
|
||||
anyhow::bail!(LanguageModelKnownError::ReadResponseError(io_error));
|
||||
}
|
||||
LanguageModelCompletionError::UnknownResponseFormat(error) => {
|
||||
anyhow::bail!(LanguageModelKnownError::UnknownResponseFormat(error));
|
||||
}
|
||||
LanguageModelCompletionError::HttpResponseError { status, ref body } => {
|
||||
if let Some(known_error) = LanguageModelKnownError::from_http_response(status, body) {
|
||||
anyhow::bail!(known_error);
|
||||
} else {
|
||||
return Err(error.into());
|
||||
}
|
||||
}
|
||||
LanguageModelCompletionError::DeserializeResponse(error) => {
|
||||
anyhow::bail!(LanguageModelKnownError::DeserializeResponse(error));
|
||||
}
|
||||
LanguageModelCompletionError::BadInputJson {
|
||||
id,
|
||||
tool_name,
|
||||
raw_input: invalid_input_json,
|
||||
json_parse_error,
|
||||
} => {
|
||||
thread.receive_invalid_tool_json(
|
||||
id,
|
||||
tool_name,
|
||||
invalid_input_json,
|
||||
json_parse_error,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
// These are all errors we can't automatically attempt to recover from (e.g. by retrying)
|
||||
err @ LanguageModelCompletionError::BadRequestFormat |
|
||||
err @ LanguageModelCompletionError::AuthenticationError |
|
||||
err @ LanguageModelCompletionError::PermissionError |
|
||||
err @ LanguageModelCompletionError::ApiEndpointNotFound |
|
||||
err @ LanguageModelCompletionError::SerializeRequest(_) |
|
||||
err @ LanguageModelCompletionError::BuildRequestBody(_) |
|
||||
err @ LanguageModelCompletionError::HttpSend(_) => {
|
||||
anyhow::bail!(err);
|
||||
}
|
||||
LanguageModelCompletionError::Other(error) => {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1680,9 +1721,7 @@ impl Thread {
|
||||
CompletionRequestStatus::UsageUpdated {
|
||||
amount, limit
|
||||
} => {
|
||||
let usage = RequestUsage { limit, amount: amount as i32 };
|
||||
|
||||
thread.last_usage = Some(usage);
|
||||
thread.update_model_request_usage(amount as u32, limit, cx);
|
||||
}
|
||||
CompletionRequestStatus::ToolUseLimitReached => {
|
||||
thread.tool_use_limit_reached = true;
|
||||
@@ -1782,6 +1821,18 @@ impl Thread {
|
||||
project.set_agent_location(None, cx);
|
||||
});
|
||||
|
||||
fn emit_generic_error(error: &anyhow::Error, cx: &mut Context<Thread>) {
|
||||
let error_message = error
|
||||
.chain()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::Message {
|
||||
header: "Error interacting with language model".into(),
|
||||
message: SharedString::from(error_message.clone()),
|
||||
}));
|
||||
}
|
||||
|
||||
if error.is::<PaymentRequiredError>() {
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
|
||||
} else if let Some(error) =
|
||||
@@ -1794,26 +1845,34 @@ impl Thread {
|
||||
error.downcast_ref::<LanguageModelKnownError>()
|
||||
{
|
||||
match known_error {
|
||||
LanguageModelKnownError::ContextWindowLimitExceeded {
|
||||
tokens,
|
||||
} => {
|
||||
LanguageModelKnownError::ContextWindowLimitExceeded { tokens } => {
|
||||
thread.exceeded_window_error = Some(ExceededWindowError {
|
||||
model_id: model.id(),
|
||||
token_count: *tokens,
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
LanguageModelKnownError::RateLimitExceeded { .. } => {
|
||||
// In the future we will report the error to the user, wait retry_after, and then retry.
|
||||
emit_generic_error(error, cx);
|
||||
}
|
||||
LanguageModelKnownError::Overloaded => {
|
||||
// In the future we will wait and then retry, up to N times.
|
||||
emit_generic_error(error, cx);
|
||||
}
|
||||
LanguageModelKnownError::ApiInternalServerError => {
|
||||
// In the future we will retry the request, but only once.
|
||||
emit_generic_error(error, cx);
|
||||
}
|
||||
LanguageModelKnownError::ReadResponseError(_) |
|
||||
LanguageModelKnownError::DeserializeResponse(_) |
|
||||
LanguageModelKnownError::UnknownResponseFormat(_) => {
|
||||
// In the future we will attempt to re-roll response, but only once
|
||||
emit_generic_error(error, cx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let error_message = error
|
||||
.chain()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::Message {
|
||||
header: "Error interacting with language model".into(),
|
||||
message: SharedString::from(error_message.clone()),
|
||||
}));
|
||||
emit_generic_error(error, cx);
|
||||
}
|
||||
|
||||
thread.cancel_last_completion(window, cx);
|
||||
@@ -1893,11 +1952,8 @@ impl Thread {
|
||||
LanguageModelCompletionEvent::StatusUpdate(
|
||||
CompletionRequestStatus::UsageUpdated { amount, limit },
|
||||
) => {
|
||||
this.update(cx, |thread, _cx| {
|
||||
thread.last_usage = Some(RequestUsage {
|
||||
limit,
|
||||
amount: amount as i32,
|
||||
});
|
||||
this.update(cx, |thread, cx| {
|
||||
thread.update_model_request_usage(amount as u32, limit, cx);
|
||||
})?;
|
||||
continue;
|
||||
}
|
||||
@@ -2779,6 +2835,20 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut Context<Self>) {
|
||||
self.project.update(cx, |project, cx| {
|
||||
project.user_store().update(cx, |user_store, cx| {
|
||||
user_store.update_model_request_usage(
|
||||
ModelRequestUsage(RequestUsage {
|
||||
amount: amount as i32,
|
||||
limit,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deny_tool_use(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
@@ -2948,11 +3018,13 @@ fn resolve_tool_name_conflicts(tools: &[Arc<dyn Tool>]) -> Vec<(String, Arc<dyn
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
|
||||
use crate::{
|
||||
context::load_context, context_store::ContextStore, thread_store, thread_store::ThreadStore,
|
||||
};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelParameters};
|
||||
use assistant_tool::ToolRegistry;
|
||||
use editor::EditorSettings;
|
||||
use gpui::TestAppContext;
|
||||
use icons::IconName;
|
||||
use language_model::fake_provider::{FakeLanguageModel, FakeLanguageModelProvider};
|
||||
use project::{FakeFs, Project};
|
||||
use prompt_store::PromptBuilder;
|
||||
@@ -2960,7 +3032,6 @@ mod tests {
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use theme::ThemeSettings;
|
||||
use ui::IconName;
|
||||
use util::path;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -3788,7 +3859,6 @@ fn main() {{
|
||||
workspace::init_settings(cx);
|
||||
language_model::init_settings(cx);
|
||||
ThemeSettings::register(cx);
|
||||
EditorSettings::register(cx);
|
||||
ToolRegistry::default_global(cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::{
|
||||
context_server_tool::ContextServerTool,
|
||||
thread::{
|
||||
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
||||
},
|
||||
};
|
||||
use agent_settings::{AgentProfileId, CompletionMode};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::future::{self, BoxFuture, Shared};
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use futures::{
|
||||
FutureExt as _, StreamExt as _,
|
||||
channel::{mpsc, oneshot},
|
||||
future::{self, BoxFuture, Shared},
|
||||
};
|
||||
use gpui::{
|
||||
App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,
|
||||
Subscription, Task, prelude::*,
|
||||
Subscription, Task, Window, prelude::*,
|
||||
};
|
||||
|
||||
use indoc::indoc;
|
||||
use language_model::{LanguageModelToolResultContent, LanguageModelToolUseId, Role, TokenUsage};
|
||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||
@@ -25,19 +28,18 @@ use prompt_store::{
|
||||
UserRulesContext, WorktreeContext,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::Window;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::context_server_tool::ContextServerTool;
|
||||
use crate::thread::{
|
||||
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use sqlez::{
|
||||
bindable::{Bind, Column},
|
||||
connection::Connection,
|
||||
statement::Statement,
|
||||
};
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DataType {
|
||||
@@ -94,7 +96,7 @@ impl SharedProjectContext {
|
||||
}
|
||||
}
|
||||
|
||||
pub type TextThreadStore = assistant_context_editor::ContextStore;
|
||||
pub type TextThreadStore = assistant_context::ContextStore;
|
||||
|
||||
pub struct ThreadStore {
|
||||
project: Entity<Project>,
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
thread::{MessageId, PromptId, ThreadId},
|
||||
thread_store::SerializedMessage,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_tool::{
|
||||
AnyToolCard, Tool, ToolResultContent, ToolResultOutput, ToolUseStatus, ToolWorkingSet,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use futures::FutureExt as _;
|
||||
use futures::future::Shared;
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use futures::{FutureExt as _, future::Shared};
|
||||
use gpui::{App, Entity, SharedString, Task, Window};
|
||||
use icons::IconName;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelRequest, LanguageModelToolResult,
|
||||
LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId, Role,
|
||||
};
|
||||
use project::Project;
|
||||
use ui::{IconName, Window};
|
||||
use std::sync::Arc;
|
||||
use util::truncate_lines_to_byte_limit;
|
||||
|
||||
use crate::thread::{MessageId, PromptId, ThreadId};
|
||||
use crate::thread_store::SerializedMessage;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
@@ -26,7 +25,7 @@ pub struct ToolUse {
|
||||
pub ui_text: SharedString,
|
||||
pub status: ToolUseStatus,
|
||||
pub input: serde_json::Value,
|
||||
pub icon: ui::IconName,
|
||||
pub icon: icons::IconName,
|
||||
pub needs_confirmation: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -734,7 +734,6 @@ impl JsonSchema for LanguageModelProviderSetting {
|
||||
"deepseek".into(),
|
||||
"openrouter".into(),
|
||||
"mistral".into(),
|
||||
"vercel".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
110
crates/agent_ui/Cargo.toml
Normal file
110
crates/agent_ui/Cargo.toml
Normal file
@@ -0,0 +1,110 @@
|
||||
[package]
|
||||
name = "agent_ui"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/agent_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"gpui/test-support",
|
||||
"language/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
agent.workspace = true
|
||||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
audio.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
component.workspace = true
|
||||
context_server.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
indoc.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
inventory.workspace = true
|
||||
itertools.workspace = true
|
||||
jsonschema.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
notifications.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rules_library.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
urlencoding.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
watch.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assistant_tools.workspace = true
|
||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
@@ -1,18 +1,17 @@
|
||||
use crate::context::{AgentContextHandle, RULES_ICON};
|
||||
use crate::context_picker::{ContextPicker, MentionLink};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::message_editor::{extract_message_creases, insert_message_creases};
|
||||
use crate::thread::{
|
||||
LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
|
||||
ThreadEvent, ThreadFeedback, ThreadSummary,
|
||||
};
|
||||
use crate::thread_store::{RulesLoadingError, TextThreadStore, ThreadStore};
|
||||
use crate::tool_use::{PendingToolUseStatus, ToolUse};
|
||||
use crate::ui::{
|
||||
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
|
||||
};
|
||||
use crate::{AgentPanel, ModelUsageContext};
|
||||
use agent::{
|
||||
ContextStore, LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, TextThreadStore,
|
||||
Thread, ThreadError, ThreadEvent, ThreadFeedback, ThreadStore, ThreadSummary,
|
||||
context::{self, AgentContextHandle, RULES_ICON},
|
||||
thread_store::RulesLoadingError,
|
||||
tool_use::{PendingToolUseStatus, ToolUse},
|
||||
};
|
||||
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
|
||||
use anyhow::Context as _;
|
||||
use assistant_tool::ToolUseStatus;
|
||||
@@ -24,7 +23,7 @@ use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer};
|
||||
use gpui::{
|
||||
AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
|
||||
ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
|
||||
ListAlignment, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful,
|
||||
ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful,
|
||||
StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation,
|
||||
UnderlineStyle, WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, percentage,
|
||||
pulsating_between,
|
||||
@@ -48,8 +47,8 @@ use std::time::Duration;
|
||||
use text::ToPoint;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Disclosure, IconButton, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
|
||||
Tooltip, prelude::*,
|
||||
Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize, Tooltip,
|
||||
prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
@@ -57,6 +56,9 @@ use workspace::{CollaboratorId, Workspace};
|
||||
use zed_actions::assistant::OpenRulesLibrary;
|
||||
use zed_llm_client::CompletionIntent;
|
||||
|
||||
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
|
||||
const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
|
||||
|
||||
pub struct ActiveThread {
|
||||
context_store: Entity<ContextStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
@@ -300,7 +302,7 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
|
||||
base_text_style: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
code_block_overflow_x_scroll: true,
|
||||
code_block_overflow_x_scroll: false,
|
||||
code_block: StyleRefinement {
|
||||
margin: EdgesRefinement::default(),
|
||||
padding: EdgesRefinement::default(),
|
||||
@@ -334,8 +336,6 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
|
||||
}
|
||||
}
|
||||
|
||||
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
|
||||
|
||||
fn render_markdown_code_block(
|
||||
message_id: MessageId,
|
||||
ix: usize,
|
||||
@@ -1327,6 +1327,8 @@ impl ActiveThread {
|
||||
self.context_store.downgrade(),
|
||||
self.thread_store.downgrade(),
|
||||
self.text_thread_store.downgrade(),
|
||||
EDIT_PREVIOUS_MESSAGE_MIN_LINES,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -1580,8 +1582,7 @@ impl ActiveThread {
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
|
||||
|
||||
let load_context_task =
|
||||
crate::context::load_context(new_context, &project, &prompt_store, cx);
|
||||
let load_context_task = context::load_context(new_context, &project, &prompt_store, cx);
|
||||
self._load_edited_message_context_task =
|
||||
Some(cx.spawn_in(window, async move |this, cx| {
|
||||
let (context, checkpoint) =
|
||||
@@ -1618,6 +1619,14 @@ impl ActiveThread {
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||
panel.focus_handle(cx).focus(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
|
||||
@@ -1683,7 +1692,7 @@ impl ActiveThread {
|
||||
let mut editor = Editor::new(
|
||||
editor::EditorMode::AutoHeight {
|
||||
min_lines: 1,
|
||||
max_lines: 4,
|
||||
max_lines: Some(4),
|
||||
},
|
||||
buffer,
|
||||
None,
|
||||
@@ -1726,7 +1735,7 @@ impl ActiveThread {
|
||||
telemetry::event!(
|
||||
"Assistant Thread Feedback Comments",
|
||||
thread_id,
|
||||
message_id = message_id.0,
|
||||
message_id = message_id.as_usize(),
|
||||
message_content,
|
||||
comments = comments_value
|
||||
);
|
||||
@@ -1861,6 +1870,14 @@ impl ActiveThread {
|
||||
}
|
||||
});
|
||||
|
||||
let scroll_to_top = IconButton::new(("scroll_to_top", ix), IconName::ArrowUpAlt)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Ignored)
|
||||
.tooltip(Tooltip::text("Scroll To Top"))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.scroll_to_top(cx);
|
||||
}));
|
||||
|
||||
// For all items that should be aligned with the LLM's response.
|
||||
const RESPONSE_PADDING_X: Pixels = px(19.);
|
||||
|
||||
@@ -1970,11 +1987,14 @@ impl ActiveThread {
|
||||
);
|
||||
})),
|
||||
)
|
||||
.child(open_as_markdown),
|
||||
.child(open_as_markdown)
|
||||
.child(scroll_to_top),
|
||||
)
|
||||
.into_any_element(),
|
||||
None => feedback_container
|
||||
.child(h_flex().child(open_as_markdown))
|
||||
.child(h_flex()
|
||||
.child(open_as_markdown))
|
||||
.child(scroll_to_top)
|
||||
.into_any_element(),
|
||||
};
|
||||
|
||||
@@ -3088,6 +3108,7 @@ impl ActiveThread {
|
||||
.pr_1()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.flex_wrap()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_t_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
@@ -3463,6 +3484,11 @@ impl ActiveThread {
|
||||
*is_expanded = !*is_expanded;
|
||||
}
|
||||
|
||||
pub fn scroll_to_top(&mut self, cx: &mut Context<Self>) {
|
||||
self.list_state.scroll_to(ListOffset::default());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn scroll_to_bottom(&mut self, cx: &mut Context<Self>) {
|
||||
self.list_state.reset(self.messages.len());
|
||||
cx.notify();
|
||||
@@ -3695,8 +3721,10 @@ fn open_editor_at_position(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use agent::{MessageSegment, context::ContextLoadResult, thread_store};
|
||||
use assistant_tool::{ToolRegistry, ToolWorkingSet};
|
||||
use editor::{EditorSettings, display_map::CreaseMetadata};
|
||||
use editor::EditorSettings;
|
||||
use fs::FakeFs;
|
||||
use gpui::{AppContext, TestAppContext, VisualTestContext};
|
||||
use language_model::{
|
||||
@@ -3710,10 +3738,6 @@ mod tests {
|
||||
use util::path;
|
||||
use workspace::CollaboratorId;
|
||||
|
||||
use crate::{ContextLoadResult, thread::MessageSegment, thread_store};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_agent_is_unfollowed_after_cancelling_completion(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
@@ -3785,10 +3809,8 @@ mod tests {
|
||||
|
||||
let creases = vec![MessageCrease {
|
||||
range: 14..22,
|
||||
metadata: CreaseMetadata {
|
||||
icon_path: "icon".into(),
|
||||
label: "foo.txt".into(),
|
||||
},
|
||||
icon_path: "icon".into(),
|
||||
label: "foo.txt".into(),
|
||||
context: None,
|
||||
}];
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod add_context_server_modal;
|
||||
mod configure_context_server_modal;
|
||||
mod manage_profiles_modal;
|
||||
mod tool_picker;
|
||||
@@ -9,22 +8,29 @@ use agent_settings::AgentSettings;
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
use extension::ExtensionManifest;
|
||||
use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt as _, AnyView, App, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, ScrollHandle, Subscription, Transformation, percentage,
|
||||
Action, Animation, AnimationExt as _, AnyView, App, Corner, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use ui::{
|
||||
Disclosure, ElevationIndex, Indicator, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip,
|
||||
prelude::*,
|
||||
ContextMenu, Disclosure, ElevationIndex, Indicator, PopoverMenu, Scrollbar, ScrollbarState,
|
||||
Switch, SwitchColor, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::ExtensionCategoryFilter;
|
||||
|
||||
pub(crate) use add_context_server_modal::AddContextServerModal;
|
||||
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
|
||||
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
||||
|
||||
@@ -32,6 +38,8 @@ use crate::AddContextServer;
|
||||
|
||||
pub struct AgentConfiguration {
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
@@ -48,6 +56,8 @@ impl AgentConfiguration {
|
||||
fs: Arc<dyn Fs>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -70,11 +80,16 @@ impl AgentConfiguration {
|
||||
},
|
||||
);
|
||||
|
||||
cx.subscribe(&context_server_store, |_, _, _, cx| cx.notify())
|
||||
.detach();
|
||||
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||
|
||||
let mut this = Self {
|
||||
fs,
|
||||
language_registry,
|
||||
workspace,
|
||||
focus_handle,
|
||||
configuration_views_by_provider: HashMap::default(),
|
||||
context_server_store,
|
||||
@@ -133,6 +148,8 @@ impl AgentConfiguration {
|
||||
) -> impl IntoElement + use<> {
|
||||
let provider_id = provider.id().0.clone();
|
||||
let provider_name = provider.name().0.clone();
|
||||
let provider_id_string = SharedString::from(format!("provider-disclosure-{provider_id}"));
|
||||
|
||||
let configuration_view = self
|
||||
.configuration_views_by_provider
|
||||
.get(&provider.id())
|
||||
@@ -145,72 +162,80 @@ impl AgentConfiguration {
|
||||
.unwrap_or(false);
|
||||
|
||||
v_flex()
|
||||
.pt_3()
|
||||
.py_2()
|
||||
.gap_1p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.6))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.id(provider_id_string.clone())
|
||||
.cursor_pointer()
|
||||
.py_0p5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.rounded_sm()
|
||||
.hover(|hover| hover.bg(cx.theme().colors().element_hover))
|
||||
.child(
|
||||
Icon::new(provider.icon())
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Label::new(provider_name.clone()).size(LabelSize::Large))
|
||||
.when(provider.is_authenticated(cx) && !is_expanded, |parent| {
|
||||
parent.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.when(provider.is_authenticated(cx), |parent| {
|
||||
parent.child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-thread-{provider_id}")),
|
||||
"Start New Thread",
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(provider.icon())
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, _window, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
})),
|
||||
)
|
||||
})
|
||||
.child(Label::new(provider_name.clone()).size(LabelSize::Large))
|
||||
.when(
|
||||
provider.is_authenticated(cx) && !is_expanded,
|
||||
|parent| {
|
||||
parent.child(
|
||||
Icon::new(IconName::Check).color(Color::Success),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Disclosure::new(
|
||||
SharedString::from(format!(
|
||||
"provider-disclosure-{provider_id}"
|
||||
)),
|
||||
is_expanded,
|
||||
)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.on_click(cx.listener({
|
||||
let provider_id = provider.id().clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_expanded = this
|
||||
.expanded_provider_configurations
|
||||
.entry(provider_id.clone())
|
||||
.or_insert(false);
|
||||
Disclosure::new(provider_id_string, is_expanded)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown),
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let provider_id = provider.id().clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_expanded = this
|
||||
.expanded_provider_configurations
|
||||
.entry(provider_id.clone())
|
||||
.or_insert(false);
|
||||
|
||||
*is_expanded = !*is_expanded;
|
||||
}
|
||||
})),
|
||||
),
|
||||
),
|
||||
*is_expanded = !*is_expanded;
|
||||
}
|
||||
})),
|
||||
)
|
||||
.when(provider.is_authenticated(cx), |parent| {
|
||||
parent.child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-thread-{provider_id}")),
|
||||
"Start New Thread",
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, _window, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
})),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(is_expanded, |parent| match configuration_view {
|
||||
Some(configuration_view) => parent.child(configuration_view),
|
||||
@@ -229,11 +254,11 @@ impl AgentConfiguration {
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.pr(DynamicSpacing::Base20.rems(cx))
|
||||
.gap_4()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
v_flex()
|
||||
.mb_2p5()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("LLM Providers"))
|
||||
.child(
|
||||
@@ -460,9 +485,22 @@ impl AgentConfiguration {
|
||||
.read(cx)
|
||||
.status_for_server(&context_server_id)
|
||||
.unwrap_or(ContextServerStatus::Stopped);
|
||||
let server_configuration = self
|
||||
.context_server_store
|
||||
.read(cx)
|
||||
.configuration_for_server(&context_server_id);
|
||||
|
||||
let is_running = matches!(server_status, ContextServerStatus::Running);
|
||||
let item_id = SharedString::from(context_server_id.0.clone());
|
||||
let is_from_extension = server_configuration
|
||||
.as_ref()
|
||||
.map(|config| {
|
||||
matches!(
|
||||
config.as_ref(),
|
||||
ContextServerConfiguration::Extension { .. }
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let error = if let ContextServerStatus::Error(error) = server_status.clone() {
|
||||
Some(error)
|
||||
@@ -484,6 +522,18 @@ impl AgentConfiguration {
|
||||
|
||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||
|
||||
let (source_icon, source_tooltip) = if is_from_extension {
|
||||
(
|
||||
IconName::ZedMcpExtension,
|
||||
"This MCP server was installed from an extension.",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
IconName::ZedMcpCustom,
|
||||
"This custom MCP server was installed directly.",
|
||||
)
|
||||
};
|
||||
|
||||
let (status_indicator, tooltip_text) = match server_status {
|
||||
ContextServerStatus::Starting => (
|
||||
Icon::new(IconName::LoadCircle)
|
||||
@@ -511,6 +561,105 @@ impl AgentConfiguration {
|
||||
),
|
||||
};
|
||||
|
||||
let context_server_configuration_menu = PopoverMenu::new("context-server-config-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("context-server-config-menu", IconName::Settings)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small),
|
||||
Tooltip::text("Open MCP server options"),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.menu({
|
||||
let fs = self.fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let language_registry = self.language_registry.clone();
|
||||
let context_server_store = self.context_server_store.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.entry("Configure Server", None, {
|
||||
let context_server_id = context_server_id.clone();
|
||||
let language_registry = language_registry.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |window, cx| {
|
||||
ConfigureContextServerModal::show_modal_for_existing_server(
|
||||
context_server_id.clone(),
|
||||
language_registry.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.separator()
|
||||
.entry("Uninstall", None, {
|
||||
let fs = fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let context_server_store = context_server_store.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |_, cx| {
|
||||
let is_provided_by_extension = context_server_store
|
||||
.read(cx)
|
||||
.configuration_for_server(&context_server_id)
|
||||
.as_ref()
|
||||
.map(|config| {
|
||||
matches!(
|
||||
config.as_ref(),
|
||||
ContextServerConfiguration::Extension { .. }
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let uninstall_extension_task = match (
|
||||
is_provided_by_extension,
|
||||
resolve_extension_for_context_server(&context_server_id, cx),
|
||||
) {
|
||||
(true, Some((id, manifest))) => {
|
||||
if extension_only_provides_context_server(manifest.as_ref())
|
||||
{
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.uninstall_extension(id, cx)
|
||||
})
|
||||
} else {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
show_unable_to_uninstall_extension_with_context_server(workspace, context_server_id.clone(), cx);
|
||||
}).log_err();
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
_ => Task::ready(Ok(())),
|
||||
};
|
||||
|
||||
cx.spawn({
|
||||
let fs = fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |cx| {
|
||||
uninstall_extension_task.await?;
|
||||
cx.update(|cx| {
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
{
|
||||
let context_server_id =
|
||||
context_server_id.clone();
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.id(item_id.clone())
|
||||
.border_1()
|
||||
@@ -556,7 +705,19 @@ impl AgentConfiguration {
|
||||
.tooltip(Tooltip::text(tooltip_text))
|
||||
.child(status_indicator),
|
||||
)
|
||||
.child(Label::new(item_id).ml_0p5().mr_1p5())
|
||||
.child(Label::new(item_id).ml_0p5())
|
||||
.child(
|
||||
div()
|
||||
.id("extension-source")
|
||||
.mt_0p5()
|
||||
.mx_1()
|
||||
.tooltip(Tooltip::text(source_tooltip))
|
||||
.child(
|
||||
Icon::new(source_icon)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.when(is_running, |this| {
|
||||
this.child(
|
||||
Label::new(if tool_count == 1 {
|
||||
@@ -570,28 +731,72 @@ impl AgentConfiguration {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager = self.context_server_store.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
move |state, _window, cx| match state {
|
||||
ToggleState::Unselected | ToggleState::Indeterminate => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
this.stop_server(&context_server_id, cx).log_err();
|
||||
});
|
||||
}
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}),
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(context_server_configuration_menu)
|
||||
.child(
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager =
|
||||
self.context_server_store.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let fs = self.fs.clone();
|
||||
|
||||
move |state, _window, cx| {
|
||||
let is_enabled = match state {
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => {
|
||||
context_server_manager.update(
|
||||
cx,
|
||||
|this, cx| {
|
||||
this.stop_server(
|
||||
&context_server_id,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
},
|
||||
);
|
||||
false
|
||||
}
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(
|
||||
cx,
|
||||
|this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx);
|
||||
}
|
||||
},
|
||||
);
|
||||
true
|
||||
}
|
||||
};
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
{
|
||||
let context_server_id =
|
||||
context_server_id.clone();
|
||||
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.entry(context_server_id.0)
|
||||
.or_insert_with(|| {
|
||||
ContextServerSettings::Extension {
|
||||
enabled: is_enabled,
|
||||
settings: serde_json::json!({}),
|
||||
}
|
||||
})
|
||||
.set_enabled(is_enabled);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
@@ -701,3 +906,92 @@ impl Render for AgentConfiguration {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn extension_only_provides_context_server(manifest: &ExtensionManifest) -> bool {
|
||||
manifest.context_servers.len() == 1
|
||||
&& manifest.themes.is_empty()
|
||||
&& manifest.icon_themes.is_empty()
|
||||
&& manifest.languages.is_empty()
|
||||
&& manifest.grammars.is_empty()
|
||||
&& manifest.language_servers.is_empty()
|
||||
&& manifest.slash_commands.is_empty()
|
||||
&& manifest.indexed_docs_providers.is_empty()
|
||||
&& manifest.snippets.is_none()
|
||||
&& manifest.debug_locators.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_extension_for_context_server(
|
||||
id: &ContextServerId,
|
||||
cx: &App,
|
||||
) -> Option<(Arc<str>, Arc<ExtensionManifest>)> {
|
||||
ExtensionStore::global(cx)
|
||||
.read(cx)
|
||||
.installed_extensions()
|
||||
.iter()
|
||||
.find(|(_, entry)| entry.manifest.context_servers.contains_key(&id.0))
|
||||
.map(|(id, entry)| (id.clone(), entry.manifest.clone()))
|
||||
}
|
||||
|
||||
// This notification appears when trying to delete
|
||||
// an MCP server extension that not only provides
|
||||
// the server, but other things, too, like language servers and more.
|
||||
fn show_unable_to_uninstall_extension_with_context_server(
|
||||
workspace: &mut Workspace,
|
||||
id: ContextServerId,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let context_server_id = id.clone();
|
||||
|
||||
let status_toast = StatusToast::new(
|
||||
format!(
|
||||
"The {} extension provides more than just the MCP server. Proceed to uninstall anyway?",
|
||||
id.0
|
||||
),
|
||||
cx,
|
||||
move |this, _cx| {
|
||||
let workspace_handle = workspace_handle.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
|
||||
this.icon(ToastIcon::new(IconName::Warning).color(Color::Warning))
|
||||
.dismiss_button(true)
|
||||
.action("Uninstall", move |_, _cx| {
|
||||
if let Some((extension_id, _)) =
|
||||
resolve_extension_for_context_server(&context_server_id, _cx)
|
||||
{
|
||||
ExtensionStore::global(_cx).update(_cx, |store, cx| {
|
||||
store
|
||||
.uninstall_extension(extension_id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
|
||||
workspace_handle
|
||||
.update(_cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
cx.spawn({
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |_workspace_handle, cx| {
|
||||
cx.update(|cx| {
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
},
|
||||
);
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use context_server::{ContextServerCommand, ContextServerId};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{
|
||||
Animation, AnimationExt as _, AsyncWindowContext, DismissEvent, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle,
|
||||
WeakEntity, percentage, prelude::*,
|
||||
};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{
|
||||
ContextServerStatus, ContextServerStore, registry::ContextServerDescriptorRegistry,
|
||||
},
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
worktree_store::WorktreeStore,
|
||||
};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::AddContextServer;
|
||||
|
||||
enum ConfigurationTarget {
|
||||
New,
|
||||
Existing {
|
||||
id: ContextServerId,
|
||||
command: ContextServerCommand,
|
||||
},
|
||||
Extension {
|
||||
id: ContextServerId,
|
||||
repository_url: Option<SharedString>,
|
||||
installation: Option<extension::ContextServerConfiguration>,
|
||||
},
|
||||
}
|
||||
|
||||
enum ConfigurationSource {
|
||||
New {
|
||||
editor: Entity<Editor>,
|
||||
},
|
||||
Existing {
|
||||
editor: Entity<Editor>,
|
||||
},
|
||||
Extension {
|
||||
id: ContextServerId,
|
||||
editor: Option<Entity<Editor>>,
|
||||
repository_url: Option<SharedString>,
|
||||
installation_instructions: Option<Entity<markdown::Markdown>>,
|
||||
settings_validator: Option<jsonschema::Validator>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ConfigurationSource {
|
||||
fn has_configuration_options(&self) -> bool {
|
||||
!matches!(self, ConfigurationSource::Extension { editor: None, .. })
|
||||
}
|
||||
|
||||
fn is_new(&self) -> bool {
|
||||
matches!(self, ConfigurationSource::New { .. })
|
||||
}
|
||||
|
||||
fn from_target(
|
||||
target: ConfigurationTarget,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
jsonc_language: Option<Arc<Language>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
fn create_editor(
|
||||
json: String,
|
||||
jsonc_language: Option<Arc<Language>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Editor> {
|
||||
cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(4, 16, window, cx);
|
||||
editor.set_text(json, window, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
|
||||
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| buffer.set_language(jsonc_language, cx))
|
||||
}
|
||||
editor
|
||||
})
|
||||
}
|
||||
|
||||
match target {
|
||||
ConfigurationTarget::New => ConfigurationSource::New {
|
||||
editor: create_editor(context_server_input(None), jsonc_language, window, cx),
|
||||
},
|
||||
ConfigurationTarget::Existing { id, command } => ConfigurationSource::Existing {
|
||||
editor: create_editor(
|
||||
context_server_input(Some((id, command))),
|
||||
jsonc_language,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
},
|
||||
ConfigurationTarget::Extension {
|
||||
id,
|
||||
repository_url,
|
||||
installation,
|
||||
} => {
|
||||
let settings_validator = installation.as_ref().and_then(|installation| {
|
||||
jsonschema::validator_for(&installation.settings_schema)
|
||||
.context("Failed to load JSON schema for context server settings")
|
||||
.log_err()
|
||||
});
|
||||
let installation_instructions = installation.as_ref().map(|installation| {
|
||||
cx.new(|cx| {
|
||||
Markdown::new(
|
||||
installation.installation_instructions.clone().into(),
|
||||
Some(language_registry.clone()),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
ConfigurationSource::Extension {
|
||||
id,
|
||||
repository_url,
|
||||
installation_instructions,
|
||||
settings_validator,
|
||||
editor: installation.map(|installation| {
|
||||
create_editor(installation.default_settings, jsonc_language, window, cx)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn output(&self, cx: &mut App) -> Result<(ContextServerId, ContextServerSettings)> {
|
||||
match self {
|
||||
ConfigurationSource::New { editor } | ConfigurationSource::Existing { editor } => {
|
||||
parse_input(&editor.read(cx).text(cx)).map(|(id, command)| {
|
||||
(
|
||||
id,
|
||||
ContextServerSettings::Custom {
|
||||
enabled: true,
|
||||
command,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
ConfigurationSource::Extension {
|
||||
id,
|
||||
editor,
|
||||
settings_validator,
|
||||
..
|
||||
} => {
|
||||
let text = editor
|
||||
.as_ref()
|
||||
.context("No output available")?
|
||||
.read(cx)
|
||||
.text(cx);
|
||||
let settings = serde_json_lenient::from_str::<serde_json::Value>(&text)?;
|
||||
if let Some(settings_validator) = settings_validator {
|
||||
if let Err(error) = settings_validator.validate(&settings) {
|
||||
return Err(anyhow::anyhow!(error.to_string()));
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
id.clone(),
|
||||
ContextServerSettings::Extension {
|
||||
enabled: true,
|
||||
settings,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)>) -> String {
|
||||
let (name, path, args, env) = match existing {
|
||||
Some((id, cmd)) => {
|
||||
let args = serde_json::to_string(&cmd.args).unwrap();
|
||||
let env = serde_json::to_string(&cmd.env.unwrap_or_default()).unwrap();
|
||||
(id.0.to_string(), cmd.path, args, env)
|
||||
}
|
||||
None => (
|
||||
"some-mcp-server".to_string(),
|
||||
"".to_string(),
|
||||
"[]".to_string(),
|
||||
"{}".to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"{{
|
||||
/// The name of your MCP server
|
||||
"{name}": {{
|
||||
"command": {{
|
||||
/// The path to the executable
|
||||
"path": "{path}",
|
||||
/// The arguments to pass to the executable
|
||||
"args": {args},
|
||||
/// The environment variables to set for the executable
|
||||
"env": {env}
|
||||
}}
|
||||
}}
|
||||
}}"#
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_context_server_extension(
|
||||
id: ContextServerId,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut App,
|
||||
) -> Task<Option<ConfigurationTarget>> {
|
||||
let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
|
||||
|
||||
let Some(descriptor) = registry.context_server_descriptor(&id.0) else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
|
||||
let extension = crate::agent_configuration::resolve_extension_for_context_server(&id, cx);
|
||||
cx.spawn(async move |cx| {
|
||||
let installation = descriptor
|
||||
.configuration(worktree_store, cx)
|
||||
.await
|
||||
.context("Failed to resolve context server configuration")
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
Some(ConfigurationTarget::Extension {
|
||||
id,
|
||||
repository_url: extension
|
||||
.and_then(|(_, manifest)| manifest.repository.clone().map(SharedString::from)),
|
||||
installation,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
enum State {
|
||||
Idle,
|
||||
Waiting,
|
||||
Error(SharedString),
|
||||
}
|
||||
|
||||
pub struct ConfigureContextServerModal {
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
source: ConfigurationSource,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
pub fn register(
|
||||
workspace: &mut Workspace,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
_window: Option<&mut Window>,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.register_action({
|
||||
let language_registry = language_registry.clone();
|
||||
move |_workspace, _: &AddContextServer, window, cx| {
|
||||
let workspace_handle = cx.weak_entity();
|
||||
let language_registry = language_registry.clone();
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
Self::show_modal(
|
||||
ConfigurationTarget::New,
|
||||
language_registry,
|
||||
workspace_handle,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_modal_for_existing_server(
|
||||
server_id: ContextServerId,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(settings) = ProjectSettings::get_global(cx)
|
||||
.context_servers
|
||||
.get(&server_id.0)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
ContextServerDescriptorRegistry::default_global(cx)
|
||||
.read(cx)
|
||||
.context_server_descriptor(&server_id.0)
|
||||
.map(|_| ContextServerSettings::Extension {
|
||||
enabled: true,
|
||||
settings: serde_json::json!({}),
|
||||
})
|
||||
})
|
||||
else {
|
||||
return Task::ready(Err(anyhow::anyhow!("Context server not found")));
|
||||
};
|
||||
|
||||
window.spawn(cx, async move |cx| {
|
||||
let target = match settings {
|
||||
ContextServerSettings::Custom {
|
||||
enabled: _,
|
||||
command,
|
||||
} => Some(ConfigurationTarget::Existing {
|
||||
id: server_id,
|
||||
command,
|
||||
}),
|
||||
ContextServerSettings::Extension { .. } => {
|
||||
match workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
resolve_context_server_extension(
|
||||
server_id,
|
||||
workspace.project().read(cx).worktree_store(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
{
|
||||
Some(task) => task.await,
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match target {
|
||||
Some(target) => Self::show_modal(target, language_registry, workspace, cx).await,
|
||||
None => Err(anyhow::anyhow!("Failed to resolve context server")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn show_modal(
|
||||
target: ConfigurationTarget,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let jsonc_language = language_registry.language_for_name("jsonc").await.ok();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let workspace_handle = cx.weak_entity();
|
||||
let context_server_store = workspace.project().read(cx).context_server_store();
|
||||
workspace.toggle_modal(window, cx, |window, cx| Self {
|
||||
context_server_store,
|
||||
workspace: workspace_handle,
|
||||
state: State::Idle,
|
||||
source: ConfigurationSource::from_target(
|
||||
target,
|
||||
language_registry,
|
||||
jsonc_language,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn set_error(&mut self, err: impl Into<SharedString>, cx: &mut Context<Self>) {
|
||||
self.state = State::Error(err.into());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut Context<Self>) {
|
||||
self.state = State::Idle;
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (id, settings) = match self.source.output(cx) {
|
||||
Ok(val) => val,
|
||||
Err(error) => {
|
||||
self.set_error(error.to_string(), cx);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.state = State::Waiting;
|
||||
let wait_for_context_server_task =
|
||||
wait_for_context_server(&self.context_server_store, id.clone(), cx);
|
||||
cx.spawn({
|
||||
let id = id.clone();
|
||||
async move |this, cx| {
|
||||
let result = wait_for_context_server_task.await;
|
||||
this.update(cx, |this, cx| match result {
|
||||
Ok(_) => {
|
||||
this.state = State::Idle;
|
||||
this.show_configured_context_server_toast(id, cx);
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
Err(err) => {
|
||||
this.set_error(err, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// When we write the settings to the file, the context server will be restarted.
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
update_settings_file::<ProjectSettings>(fs.clone(), cx, |project_settings, _| {
|
||||
project_settings.context_servers.insert(id.0, settings);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn show_configured_context_server_toast(&self, id: ContextServerId, cx: &mut App) {
|
||||
self.workspace
|
||||
.update(cx, {
|
||||
|workspace, cx| {
|
||||
let status_toast = StatusToast::new(
|
||||
format!("{} configured successfully.", id.0),
|
||||
cx,
|
||||
|this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Hammer).color(Color::Muted))
|
||||
.action("Dismiss", |_, _| {})
|
||||
},
|
||||
);
|
||||
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_input(text: &str) -> Result<(ContextServerId, ContextServerCommand)> {
|
||||
let value: serde_json::Value = serde_json_lenient::from_str(text)?;
|
||||
let object = value.as_object().context("Expected object")?;
|
||||
anyhow::ensure!(object.len() == 1, "Expected exactly one key-value pair");
|
||||
let (context_server_name, value) = object.into_iter().next().unwrap();
|
||||
let command = value.get("command").context("Expected command")?;
|
||||
let command: ContextServerCommand = serde_json::from_value(command.clone())?;
|
||||
Ok((ContextServerId(context_server_name.clone().into()), command))
|
||||
}
|
||||
|
||||
impl ModalView for ConfigureContextServerModal {}
|
||||
|
||||
impl Focusable for ConfigureContextServerModal {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match &self.source {
|
||||
ConfigurationSource::New { editor } => editor.focus_handle(cx),
|
||||
ConfigurationSource::Existing { editor, .. } => editor.focus_handle(cx),
|
||||
ConfigurationSource::Extension { editor, .. } => editor
|
||||
.as_ref()
|
||||
.map(|editor| editor.focus_handle(cx))
|
||||
.unwrap_or_else(|| cx.focus_handle()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ConfigureContextServerModal {}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
fn render_modal_header(&self) -> ModalHeader {
|
||||
let text: SharedString = match &self.source {
|
||||
ConfigurationSource::New { .. } => "Add MCP Server".into(),
|
||||
ConfigurationSource::Existing { .. } => "Configure MCP Server".into(),
|
||||
ConfigurationSource::Extension { id, .. } => format!("Configure {}", id.0).into(),
|
||||
};
|
||||
ModalHeader::new().headline(text)
|
||||
}
|
||||
|
||||
fn render_modal_description(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
const MODAL_DESCRIPTION: &'static str = "Visit the MCP server configuration docs to find all necessary arguments and environment variables.";
|
||||
|
||||
if let ConfigurationSource::Extension {
|
||||
installation_instructions: Some(installation_instructions),
|
||||
..
|
||||
} = &self.source
|
||||
{
|
||||
div()
|
||||
.pb_2()
|
||||
.text_sm()
|
||||
.child(MarkdownElement::new(
|
||||
installation_instructions.clone(),
|
||||
default_markdown_style(window, cx),
|
||||
))
|
||||
.into_any_element()
|
||||
} else {
|
||||
Label::new(MODAL_DESCRIPTION)
|
||||
.color(Color::Muted)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_modal_content(&self, cx: &App) -> AnyElement {
|
||||
let editor = match &self.source {
|
||||
ConfigurationSource::New { editor } => editor,
|
||||
ConfigurationSource::Existing { editor } => editor,
|
||||
ConfigurationSource::Extension { editor, .. } => {
|
||||
let Some(editor) = editor else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
editor
|
||||
}
|
||||
};
|
||||
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
let is_connecting = matches!(self.state, State::Waiting);
|
||||
|
||||
ModalFooter::new()
|
||||
.start_slot::<Button>(
|
||||
if let ConfigurationSource::Extension {
|
||||
repository_url: Some(repository_url),
|
||||
..
|
||||
} = &self.source
|
||||
{
|
||||
Some(
|
||||
Button::new("open-repository", "Open Repository")
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip({
|
||||
let repository_url = repository_url.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Open Repository",
|
||||
None,
|
||||
repository_url.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click({
|
||||
let repository_url = repository_url.clone();
|
||||
move |_, _, cx| cx.open_url(&repository_url)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new(
|
||||
"cancel",
|
||||
if self.source.has_configuration_options() {
|
||||
"Cancel"
|
||||
} else {
|
||||
"Dismiss"
|
||||
},
|
||||
)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(|this, _event, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||
),
|
||||
)
|
||||
.children(self.source.has_configuration_options().then(|| {
|
||||
Button::new(
|
||||
"add-server",
|
||||
if self.source.is_new() {
|
||||
"Add Server"
|
||||
} else {
|
||||
"Configure Server"
|
||||
},
|
||||
)
|
||||
.disabled(is_connecting)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(|this, _event, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
}),
|
||||
)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_waiting_for_context_server() -> Div {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Label::new("Waiting for Context Server")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_modal_error(error: SharedString) -> Div {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.child(Label::new(error).size(LabelSize::Small).color(Color::Muted)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ConfigureContextServerModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(34.))
|
||||
.key_context("ConfigureContextServerModal")
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Confirm, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
}),
|
||||
)
|
||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||
this.focus_handle(cx).focus(window);
|
||||
}))
|
||||
.child(
|
||||
Modal::new("configure-context-server", None)
|
||||
.header(self.render_modal_header())
|
||||
.section(
|
||||
Section::new()
|
||||
.child(self.render_modal_description(window, cx))
|
||||
.child(self.render_modal_content(cx))
|
||||
.child(match &self.state {
|
||||
State::Idle => div(),
|
||||
State::Waiting => Self::render_waiting_for_context_server(),
|
||||
State::Error(error) => Self::render_modal_error(error.clone()),
|
||||
}),
|
||||
)
|
||||
.footer(self.render_modal_footer(window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_context_server(
|
||||
context_server_store: &Entity<ContextServerStore>,
|
||||
context_server_id: ContextServerId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(), Arc<str>>> {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let tx = Arc::new(Mutex::new(Some(tx)));
|
||||
|
||||
let subscription = cx.subscribe(context_server_store, move |_, event, _cx| match event {
|
||||
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
|
||||
match status {
|
||||
ContextServerStatus::Running => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Stopped => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err("Context server stopped running".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Error(error) => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err(error.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(async move |_cx| {
|
||||
let result = rx.await.unwrap();
|
||||
drop(subscription);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let colors = cx.theme().colors();
|
||||
let mut text_style = window.text_style();
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||
font_features: Some(theme_settings.ui_font.features.clone()),
|
||||
font_size: Some(TextSize::XSmall.rems(cx).into()),
|
||||
color: Some(colors.text_muted),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(colors.text_accent.opacity(0.5)),
|
||||
thickness: px(1.),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
|
||||
use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
|
||||
use crate::agent_profile::AgentProfile;
|
||||
use crate::{AgentPanel, ManageProfiles};
|
||||
use agent::agent_profile::AgentProfile;
|
||||
|
||||
use super::tool_picker::ToolPickerMode;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll, Thread, ThreadEvent};
|
||||
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
|
||||
use agent::{Thread, ThreadEvent};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::Result;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
@@ -1748,7 +1749,8 @@ impl editor::Addon for EditorAgentDiffAddon {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Keep, ThreadStore, thread_store};
|
||||
use crate::Keep;
|
||||
use agent::thread_store::{self, ThreadStore};
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use editor::EditorSettings;
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::{
|
||||
ModelUsageContext,
|
||||
language_model_selector::{
|
||||
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||
},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{Entity, FocusHandle, SharedString};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
|
||||
use crate::ModelUsageContext;
|
||||
use assistant_context_editor::language_model_selector::{
|
||||
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||
};
|
||||
use language_model::{ConfiguredModel, LanguageModelRegistry};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
|
||||
@@ -7,17 +7,35 @@ use std::time::Duration;
|
||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::language_model_selector::ToggleModelSelector;
|
||||
use crate::{
|
||||
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
|
||||
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
|
||||
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
|
||||
ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
|
||||
active_thread::{self, ActiveThread, ActiveThreadEvent},
|
||||
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
|
||||
agent_diff::AgentDiff,
|
||||
message_editor::{MessageEditor, MessageEditorEvent},
|
||||
slash_command::SlashCommandCompletionProvider,
|
||||
text_thread_editor::{
|
||||
AgentPanelDelegate, TextThreadEditor, humanize_token_count, make_lsp_adapter_delegate,
|
||||
render_remaining_tokens,
|
||||
},
|
||||
thread_history::{HistoryEntryElement, ThreadHistory},
|
||||
ui::AgentOnboardingModal,
|
||||
};
|
||||
use agent::{
|
||||
Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
|
||||
context_store::ContextStore,
|
||||
history_store::{HistoryEntryId, HistoryStore},
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_context_editor::{
|
||||
AgentPanelDelegate, AssistantContext, ContextEditor, ContextEvent, ContextSummary,
|
||||
SlashCommandCompletionProvider, humanize_token_count, make_lsp_adapter_delegate,
|
||||
render_remaining_tokens,
|
||||
};
|
||||
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
|
||||
use assistant_context_editor::language_model_selector::ToggleModelSelector;
|
||||
use client::{UserStore, zed_urls};
|
||||
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
|
||||
use fs::Fs;
|
||||
@@ -29,8 +47,7 @@ use gpui::{
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{
|
||||
ConfigurationError, LanguageModelProviderTosView, LanguageModelRegistry, RequestUsage,
|
||||
ZED_CLOUD_PROVIDER_ID,
|
||||
ConfigurationError, LanguageModelProviderTosView, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use project::{Project, ProjectPath, Worktree};
|
||||
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
|
||||
@@ -45,33 +62,18 @@ use ui::{
|
||||
Banner, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu,
|
||||
PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
|
||||
};
|
||||
use util::{ResultExt as _, maybe};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
};
|
||||
use zed_actions::{
|
||||
DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
|
||||
agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding},
|
||||
assistant::{OpenRulesLibrary, ToggleFocus},
|
||||
};
|
||||
use zed_actions::agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding};
|
||||
use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
|
||||
use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};
|
||||
use zed_llm_client::{CompletionIntent, UsageLimit};
|
||||
|
||||
use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
|
||||
use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
|
||||
use crate::agent_diff::AgentDiff;
|
||||
use crate::history_store::{HistoryEntryId, HistoryStore};
|
||||
use crate::message_editor::{MessageEditor, MessageEditorEvent};
|
||||
use crate::thread::{Thread, ThreadError, ThreadId, ThreadSummary, TokenUsageRatio};
|
||||
use crate::thread_history::{HistoryEntryElement, ThreadHistory};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::AgentOnboardingModal;
|
||||
use crate::{
|
||||
AddContextServer, AgentDiffPane, ContextStore, ContinueThread, ContinueWithBurnMode,
|
||||
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
|
||||
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
|
||||
ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleBurnMode, ToggleContextPicker,
|
||||
ToggleNavigationMenu, ToggleOptionsMenu,
|
||||
};
|
||||
|
||||
const AGENT_PANEL_KEY: &str = "agent_panel";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -176,7 +178,7 @@ enum ActiveView {
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
},
|
||||
TextThread {
|
||||
context_editor: Entity<ContextEditor>,
|
||||
context_editor: Entity<TextThreadEditor>,
|
||||
title_editor: Entity<Editor>,
|
||||
buffer_search_bar: Entity<BufferSearchBar>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
@@ -257,7 +259,7 @@ impl ActiveView {
|
||||
}
|
||||
|
||||
pub fn prompt_editor(
|
||||
context_editor: Entity<ContextEditor>,
|
||||
context_editor: Entity<TextThreadEditor>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
window: &mut Window,
|
||||
@@ -370,7 +372,7 @@ pub struct AgentPanel {
|
||||
_default_model_subscription: Subscription,
|
||||
context_store: Entity<TextThreadStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
inline_assist_context_store: Entity<crate::context_store::ContextStore>,
|
||||
inline_assist_context_store: Entity<ContextStore>,
|
||||
configuration: Option<Entity<AgentConfiguration>>,
|
||||
configuration_subscription: Option<Subscription>,
|
||||
local_timezone: UtcOffset,
|
||||
@@ -431,7 +433,7 @@ impl AgentPanel {
|
||||
let context_store = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
assistant_context_editor::ContextStore::new(
|
||||
assistant_context::ContextStore::new(
|
||||
project,
|
||||
prompt_builder.clone(),
|
||||
slash_commands,
|
||||
@@ -491,18 +493,10 @@ impl AgentPanel {
|
||||
let workspace = workspace.weak_handle();
|
||||
let weak_self = cx.entity().downgrade();
|
||||
|
||||
let message_editor_context_store = cx.new(|_cx| {
|
||||
crate::context_store::ContextStore::new(
|
||||
project.downgrade(),
|
||||
Some(thread_store.downgrade()),
|
||||
)
|
||||
});
|
||||
let inline_assist_context_store = cx.new(|_cx| {
|
||||
crate::context_store::ContextStore::new(
|
||||
project.downgrade(),
|
||||
Some(thread_store.downgrade()),
|
||||
)
|
||||
});
|
||||
let message_editor_context_store =
|
||||
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
|
||||
let inline_assist_context_store =
|
||||
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
|
||||
|
||||
let message_editor = cx.new(|cx| {
|
||||
MessageEditor::new(
|
||||
@@ -551,7 +545,7 @@ impl AgentPanel {
|
||||
context_store.update(cx, |context_store, cx| context_store.create(cx));
|
||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
|
||||
let context_editor = cx.new(|cx| {
|
||||
let mut editor = ContextEditor::for_context(
|
||||
let mut editor = TextThreadEditor::for_context(
|
||||
context,
|
||||
fs.clone(),
|
||||
workspace.clone(),
|
||||
@@ -709,9 +703,7 @@ impl AgentPanel {
|
||||
&self.prompt_store
|
||||
}
|
||||
|
||||
pub(crate) fn inline_assist_context_store(
|
||||
&self,
|
||||
) -> &Entity<crate::context_store::ContextStore> {
|
||||
pub(crate) fn inline_assist_context_store(&self) -> &Entity<ContextStore> {
|
||||
&self.inline_assist_context_store
|
||||
}
|
||||
|
||||
@@ -729,6 +721,12 @@ impl AgentPanel {
|
||||
}
|
||||
|
||||
fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
|
||||
// Preserve chat box text when using creating new thread from summary'
|
||||
let preserved_text = action
|
||||
.from_thread_id
|
||||
.is_some()
|
||||
.then(|| self.message_editor.read(cx).get_text(cx).trim().to_string());
|
||||
|
||||
let thread = self
|
||||
.thread_store
|
||||
.update(cx, |this, cx| this.create_thread(cx));
|
||||
@@ -737,7 +735,7 @@ impl AgentPanel {
|
||||
self.set_active_view(thread_view, window, cx);
|
||||
|
||||
let context_store = cx.new(|_cx| {
|
||||
crate::context_store::ContextStore::new(
|
||||
ContextStore::new(
|
||||
self.project.downgrade(),
|
||||
Some(self.thread_store.downgrade()),
|
||||
)
|
||||
@@ -805,6 +803,13 @@ impl AgentPanel {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(text) = preserved_text {
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_text(text, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
self.message_editor.focus_handle(cx).focus(window);
|
||||
|
||||
let message_editor_subscription =
|
||||
@@ -835,7 +840,7 @@ impl AgentPanel {
|
||||
.flatten();
|
||||
|
||||
let context_editor = cx.new(|cx| {
|
||||
let mut editor = ContextEditor::for_context(
|
||||
let mut editor = TextThreadEditor::for_context(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
@@ -927,7 +932,7 @@ impl AgentPanel {
|
||||
.log_err()
|
||||
.flatten();
|
||||
let editor = cx.new(|cx| {
|
||||
ContextEditor::for_context(
|
||||
TextThreadEditor::for_context(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
@@ -978,7 +983,7 @@ impl AgentPanel {
|
||||
let thread_view = ActiveView::thread(thread.clone(), window, cx);
|
||||
self.set_active_view(thread_view, window, cx);
|
||||
let context_store = cx.new(|_cx| {
|
||||
crate::context_store::ContextStore::new(
|
||||
ContextStore::new(
|
||||
self.project.downgrade(),
|
||||
Some(self.thread_store.downgrade()),
|
||||
)
|
||||
@@ -1184,8 +1189,17 @@ impl AgentPanel {
|
||||
let fs = self.fs.clone();
|
||||
|
||||
self.set_active_view(ActiveView::Configuration, window, cx);
|
||||
self.configuration =
|
||||
Some(cx.new(|cx| AgentConfiguration::new(fs, context_server_store, tools, window, cx)));
|
||||
self.configuration = Some(cx.new(|cx| {
|
||||
AgentConfiguration::new(
|
||||
fs,
|
||||
context_server_store,
|
||||
tools,
|
||||
self.language_registry.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
self.configuration_subscription = Some(cx.subscribe_in(
|
||||
@@ -1306,7 +1320,7 @@ impl AgentPanel {
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
|
||||
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
|
||||
match &self.active_view {
|
||||
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
|
||||
_ => None,
|
||||
@@ -1673,24 +1687,7 @@ impl AgentPanel {
|
||||
let thread_id = thread.id().clone();
|
||||
let is_empty = active_thread.is_empty();
|
||||
let editor_empty = self.message_editor.read(cx).is_editor_fully_empty(cx);
|
||||
let last_usage = active_thread.thread().read(cx).last_usage().or_else(|| {
|
||||
maybe!({
|
||||
let amount = user_store.model_request_usage_amount()?;
|
||||
let limit = user_store.model_request_usage_limit()?.variant?;
|
||||
|
||||
Some(RequestUsage {
|
||||
amount: amount as i32,
|
||||
limit: match limit {
|
||||
proto::usage_limit::Variant::Limited(limited) => {
|
||||
zed_llm_client::UsageLimit::Limited(limited.limit as i32)
|
||||
}
|
||||
proto::usage_limit::Variant::Unlimited(_) => {
|
||||
zed_llm_client::UsageLimit::Unlimited
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
});
|
||||
let usage = user_store.model_request_usage();
|
||||
|
||||
let account_url = zed_urls::account_url(cx);
|
||||
|
||||
@@ -1811,7 +1808,7 @@ impl AgentPanel {
|
||||
.action("Add Custom Server…", Box::new(AddContextServer))
|
||||
.separator();
|
||||
|
||||
if let Some(usage) = last_usage {
|
||||
if let Some(usage) = usage {
|
||||
menu = menu
|
||||
.header_with_link("Prompt Usage", "Manage", account_url.clone())
|
||||
.custom_entry(
|
||||
@@ -2901,7 +2898,7 @@ impl AgentPanel {
|
||||
|
||||
fn render_prompt_editor(
|
||||
&self,
|
||||
context_editor: &Entity<ContextEditor>,
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
buffer_search_bar: &Entity<BufferSearchBar>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -3028,7 +3025,7 @@ impl AgentPanel {
|
||||
}
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
ContextEditor::insert_dragged_files(
|
||||
TextThreadEditor::insert_dragged_files(
|
||||
context_editor,
|
||||
paths,
|
||||
added_worktrees,
|
||||
@@ -3207,7 +3204,7 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
workspace: &mut Workspace,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<ContextEditor>> {
|
||||
) -> Option<Entity<TextThreadEditor>> {
|
||||
let panel = workspace.panel::<AgentPanel>(cx)?;
|
||||
panel.read(cx).active_context_editor()
|
||||
}
|
||||
@@ -3231,10 +3228,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
fn open_remote_context(
|
||||
&self,
|
||||
_workspace: &mut Workspace,
|
||||
_context_id: assistant_context_editor::ContextId,
|
||||
_context_id: assistant_context::ContextId,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<ContextEditor>>> {
|
||||
) -> Task<Result<Entity<TextThreadEditor>>> {
|
||||
Task::ready(Err(anyhow!("opening remote context not implemented")))
|
||||
}
|
||||
|
||||
291
crates/agent_ui/src/agent_ui.rs
Normal file
291
crates/agent_ui/src/agent_ui.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
mod active_thread;
|
||||
mod agent_configuration;
|
||||
mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod buffer_codegen;
|
||||
mod context_picker;
|
||||
mod context_server_configuration;
|
||||
mod context_strip;
|
||||
mod debug;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod language_model_selector;
|
||||
mod max_mode_tooltip;
|
||||
mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command;
|
||||
mod slash_command_picker;
|
||||
mod slash_command_settings;
|
||||
mod terminal_codegen;
|
||||
mod terminal_inline_assistant;
|
||||
mod text_thread_editor;
|
||||
mod thread_history;
|
||||
mod tool_compatibility;
|
||||
mod ui;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::{Thread, ThreadId};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use fs::Fs;
|
||||
use gpui::{Action, App, Entity, actions};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
|
||||
};
|
||||
use prompt_store::PromptBuilder;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
|
||||
pub use crate::active_thread::ActiveThread;
|
||||
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use text_thread_editor::AgentPanelDelegate;
|
||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||
|
||||
actions!(
|
||||
agent,
|
||||
[
|
||||
NewTextThread,
|
||||
ToggleContextPicker,
|
||||
ToggleNavigationMenu,
|
||||
ToggleOptionsMenu,
|
||||
DeleteRecentlyOpenThread,
|
||||
ToggleProfileSelector,
|
||||
RemoveAllContext,
|
||||
ExpandMessageEditor,
|
||||
OpenHistory,
|
||||
AddContextServer,
|
||||
RemoveSelectedThread,
|
||||
Chat,
|
||||
ChatWithFollow,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist,
|
||||
FocusUp,
|
||||
FocusDown,
|
||||
FocusLeft,
|
||||
FocusRight,
|
||||
RemoveFocusedContext,
|
||||
AcceptSuggestedContext,
|
||||
OpenActiveThreadAsMarkdown,
|
||||
OpenAgentDiff,
|
||||
Keep,
|
||||
Reject,
|
||||
RejectAll,
|
||||
KeepAll,
|
||||
Follow,
|
||||
ResetTrialUpsell,
|
||||
ResetTrialEndUpsell,
|
||||
ContinueThread,
|
||||
ContinueWithBurnMode,
|
||||
ToggleBurnMode,
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
pub struct NewThread {
|
||||
#[serde(default)]
|
||||
from_thread_id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
pub struct ManageProfiles {
|
||||
#[serde(default)]
|
||||
pub customize_tools: Option<AgentProfileId>,
|
||||
}
|
||||
|
||||
impl ManageProfiles {
|
||||
pub fn customize_tools(profile_id: AgentProfileId) -> Self {
|
||||
Self {
|
||||
customize_tools: Some(profile_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ModelUsageContext {
|
||||
Thread(Entity<Thread>),
|
||||
InlineAssistant,
|
||||
}
|
||||
|
||||
impl ModelUsageContext {
|
||||
pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
||||
match self {
|
||||
Self::Thread(thread) => thread.read(cx).configured_model(),
|
||||
Self::InlineAssistant => {
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
self.configured_model(cx)
|
||||
.map(|configured_model| configured_model.model)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the `agent` crate.
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
is_eval: bool,
|
||||
cx: &mut App,
|
||||
) {
|
||||
AgentSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
assistant_context::init(client.clone(), cx);
|
||||
rules_library::init(cx);
|
||||
if !is_eval {
|
||||
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||
// we're not running inside of the eval.
|
||||
init_language_model_settings(cx);
|
||||
}
|
||||
assistant_slash_command::init(cx);
|
||||
agent::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
terminal_inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
indexed_docs::init(cx);
|
||||
cx.observe_new(move |workspace, window, cx| {
|
||||
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
|
||||
})
|
||||
.detach();
|
||||
cx.observe_new(ManageProfilesModal::register).detach();
|
||||
}
|
||||
|
||||
fn init_language_model_settings(cx: &mut App) {
|
||||
update_active_language_model_from_settings(cx);
|
||||
|
||||
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
|
||||
.detach();
|
||||
cx.subscribe(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
|_, event: &language_model::Event, cx| match event {
|
||||
language_model::Event::ProviderStateChanged
|
||||
| language_model::Event::AddedProvider(_)
|
||||
| language_model::Event::RemovedProvider(_) => {
|
||||
update_active_language_model_from_settings(cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
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()),
|
||||
model: LanguageModelId::from(selection.model.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
let default = to_selected_model(&settings.default_model);
|
||||
let inline_assistant = settings
|
||||
.inline_assistant_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let commit_message = settings
|
||||
.commit_message_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let thread_summary = settings
|
||||
.thread_summary_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let inline_alternatives = settings
|
||||
.inline_alternatives
|
||||
.iter()
|
||||
.map(to_selected_model)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_default_model(Some(&default), cx);
|
||||
registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
|
||||
registry.select_commit_message_model(commit_message.as_ref(), cx);
|
||||
registry.select_thread_summary_model(thread_summary.as_ref(), cx);
|
||||
registry.select_inline_alternative_models(inline_alternatives, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn register_slash_commands(cx: &mut App) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
|
||||
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
|
||||
|
||||
cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry.register_command(
|
||||
assistant_slash_commands::StreamingExampleSlashCommand,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
update_slash_commands_from_settings(cx);
|
||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_slash_commands_from_settings(cx: &mut App) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
let settings = SlashCommandSettings::get_global(cx);
|
||||
|
||||
if settings.docs.enabled {
|
||||
slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
|
||||
}
|
||||
|
||||
if settings.cargo_workspace.enabled {
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry
|
||||
.unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::context::ContextLoadResult;
|
||||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use crate::{context::load_context, context_store::ContextStore};
|
||||
use agent::{
|
||||
ContextStore,
|
||||
context::{ContextLoadResult, load_context},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
@@ -18,8 +20,7 @@ use language_model::{
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use prompt_store::PromptStore;
|
||||
use prompt_store::{PromptBuilder, PromptStore};
|
||||
use rope::Rope;
|
||||
use smol::future::FutureExt;
|
||||
use std::{
|
||||
@@ -37,10 +37,12 @@ use uuid::Uuid;
|
||||
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||
|
||||
use crate::AgentPanel;
|
||||
use crate::context::RULES_ICON;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread::ThreadId;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use agent::{
|
||||
ThreadId,
|
||||
context::RULES_ICON,
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ContextPickerEntry {
|
||||
@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use agent::context_store::ContextStore;
|
||||
use anyhow::Result;
|
||||
use editor::{CompletionProvider, Editor, ExcerptId, ToOffset as _};
|
||||
use file_icons::FileIcons;
|
||||
@@ -20,10 +21,11 @@ use ui::prelude::*;
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::Thread;
|
||||
use crate::context::{AgentContextHandle, AgentContextKey, ContextCreasesAddon, RULES_ICON};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use agent::{
|
||||
Thread,
|
||||
context::{AgentContextHandle, AgentContextKey, RULES_ICON},
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
use super::fetch_context_picker::fetch_url_content;
|
||||
use super::file_context_picker::{FileMatch, search_files};
|
||||
@@ -35,6 +37,7 @@ use super::{
|
||||
ContextPickerAction, ContextPickerEntry, ContextPickerMode, MentionLink, RecentEntry,
|
||||
available_context_picker_entries, recent_context_picker_entries, selection_ranges,
|
||||
};
|
||||
use crate::message_editor::ContextCreasesAddon;
|
||||
|
||||
pub(crate) enum Match {
|
||||
File(FileMatch),
|
||||
@@ -70,7 +73,7 @@ fn search(
|
||||
recent_entries: Vec<RecentEntry>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
text_thread_context_store: Option<WeakEntity<assistant_context_editor::ContextStore>>,
|
||||
text_thread_context_store: Option<WeakEntity<assistant_context::ContextStore>>,
|
||||
workspace: Entity<Workspace>,
|
||||
cx: &mut App,
|
||||
) -> Task<Vec<Match>> {
|
||||
@@ -2,6 +2,7 @@ use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::context_store::ContextStore;
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||
@@ -12,7 +13,6 @@ use ui::{Context, ListItem, Window, prelude::*};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FetchContextPicker {
|
||||
picker: Entity<Picker<FetchContextPickerDelegate>>,
|
||||
@@ -14,7 +14,7 @@ use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::{ContextStore, FileInclusion};
|
||||
use agent::context_store::{ContextStore, FileInclusion};
|
||||
|
||||
pub struct FileContextPicker {
|
||||
picker: Entity<Picker<FileContextPickerDelegate>>,
|
||||
@@ -7,9 +7,9 @@ use prompt_store::{PromptId, PromptStore, UserPromptId};
|
||||
use ui::{ListItem, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::context::RULES_ICON;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::{self, ContextStore};
|
||||
use agent::context::RULES_ICON;
|
||||
use agent::context_store::{self, ContextStore};
|
||||
|
||||
pub struct RulesContextPicker {
|
||||
picker: Entity<Picker<RulesContextPickerDelegate>>,
|
||||
@@ -14,9 +14,9 @@ use ui::{ListItem, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::AgentContextHandle;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use agent::context::AgentContextHandle;
|
||||
use agent::context_store::ContextStore;
|
||||
|
||||
pub struct SymbolContextPicker {
|
||||
picker: Entity<Picker<SymbolContextPickerDelegate>>,
|
||||
@@ -9,9 +9,11 @@ use picker::{Picker, PickerDelegate};
|
||||
use ui::{ListItem, prelude::*};
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::{self, ContextStore};
|
||||
use crate::thread::ThreadId;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use agent::{
|
||||
ThreadId,
|
||||
context_store::{self, ContextStore},
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
pub struct ThreadContextPicker {
|
||||
picker: Entity<Picker<ThreadContextPickerDelegate>>,
|
||||
116
crates/agent_ui/src/context_server_configuration.rs
Normal file
116
crates/agent_ui/src/context_server_configuration.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use context_server::ContextServerId;
|
||||
use extension::ExtensionManifest;
|
||||
use fs::Fs;
|
||||
use gpui::WeakEntity;
|
||||
use language::LanguageRegistry;
|
||||
use project::project_settings::ProjectSettings;
|
||||
use settings::update_settings_file;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::agent_configuration::ConfigureContextServerModal;
|
||||
|
||||
pub(crate) fn init(language_registry: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
cx.observe_new(move |_: &mut Workspace, window, cx| {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
|
||||
cx.subscribe_in(extension_events, window, {
|
||||
let language_registry = language_registry.clone();
|
||||
let fs = fs.clone();
|
||||
move |_, _, event, window, cx| match event {
|
||||
extension::Event::ExtensionInstalled(manifest) => {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
cx.weak_entity(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
extension::Event::ExtensionUninstalled(manifest) => {
|
||||
remove_context_server_settings(
|
||||
manifest.context_servers.keys().cloned().collect(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
extension::Event::ConfigureExtensionRequested(manifest) => {
|
||||
if !manifest.context_servers.is_empty() {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
cx.weak_entity(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
log::info!(
|
||||
"No extension events global found. Skipping context server configuration wizard"
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn remove_context_server_settings(
|
||||
context_server_ids: Vec<Arc<str>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
update_settings_file::<ProjectSettings>(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.retain(|server_id, _| !context_server_ids.contains(server_id));
|
||||
});
|
||||
}
|
||||
|
||||
fn show_configure_mcp_modal(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Workspace>,
|
||||
) {
|
||||
if !window.is_window_active() {
|
||||
return;
|
||||
}
|
||||
|
||||
let ids = manifest.context_servers.keys().cloned().collect::<Vec<_>>();
|
||||
if ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
for id in ids {
|
||||
let Some(task) = cx
|
||||
.update(|window, cx| {
|
||||
ConfigureContextServerModal::show_modal_for_existing_server(
|
||||
ContextServerId(id.clone()),
|
||||
language_registry.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
task.await.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use assistant_context_editor::AssistantContext;
|
||||
use crate::{
|
||||
AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
||||
ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
|
||||
context_picker::ContextPicker,
|
||||
ui::{AddedContext, ContextPill},
|
||||
};
|
||||
use agent::context_store::SuggestedContext;
|
||||
use agent::{
|
||||
context::AgentContextHandle,
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use collections::HashSet;
|
||||
use editor::Editor;
|
||||
use file_icons::FileIcons;
|
||||
@@ -10,22 +18,11 @@ use gpui::{
|
||||
Subscription, WeakEntity,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::Buffer;
|
||||
use project::ProjectItem;
|
||||
use std::{path::Path, rc::Rc};
|
||||
use ui::{PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::{AgentContextHandle, ContextKind};
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread::Thread;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use crate::ui::{AddedContext, ContextPill};
|
||||
use crate::{
|
||||
AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
||||
ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
|
||||
};
|
||||
|
||||
pub struct ContextStrip {
|
||||
context_store: Entity<ContextStore>,
|
||||
context_picker: Entity<ContextPicker>,
|
||||
@@ -575,46 +572,3 @@ pub enum SuggestContextKind {
|
||||
File,
|
||||
Thread,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SuggestedContext {
|
||||
File {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakEntity<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
name: SharedString,
|
||||
thread: WeakEntity<Thread>,
|
||||
},
|
||||
TextThread {
|
||||
name: SharedString,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SuggestedContext {
|
||||
pub fn name(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { name, .. } => name,
|
||||
Self::Thread { name, .. } => name,
|
||||
Self::TextThread { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_path(&self) -> Option<SharedString> {
|
||||
match self {
|
||||
Self::File { icon_path, .. } => icon_path.clone(),
|
||||
Self::Thread { .. } => None,
|
||||
Self::TextThread { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ContextKind {
|
||||
match self {
|
||||
Self::File { .. } => ContextKind::File,
|
||||
Self::Thread { .. } => ContextKind::Thread,
|
||||
Self::TextThread { .. } => ContextKind::TextThread,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(unused, dead_code)]
|
||||
|
||||
use client::{ModelRequestUsage, RequestUsage};
|
||||
use gpui::Global;
|
||||
use language_model::RequestUsage;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use ui::prelude::*;
|
||||
use zed_llm_client::{Plan, UsageLimit};
|
||||
@@ -17,7 +17,7 @@ pub struct DebugAccountState {
|
||||
pub enabled: bool,
|
||||
pub trial_expired: bool,
|
||||
pub plan: Plan,
|
||||
pub custom_prompt_usage: RequestUsage,
|
||||
pub custom_prompt_usage: ModelRequestUsage,
|
||||
pub usage_based_billing_enabled: bool,
|
||||
pub monthly_spending_cap: i32,
|
||||
pub custom_edit_prediction_usage: UsageLimit,
|
||||
@@ -43,7 +43,7 @@ impl DebugAccountState {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_custom_prompt_usage(&mut self, custom_prompt_usage: RequestUsage) -> &mut Self {
|
||||
pub fn set_custom_prompt_usage(&mut self, custom_prompt_usage: ModelRequestUsage) -> &mut Self {
|
||||
self.custom_prompt_usage = custom_prompt_usage;
|
||||
self
|
||||
}
|
||||
@@ -76,10 +76,10 @@ impl Default for DebugAccountState {
|
||||
enabled: false,
|
||||
trial_expired: false,
|
||||
plan: Plan::ZedFree,
|
||||
custom_prompt_usage: RequestUsage {
|
||||
custom_prompt_usage: ModelRequestUsage(RequestUsage {
|
||||
limit: UsageLimit::Unlimited,
|
||||
amount: 0,
|
||||
},
|
||||
}),
|
||||
usage_based_billing_enabled: false,
|
||||
// $50.00
|
||||
monthly_spending_cap: 5000,
|
||||
@@ -4,18 +4,27 @@ use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
AgentPanel,
|
||||
buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent},
|
||||
inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent},
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
};
|
||||
use agent::{
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::display_map::EditorMargins;
|
||||
use editor::{
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
actions::SelectAll,
|
||||
display_map::{
|
||||
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
ToDisplayPoint,
|
||||
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,
|
||||
RenderBlock, ToDisplayPoint,
|
||||
},
|
||||
};
|
||||
use fs::Fs;
|
||||
@@ -24,16 +33,13 @@ use gpui::{
|
||||
WeakEntity, Window, point,
|
||||
};
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use language_model::ConfigurationError;
|
||||
use language_model::ConfiguredModel;
|
||||
use language_model::{LanguageModelRegistry, report_assistant_event};
|
||||
use language_model::{
|
||||
ConfigurationError, ConfiguredModel, LanguageModelRegistry, report_assistant_event,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::LspAction;
|
||||
use project::Project;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
use prompt_store::PromptBuilder;
|
||||
use prompt_store::PromptStore;
|
||||
use project::{CodeAction, LspAction, Project, ProjectTransaction};
|
||||
use prompt_store::{PromptBuilder, PromptStore};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||
@@ -43,14 +49,6 @@ use util::{RangeExt, ResultExt, maybe};
|
||||
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
|
||||
use zed_actions::agent::OpenConfiguration;
|
||||
|
||||
use crate::AgentPanel;
|
||||
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
||||
use crate::terminal_inline_assistant::TerminalInlineAssistant;
|
||||
use crate::thread_store::TextThreadStore;
|
||||
use crate::thread_store::ThreadStore;
|
||||
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::agent_model_selector::AgentModelSelector;
|
||||
use crate::buffer_codegen::BufferCodegen;
|
||||
use crate::context::ContextCreasesAddon;
|
||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::message_editor::{extract_message_creases, insert_message_creases};
|
||||
use crate::language_model_selector::ToggleModelSelector;
|
||||
use crate::message_editor::{ContextCreasesAddon, extract_message_creases, insert_message_creases};
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
|
||||
use crate::{RemoveAllContext, ToggleContextPicker};
|
||||
use assistant_context_editor::language_model_selector::ToggleModelSelector;
|
||||
use agent::{
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use client::ErrorExt;
|
||||
use collections::VecDeque;
|
||||
use db::kvp::Dismissable;
|
||||
@@ -870,7 +871,7 @@ impl PromptEditor<BufferCodegen> {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::AutoHeight {
|
||||
min_lines: 1,
|
||||
max_lines: Self::MAX_LINES as usize,
|
||||
max_lines: Some(Self::MAX_LINES as usize),
|
||||
},
|
||||
prompt_buffer,
|
||||
None,
|
||||
@@ -1049,7 +1050,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::AutoHeight {
|
||||
min_lines: 1,
|
||||
max_lines: Self::MAX_LINES as usize,
|
||||
max_lines: Some(Self::MAX_LINES as usize),
|
||||
},
|
||||
prompt_buffer,
|
||||
None,
|
||||
@@ -4,8 +4,7 @@ use collections::{HashSet, IndexMap};
|
||||
use feature_flags::ZedProFeatureFlag;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
|
||||
use gpui::{
|
||||
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task,
|
||||
action_with_deprecated_aliases,
|
||||
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task, actions,
|
||||
};
|
||||
use language_model::{
|
||||
AuthenticateError, ConfiguredModel, LanguageModel, LanguageModelProviderId,
|
||||
@@ -16,12 +15,11 @@ use picker::{Picker, PickerDelegate};
|
||||
use proto::Plan;
|
||||
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
|
||||
action_with_deprecated_aliases!(
|
||||
actions!(
|
||||
agent,
|
||||
ToggleModelSelector,
|
||||
[
|
||||
"assistant::ToggleModelSelector",
|
||||
"assistant2::ToggleModelSelector"
|
||||
#[action(deprecated_aliases = ["assistant::ToggleModelSelector", "assistant2::ToggleModelSelector"])]
|
||||
ToggleModelSelector
|
||||
]
|
||||
);
|
||||
|
||||
@@ -3,21 +3,25 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::agent_model_selector::AgentModelSelector;
|
||||
use crate::context::{AgentContextKey, ContextCreasesAddon, ContextLoadResult, load_context};
|
||||
use crate::language_model_selector::ToggleModelSelector;
|
||||
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
|
||||
use crate::ui::{
|
||||
MaxModeTooltip,
|
||||
preview::{AgentPreview, UsageCallout},
|
||||
};
|
||||
use agent::{
|
||||
context::{AgentContextKey, ContextLoadResult, load_context},
|
||||
context_store::ContextStoreEvent,
|
||||
};
|
||||
use agent_settings::{AgentSettings, CompletionMode};
|
||||
use assistant_context_editor::language_model_selector::ToggleModelSelector;
|
||||
use buffer_diff::BufferDiff;
|
||||
use client::UserStore;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::actions::{MoveUp, Paste};
|
||||
use editor::display_map::CreaseId;
|
||||
use editor::{
|
||||
AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorEvent,
|
||||
EditorMode, EditorStyle, MultiBuffer,
|
||||
Addon, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
|
||||
EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||
};
|
||||
use file_icons::FileIcons;
|
||||
use fs::Fs;
|
||||
@@ -29,8 +33,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Buffer, Language, Point};
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModelRequestMessage, MessageContent, RequestUsage,
|
||||
ZED_CLOUD_PROVIDER_ID,
|
||||
ConfiguredModel, LanguageModelRequestMessage, MessageContent, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use multi_buffer;
|
||||
use project::Project;
|
||||
@@ -42,21 +45,23 @@ use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Callout, Disclosure, Divider, DividerColor, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*,
|
||||
};
|
||||
use util::{ResultExt as _, maybe};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
use zed_llm_client::CompletionIntent;
|
||||
|
||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::profile_selector::ProfileSelector;
|
||||
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use crate::{
|
||||
ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
|
||||
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
|
||||
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
||||
};
|
||||
use agent::{
|
||||
MessageCrease, Thread, TokenUsageRatio,
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct MessageEditor {
|
||||
@@ -89,6 +94,8 @@ pub(crate) fn create_editor(
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
text_thread_store: WeakEntity<TextThreadStore>,
|
||||
min_lines: usize,
|
||||
max_lines: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Editor> {
|
||||
@@ -105,8 +112,8 @@ pub(crate) fn create_editor(
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let mut editor = Editor::new(
|
||||
editor::EditorMode::AutoHeight {
|
||||
min_lines: MIN_EDITOR_LINES,
|
||||
max_lines: MAX_EDITOR_LINES,
|
||||
min_lines,
|
||||
max_lines: max_lines,
|
||||
},
|
||||
buffer,
|
||||
None,
|
||||
@@ -161,6 +168,8 @@ impl MessageEditor {
|
||||
context_store.downgrade(),
|
||||
thread_store.clone(),
|
||||
text_thread_store.clone(),
|
||||
MIN_EDITOR_LINES,
|
||||
Some(MAX_EDITOR_LINES),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -237,6 +246,21 @@ impl MessageEditor {
|
||||
&self.context_store
|
||||
}
|
||||
|
||||
pub fn get_text(&self, cx: &App) -> String {
|
||||
self.editor.read(cx).text(cx)
|
||||
}
|
||||
|
||||
pub fn set_text(
|
||||
&mut self,
|
||||
text: impl Into<Arc<str>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(text, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn expand_message_editor(
|
||||
&mut self,
|
||||
_: &ExpandMessageEditor,
|
||||
@@ -258,7 +282,7 @@ impl MessageEditor {
|
||||
} else {
|
||||
editor.set_mode(EditorMode::AutoHeight {
|
||||
min_lines: MIN_EDITOR_LINES,
|
||||
max_lines: MAX_EDITOR_LINES,
|
||||
max_lines: Some(MAX_EDITOR_LINES),
|
||||
})
|
||||
}
|
||||
});
|
||||
@@ -1222,16 +1246,17 @@ impl MessageEditor {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let is_using_zed_provider = self
|
||||
.thread
|
||||
fn is_using_zed_provider(&self, cx: &App) -> bool {
|
||||
self.thread
|
||||
.read(cx)
|
||||
.configured_model()
|
||||
.map_or(false, |model| {
|
||||
model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if !is_using_zed_provider {
|
||||
fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
|
||||
if !self.is_using_zed_provider(cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1253,24 +1278,8 @@ impl MessageEditor {
|
||||
Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
|
||||
})
|
||||
.unwrap_or(zed_llm_client::Plan::ZedFree);
|
||||
let usage = self.thread.read(cx).last_usage().or_else(|| {
|
||||
maybe!({
|
||||
let amount = user_store.model_request_usage_amount()?;
|
||||
let limit = user_store.model_request_usage_limit()?.variant?;
|
||||
|
||||
Some(RequestUsage {
|
||||
amount: amount as i32,
|
||||
limit: match limit {
|
||||
proto::usage_limit::Variant::Limited(limited) => {
|
||||
zed_llm_client::UsageLimit::Limited(limited.limit as i32)
|
||||
}
|
||||
proto::usage_limit::Variant::Unlimited(_) => {
|
||||
zed_llm_client::UsageLimit::Unlimited
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
})?;
|
||||
let usage = user_store.model_request_usage()?;
|
||||
|
||||
Some(
|
||||
div()
|
||||
@@ -1301,37 +1310,41 @@ impl MessageEditor {
|
||||
"Thread reaching the token limit soon"
|
||||
};
|
||||
|
||||
let description = if self.is_using_zed_provider(cx) {
|
||||
"To continue, start a new thread from a summary or turn burn mode on."
|
||||
} else {
|
||||
"To continue, start a new thread from a summary."
|
||||
};
|
||||
|
||||
let mut callout = Callout::new()
|
||||
.line_height(line_height)
|
||||
.icon(icon)
|
||||
.title(title)
|
||||
.description(description)
|
||||
.primary_action(
|
||||
Button::new("start-new-thread", "Start New Thread")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
let from_thread_id = Some(this.thread.read(cx).id().clone());
|
||||
window.dispatch_action(Box::new(NewThread { from_thread_id }), cx);
|
||||
})),
|
||||
);
|
||||
|
||||
if self.is_using_zed_provider(cx) {
|
||||
callout = callout.secondary_action(
|
||||
IconButton::new("burn-mode-callout", IconName::ZedBurnMode)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
this.toggle_burn_mode(&ToggleBurnMode, window, cx);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Some(
|
||||
div()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
Callout::new()
|
||||
.line_height(line_height)
|
||||
.icon(icon)
|
||||
.title(title)
|
||||
.description(
|
||||
"To continue, start a new thread from a summary or turn burn mode on.",
|
||||
)
|
||||
.primary_action(
|
||||
Button::new("start-new-thread", "Start New Thread")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
let from_thread_id = Some(this.thread.read(cx).id().clone());
|
||||
window.dispatch_action(
|
||||
Box::new(NewThread { from_thread_id }),
|
||||
cx,
|
||||
);
|
||||
})),
|
||||
)
|
||||
.secondary_action(
|
||||
IconButton::new("burn-mode-callout", IconName::ZedBurnMode)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
this.toggle_burn_mode(&ToggleBurnMode, window, cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
.child(callout),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1459,6 +1472,69 @@ impl MessageEditor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContextCreasesAddon {
|
||||
creases: HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>>,
|
||||
_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl Addon for ContextCreasesAddon {
|
||||
fn to_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextCreasesAddon {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
creases: HashMap::default(),
|
||||
_subscription: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_creases(
|
||||
&mut self,
|
||||
context_store: &Entity<ContextStore>,
|
||||
key: AgentContextKey,
|
||||
creases: impl IntoIterator<Item = (CreaseId, SharedString)>,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.creases.entry(key).or_default().extend(creases);
|
||||
self._subscription = Some(cx.subscribe(
|
||||
&context_store,
|
||||
|editor, _, event, cx| match event {
|
||||
ContextStoreEvent::ContextRemoved(key) => {
|
||||
let Some(this) = editor.addon_mut::<Self>() else {
|
||||
return;
|
||||
};
|
||||
let (crease_ids, replacement_texts): (Vec<_>, Vec<_>) = this
|
||||
.creases
|
||||
.remove(key)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let ranges = editor
|
||||
.remove_creases(crease_ids, cx)
|
||||
.into_iter()
|
||||
.map(|(_, range)| range)
|
||||
.collect::<Vec<_>>();
|
||||
editor.unfold_ranges(&ranges, false, false, cx);
|
||||
editor.edit(ranges.into_iter().zip(replacement_texts), cx);
|
||||
cx.notify();
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>> {
|
||||
self.creases
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_message_creases(
|
||||
editor: &mut Editor,
|
||||
cx: &mut Context<'_, Editor>,
|
||||
@@ -1497,8 +1573,9 @@ pub fn extract_message_creases(
|
||||
let context = contexts_by_crease_id.remove(&id);
|
||||
MessageCrease {
|
||||
range,
|
||||
metadata,
|
||||
context,
|
||||
label: metadata.label,
|
||||
icon_path: metadata.icon_path,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -1570,8 +1647,8 @@ pub fn insert_message_creases(
|
||||
let start = buffer_snapshot.anchor_after(crease.range.start);
|
||||
let end = buffer_snapshot.anchor_before(crease.range.end);
|
||||
crease_for_mention(
|
||||
crease.metadata.label.clone(),
|
||||
crease.metadata.icon_path.clone(),
|
||||
crease.label.clone(),
|
||||
crease.icon_path.clone(),
|
||||
start..end,
|
||||
cx.weak_entity(),
|
||||
)
|
||||
@@ -1583,12 +1660,7 @@ pub fn insert_message_creases(
|
||||
for (crease, id) in message_creases.iter().zip(ids) {
|
||||
if let Some(context) = crease.context.as_ref() {
|
||||
let key = AgentContextKey(context.clone());
|
||||
addon.add_creases(
|
||||
context_store,
|
||||
key,
|
||||
vec![(id, crease.metadata.label.clone())],
|
||||
cx,
|
||||
);
|
||||
addon.add_creases(context_store, key, vec![(id, crease.label.clone())], cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{ManageProfiles, ToggleProfileSelector};
|
||||
use agent::{
|
||||
Thread,
|
||||
agent_profile::{AgentProfile, AvailableProfiles},
|
||||
};
|
||||
use agent_settings::{AgentDockPosition, AgentProfileId, AgentSettings, builtin_profiles};
|
||||
use fs::Fs;
|
||||
use gpui::{Action, Empty, Entity, FocusHandle, Subscription, prelude::*};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use settings::{Settings as _, SettingsStore, update_settings_file};
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
ContextMenu, ContextMenuEntry, DocumentationSide, PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ManageProfiles, Thread, ToggleProfileSelector,
|
||||
agent_profile::{AgentProfile, AvailableProfiles},
|
||||
};
|
||||
|
||||
pub struct ProfileSelector {
|
||||
profiles: AvailableProfiles,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::context_editor::ContextEditor;
|
||||
use crate::text_thread_editor::TextThreadEditor;
|
||||
use anyhow::Result;
|
||||
pub use assistant_slash_command::SlashCommand;
|
||||
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
|
||||
@@ -21,14 +21,14 @@ use workspace::Workspace;
|
||||
pub struct SlashCommandCompletionProvider {
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakEntity<ContextEditor>>,
|
||||
editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
}
|
||||
|
||||
impl SlashCommandCompletionProvider {
|
||||
pub fn new(
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakEntity<ContextEditor>>,
|
||||
editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -1,12 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::text_thread_editor::TextThreadEditor;
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use gpui::{AnyElement, AnyView, DismissEvent, SharedString, Task, WeakEntity};
|
||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||
use std::sync::Arc;
|
||||
use ui::{ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip, prelude::*};
|
||||
|
||||
use crate::context_editor::ContextEditor;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct SlashCommandSelector<T, TT>
|
||||
where
|
||||
@@ -14,7 +12,7 @@ where
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakEntity<ContextEditor>,
|
||||
active_context_editor: WeakEntity<TextThreadEditor>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
}
|
||||
@@ -49,7 +47,7 @@ impl AsRef<str> for SlashCommandEntry {
|
||||
pub(crate) struct SlashCommandDelegate {
|
||||
all_commands: Vec<SlashCommandEntry>,
|
||||
filtered_commands: Vec<SlashCommandEntry>,
|
||||
active_context_editor: WeakEntity<ContextEditor>,
|
||||
active_context_editor: WeakEntity<TextThreadEditor>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
@@ -60,7 +58,7 @@ where
|
||||
{
|
||||
pub(crate) fn new(
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakEntity<ContextEditor>,
|
||||
active_context_editor: WeakEntity<TextThreadEditor>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
) -> Self {
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::context::load_context;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::inline_prompt_editor::{
|
||||
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
|
||||
};
|
||||
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use agent::{
|
||||
context::load_context,
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
@@ -27,11 +27,11 @@ use editor::{FoldPlaceholder, display_map::CreaseId};
|
||||
use fs::Fs;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, AnyView, App, ClipboardEntry, ClipboardItem, Empty,
|
||||
Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement,
|
||||
Action, Animation, AnimationExt, AnyElement, AnyView, App, ClipboardEntry, ClipboardItem,
|
||||
Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement,
|
||||
IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size,
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity, actions,
|
||||
div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between, size,
|
||||
div, img, percentage, point, prelude::*, pulsating_between, size,
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
@@ -76,14 +76,11 @@ use workspace::{
|
||||
searchable::{SearchEvent, SearchableItem},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
|
||||
use assistant_context::{
|
||||
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
|
||||
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
|
||||
ParsedSlashCommand, PendingSlashCommandStatus,
|
||||
};
|
||||
use crate::{
|
||||
ThoughtProcessOutputSection, slash_command::SlashCommandCompletionProvider,
|
||||
slash_command_picker,
|
||||
ParsedSlashCommand, PendingSlashCommandStatus, ThoughtProcessOutputSection,
|
||||
};
|
||||
|
||||
actions!(
|
||||
@@ -99,14 +96,13 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Clone, Action)]
|
||||
#[action(namespace = assistant, no_json, no_register)]
|
||||
pub enum InsertDraggedFiles {
|
||||
ProjectPaths(Vec<ProjectPath>),
|
||||
ExternalFiles(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
impl_internal_actions!(assistant, [InsertDraggedFiles]);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
struct ScrollPosition {
|
||||
offset_before_cursor: gpui::Point<f32>,
|
||||
@@ -132,7 +128,7 @@ pub trait AgentPanelDelegate {
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<ContextEditor>>;
|
||||
) -> Option<Entity<TextThreadEditor>>;
|
||||
|
||||
fn open_saved_context(
|
||||
&self,
|
||||
@@ -148,7 +144,7 @@ pub trait AgentPanelDelegate {
|
||||
context_id: ContextId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<ContextEditor>>>;
|
||||
) -> Task<Result<Entity<TextThreadEditor>>>;
|
||||
|
||||
fn quote_selection(
|
||||
&self,
|
||||
@@ -177,7 +173,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
|
||||
|
||||
impl Global for GlobalAssistantPanelDelegate {}
|
||||
|
||||
pub struct ContextEditor {
|
||||
pub struct TextThreadEditor {
|
||||
context: Entity<AssistantContext>,
|
||||
fs: Arc<dyn Fs>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
@@ -207,10 +203,24 @@ pub struct ContextEditor {
|
||||
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TAB_TITLE: &str = "New Chat";
|
||||
const MAX_TAB_TITLE_LEN: usize = 16;
|
||||
|
||||
impl ContextEditor {
|
||||
impl TextThreadEditor {
|
||||
pub fn init(cx: &mut App) {
|
||||
workspace::FollowableViewRegistry::register::<TextThreadEditor>(cx);
|
||||
|
||||
cx.observe_new(
|
||||
|workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
|
||||
workspace
|
||||
.register_action(TextThreadEditor::quote_selection)
|
||||
.register_action(TextThreadEditor::insert_selection)
|
||||
.register_action(TextThreadEditor::copy_code)
|
||||
.register_action(TextThreadEditor::handle_insert_dragged_files);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn for_context(
|
||||
context: Entity<AssistantContext>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -1280,7 +1290,7 @@ impl ContextEditor {
|
||||
/// Returns either the selected text, or the content of the Markdown code
|
||||
/// block surrounding the cursor.
|
||||
fn get_selection_or_code_block(
|
||||
context_editor_view: &Entity<ContextEditor>,
|
||||
context_editor_view: &Entity<TextThreadEditor>,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<(String, bool)> {
|
||||
const CODE_FENCE_DELIMITER: &'static str = "```";
|
||||
@@ -2030,7 +2040,7 @@ impl ContextEditor {
|
||||
/// Whether or not we should allow messages to be sent.
|
||||
/// Will return false if the selected provided has a configuration error or
|
||||
/// if the user has not accepted the terms of service for this provider.
|
||||
fn sending_disabled(&self, cx: &mut Context<'_, ContextEditor>) -> bool {
|
||||
fn sending_disabled(&self, cx: &mut Context<'_, TextThreadEditor>) -> bool {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
let Some(configuration_error) =
|
||||
model_registry.configuration_error(model_registry.default_model(), cx)
|
||||
@@ -2547,10 +2557,10 @@ struct SelectedCreaseMetadata {
|
||||
crease: CreaseMetadata,
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for ContextEditor {}
|
||||
impl EventEmitter<SearchEvent> for ContextEditor {}
|
||||
impl EventEmitter<EditorEvent> for TextThreadEditor {}
|
||||
impl EventEmitter<SearchEvent> for TextThreadEditor {}
|
||||
|
||||
impl Render for ContextEditor {
|
||||
impl Render for TextThreadEditor {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let provider = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
@@ -2569,15 +2579,15 @@ impl Render for ContextEditor {
|
||||
|
||||
v_flex()
|
||||
.key_context("ContextEditor")
|
||||
.capture_action(cx.listener(ContextEditor::cancel))
|
||||
.capture_action(cx.listener(ContextEditor::save))
|
||||
.capture_action(cx.listener(ContextEditor::copy))
|
||||
.capture_action(cx.listener(ContextEditor::cut))
|
||||
.capture_action(cx.listener(ContextEditor::paste))
|
||||
.capture_action(cx.listener(ContextEditor::cycle_message_role))
|
||||
.capture_action(cx.listener(ContextEditor::confirm_command))
|
||||
.on_action(cx.listener(ContextEditor::assist))
|
||||
.on_action(cx.listener(ContextEditor::split))
|
||||
.capture_action(cx.listener(TextThreadEditor::cancel))
|
||||
.capture_action(cx.listener(TextThreadEditor::save))
|
||||
.capture_action(cx.listener(TextThreadEditor::copy))
|
||||
.capture_action(cx.listener(TextThreadEditor::cut))
|
||||
.capture_action(cx.listener(TextThreadEditor::paste))
|
||||
.capture_action(cx.listener(TextThreadEditor::cycle_message_role))
|
||||
.capture_action(cx.listener(TextThreadEditor::confirm_command))
|
||||
.on_action(cx.listener(TextThreadEditor::assist))
|
||||
.on_action(cx.listener(TextThreadEditor::split))
|
||||
.on_action(move |_: &ToggleModelSelector, window, cx| {
|
||||
language_model_selector.toggle(window, cx);
|
||||
})
|
||||
@@ -2632,13 +2642,13 @@ impl Render for ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for ContextEditor {
|
||||
impl Focusable for TextThreadEditor {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ContextEditor {
|
||||
impl Item for TextThreadEditor {
|
||||
type Event = editor::EditorEvent;
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
|
||||
@@ -2711,7 +2721,7 @@ impl Item for ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchableItem for ContextEditor {
|
||||
impl SearchableItem for TextThreadEditor {
|
||||
type Match = <Editor as SearchableItem>::Match;
|
||||
|
||||
fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -2792,7 +2802,7 @@ impl SearchableItem for ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
impl FollowableItem for ContextEditor {
|
||||
impl FollowableItem for TextThreadEditor {
|
||||
fn remote_id(&self) -> Option<workspace::ViewId> {
|
||||
self.remote_id
|
||||
}
|
||||
@@ -2915,21 +2925,14 @@ impl FollowableItem for ContextEditor {
|
||||
}
|
||||
|
||||
pub struct ContextEditorToolbarItem {
|
||||
active_context_editor: Option<WeakEntity<ContextEditor>>,
|
||||
active_context_editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
model_summary_editor: Entity<Editor>,
|
||||
}
|
||||
|
||||
impl ContextEditorToolbarItem {
|
||||
pub fn new(model_summary_editor: Entity<Editor>) -> Self {
|
||||
Self {
|
||||
active_context_editor: None,
|
||||
model_summary_editor,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ContextEditorToolbarItem {}
|
||||
|
||||
pub fn render_remaining_tokens(
|
||||
context_editor: &Entity<ContextEditor>,
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
cx: &App,
|
||||
) -> Option<impl IntoElement + use<>> {
|
||||
let context = &context_editor.read(cx).context;
|
||||
@@ -3045,7 +3048,7 @@ impl ToolbarItemView for ContextEditorToolbarItem {
|
||||
cx: &mut Context<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
self.active_context_editor = active_pane_item
|
||||
.and_then(|item| item.act_as::<ContextEditor>(cx))
|
||||
.and_then(|item| item.act_as::<TextThreadEditor>(cx))
|
||||
.map(|editor| editor.downgrade());
|
||||
cx.notify();
|
||||
if self.active_context_editor.is_none() {
|
||||
@@ -3406,7 +3409,7 @@ mod tests {
|
||||
cx: &mut TestAppContext,
|
||||
) -> (
|
||||
Entity<AssistantContext>,
|
||||
Entity<ContextEditor>,
|
||||
Entity<TextThreadEditor>,
|
||||
VisualTestContext,
|
||||
) {
|
||||
cx.update(init_test);
|
||||
@@ -3422,7 +3425,7 @@ mod tests {
|
||||
let context_editor = window
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
let editor = ContextEditor::for_context(
|
||||
let editor = TextThreadEditor::for_context(
|
||||
context.clone(),
|
||||
fs,
|
||||
workspace.downgrade(),
|
||||
@@ -3455,7 +3458,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
|
||||
context_editor: &Entity<ContextEditor>,
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
range: Range<T>,
|
||||
expected_text: &str,
|
||||
cx: &mut VisualTestContext,
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::fmt::Display;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{AgentPanel, RemoveSelectedThread};
|
||||
use agent::history_store::{HistoryEntry, HistoryStore};
|
||||
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
|
||||
use editor::{Editor, EditorEvent};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
@@ -9,6 +7,7 @@ use gpui::{
|
||||
App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task,
|
||||
UniformListScrollHandle, WeakEntity, Window, uniform_list,
|
||||
};
|
||||
use std::{fmt::Display, ops::Range, sync::Arc};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{
|
||||
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
|
||||
@@ -16,9 +15,6 @@ use ui::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::history_store::{HistoryEntry, HistoryStore};
|
||||
use crate::{AgentPanel, RemoveSelectedThread};
|
||||
|
||||
pub struct ThreadHistory {
|
||||
agent_panel: WeakEntity<AgentPanel>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
@@ -1,13 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::{Thread, ThreadEvent};
|
||||
use assistant_tool::{Tool, ToolSource};
|
||||
use collections::HashMap;
|
||||
use gpui::{App, Context, Entity, IntoElement, Render, Subscription, Window};
|
||||
use language_model::{LanguageModel, LanguageModelToolSchemaFormat};
|
||||
use std::sync::Arc;
|
||||
use ui::prelude::*;
|
||||
|
||||
use crate::{Thread, ThreadEvent};
|
||||
|
||||
pub struct IncompatibleToolsState {
|
||||
cache: HashMap<LanguageModelToolSchemaFormat, Vec<Arc<dyn Tool>>>,
|
||||
thread: Entity<Thread>,
|
||||
@@ -12,7 +12,7 @@ use prompt_store::PromptStore;
|
||||
use rope::Point;
|
||||
use ui::{IconButtonShape, Tooltip, prelude::*, tooltip_container};
|
||||
|
||||
use crate::context::{
|
||||
use agent::context::{
|
||||
AgentContext, AgentContextHandle, ContextId, ContextKind, DirectoryContext,
|
||||
DirectoryContextHandle, FetchedUrlContext, FileContext, FileContextHandle, ImageContext,
|
||||
ImageStatus, RulesContext, RulesContextHandle, SelectionContext, SelectionContextHandle,
|
||||
@@ -1,18 +1,17 @@
|
||||
use client::zed_urls;
|
||||
use client::{ModelRequestUsage, RequestUsage, zed_urls};
|
||||
use component::{empty_example, example_group_with_title, single_example};
|
||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
||||
use language_model::RequestUsage;
|
||||
use ui::{Callout, prelude::*};
|
||||
use zed_llm_client::{Plan, UsageLimit};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct UsageCallout {
|
||||
plan: Plan,
|
||||
usage: RequestUsage,
|
||||
usage: ModelRequestUsage,
|
||||
}
|
||||
|
||||
impl UsageCallout {
|
||||
pub fn new(plan: Plan, usage: RequestUsage) -> Self {
|
||||
pub fn new(plan: Plan, usage: ModelRequestUsage) -> Self {
|
||||
Self { plan, usage }
|
||||
}
|
||||
}
|
||||
@@ -128,10 +127,10 @@ impl Component for UsageCallout {
|
||||
"Approaching limit (90%)",
|
||||
UsageCallout::new(
|
||||
Plan::ZedFree,
|
||||
RequestUsage {
|
||||
ModelRequestUsage(RequestUsage {
|
||||
limit: UsageLimit::Limited(50),
|
||||
amount: 45, // 90% of limit
|
||||
},
|
||||
}),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -139,10 +138,10 @@ impl Component for UsageCallout {
|
||||
"Limit reached (100%)",
|
||||
UsageCallout::new(
|
||||
Plan::ZedFree,
|
||||
RequestUsage {
|
||||
ModelRequestUsage(RequestUsage {
|
||||
limit: UsageLimit::Limited(50),
|
||||
amount: 50, // 100% of limit
|
||||
},
|
||||
}),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -156,10 +155,10 @@ impl Component for UsageCallout {
|
||||
"Approaching limit (90%)",
|
||||
UsageCallout::new(
|
||||
Plan::ZedProTrial,
|
||||
RequestUsage {
|
||||
ModelRequestUsage(RequestUsage {
|
||||
limit: UsageLimit::Limited(150),
|
||||
amount: 135, // 90% of limit
|
||||
},
|
||||
}),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -167,10 +166,10 @@ impl Component for UsageCallout {
|
||||
"Limit reached (100%)",
|
||||
UsageCallout::new(
|
||||
Plan::ZedProTrial,
|
||||
RequestUsage {
|
||||
ModelRequestUsage(RequestUsage {
|
||||
limit: UsageLimit::Limited(150),
|
||||
amount: 150, // 100% of limit
|
||||
},
|
||||
}),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -184,10 +183,10 @@ impl Component for UsageCallout {
|
||||
"Limit reached (100%)",
|
||||
UsageCallout::new(
|
||||
Plan::ZedPro,
|
||||
RequestUsage {
|
||||
ModelRequestUsage(RequestUsage {
|
||||
limit: UsageLimit::Limited(500),
|
||||
amount: 500, // 100% of limit
|
||||
},
|
||||
}),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::io;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
|
||||
use http_client::http::{HeaderMap, HeaderValue};
|
||||
use http_client::http::{self, HeaderMap, HeaderValue};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString};
|
||||
@@ -336,7 +337,7 @@ pub async fn complete(
|
||||
let uri = format!("{api_url}/v1/messages");
|
||||
let beta_headers = Model::from_id(&request.model)
|
||||
.map(|model| model.beta_headers())
|
||||
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
|
||||
.unwrap_or_else(|_| Model::DEFAULT_BETA_HEADERS.join(","));
|
||||
let request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
@@ -346,39 +347,30 @@ pub async fn complete(
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
let serialized_request =
|
||||
serde_json::to_string(&request).context("failed to serialize request")?;
|
||||
serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
|
||||
let request = request_builder
|
||||
.body(AsyncBody::from(serialized_request))
|
||||
.context("failed to construct request body")?;
|
||||
.map_err(AnthropicError::BuildRequestBody)?;
|
||||
|
||||
let mut response = client
|
||||
.send(request)
|
||||
.await
|
||||
.context("failed to send request to Anthropic")?;
|
||||
if response.status().is_success() {
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("failed to read response body")?;
|
||||
let response_message: Response =
|
||||
serde_json::from_slice(&body).context("failed to deserialize response body")?;
|
||||
Ok(response_message)
|
||||
.map_err(AnthropicError::HttpSend)?;
|
||||
let status = response.status();
|
||||
let mut body = String::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_string(&mut body)
|
||||
.await
|
||||
.map_err(AnthropicError::ReadResponse)?;
|
||||
|
||||
if status.is_success() {
|
||||
Ok(serde_json::from_str(&body).map_err(AnthropicError::DeserializeResponse)?)
|
||||
} else {
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("failed to read response body")?;
|
||||
let body_str =
|
||||
std::str::from_utf8(&body).context("failed to parse response body as UTF-8")?;
|
||||
Err(AnthropicError::Other(anyhow!(
|
||||
"Failed to connect to API: {} {}",
|
||||
response.status(),
|
||||
body_str
|
||||
)))
|
||||
Err(AnthropicError::HttpResponseError {
|
||||
status: status.as_u16(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +483,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
let uri = format!("{api_url}/v1/messages");
|
||||
let beta_headers = Model::from_id(&request.base.model)
|
||||
.map(|model| model.beta_headers())
|
||||
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
|
||||
.unwrap_or_else(|_| Model::DEFAULT_BETA_HEADERS.join(","));
|
||||
let request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
@@ -500,15 +492,15 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("Content-Type", "application/json");
|
||||
let serialized_request =
|
||||
serde_json::to_string(&request).context("failed to serialize request")?;
|
||||
serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
|
||||
let request = request_builder
|
||||
.body(AsyncBody::from(serialized_request))
|
||||
.context("failed to construct request body")?;
|
||||
.map_err(AnthropicError::BuildRequestBody)?;
|
||||
|
||||
let mut response = client
|
||||
.send(request)
|
||||
.await
|
||||
.context("failed to send request to Anthropic")?;
|
||||
.map_err(AnthropicError::HttpSend)?;
|
||||
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
||||
if response.status().is_success() {
|
||||
let reader = BufReader::new(response.into_body());
|
||||
@@ -520,37 +512,31 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
let line = line.strip_prefix("data: ")?;
|
||||
match serde_json::from_str(line) {
|
||||
Ok(response) => Some(Ok(response)),
|
||||
Err(error) => Some(Err(AnthropicError::Other(anyhow!(error)))),
|
||||
Err(error) => Some(Err(AnthropicError::DeserializeResponse(error))),
|
||||
}
|
||||
}
|
||||
Err(error) => Some(Err(AnthropicError::Other(anyhow!(error)))),
|
||||
Err(error) => Some(Err(AnthropicError::ReadResponse(error))),
|
||||
}
|
||||
})
|
||||
.boxed();
|
||||
Ok((stream, Some(rate_limits)))
|
||||
} else if let Some(retry_after) = rate_limits.retry_after {
|
||||
Err(AnthropicError::RateLimit(retry_after))
|
||||
Err(AnthropicError::RateLimit { retry_after })
|
||||
} else {
|
||||
let mut body = Vec::new();
|
||||
let mut body = String::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.read_to_string(&mut body)
|
||||
.await
|
||||
.context("failed to read response body")?;
|
||||
.map_err(AnthropicError::ReadResponse)?;
|
||||
|
||||
let body_str =
|
||||
std::str::from_utf8(&body).context("failed to parse response body as UTF-8")?;
|
||||
|
||||
match serde_json::from_str::<Event>(body_str) {
|
||||
match serde_json::from_str::<Event>(&body) {
|
||||
Ok(Event::Error { error }) => Err(AnthropicError::ApiError(error)),
|
||||
Ok(_) => Err(AnthropicError::Other(anyhow!(
|
||||
"Unexpected success response while expecting an error: '{body_str}'",
|
||||
))),
|
||||
Err(_) => Err(AnthropicError::Other(anyhow!(
|
||||
"Failed to connect to API: {} {}",
|
||||
response.status(),
|
||||
body_str,
|
||||
))),
|
||||
Ok(_) => Err(AnthropicError::UnexpectedResponseFormat(body)),
|
||||
Err(_) => Err(AnthropicError::HttpResponseError {
|
||||
status: response.status().as_u16(),
|
||||
body: body,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -797,17 +783,38 @@ pub struct MessageDelta {
|
||||
pub stop_sequence: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub enum AnthropicError {
|
||||
#[error("rate limit exceeded, retry after {0:?}")]
|
||||
RateLimit(Duration),
|
||||
#[error("an error occurred while interacting with the Anthropic API: {error_type}: {message}", error_type = .0.error_type, message = .0.message)]
|
||||
/// Failed to serialize the HTTP request body to JSON
|
||||
SerializeRequest(serde_json::Error),
|
||||
|
||||
/// Failed to construct the HTTP request body
|
||||
BuildRequestBody(http::Error),
|
||||
|
||||
/// Failed to send the HTTP request
|
||||
HttpSend(anyhow::Error),
|
||||
|
||||
/// Failed to deserialize the response from JSON
|
||||
DeserializeResponse(serde_json::Error),
|
||||
|
||||
/// Failed to read from response stream
|
||||
ReadResponse(io::Error),
|
||||
|
||||
/// HTTP error response from the API
|
||||
HttpResponseError { status: u16, body: String },
|
||||
|
||||
/// Rate limit exceeded
|
||||
RateLimit { retry_after: Duration },
|
||||
|
||||
/// API returned an error response
|
||||
ApiError(ApiError),
|
||||
#[error("{0}")]
|
||||
Other(#[from] anyhow::Error),
|
||||
|
||||
/// Unexpected response format
|
||||
UnexpectedResponseFormat(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Error)]
|
||||
#[error("Anthropic API Error: {error_type}: {message}")]
|
||||
pub struct ApiError {
|
||||
#[serde(rename = "type")]
|
||||
pub error_type: String,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "assistant_context_editor"
|
||||
name = "assistant_context"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant_context_editor.rs"
|
||||
path = "src/assistant_context.rs"
|
||||
|
||||
[dependencies]
|
||||
agent_settings.workspace = true
|
||||
@@ -21,27 +21,20 @@ client.workspace = true
|
||||
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
|
||||
gpui.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
open_ai.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -50,21 +43,17 @@ smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
indoc.workspace = true
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
rand.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
1
crates/assistant_context/LICENSE-GPL
Symbolic link
1
crates/assistant_context/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,5 +1,6 @@
|
||||
#[cfg(test)]
|
||||
mod context_tests;
|
||||
mod assistant_context_tests;
|
||||
mod context_store;
|
||||
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
@@ -8,7 +9,7 @@ use assistant_slash_command::{
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
};
|
||||
use assistant_slash_commands::FileCommandMetadata;
|
||||
use client::{self, proto, telemetry::Telemetry};
|
||||
use client::{self, Client, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::{Fs, RenameOptions};
|
||||
@@ -47,6 +48,12 @@ use util::{ResultExt, TryFutureExt, post_inc};
|
||||
use uuid::Uuid;
|
||||
use zed_llm_client::CompletionIntent;
|
||||
|
||||
pub use crate::context_store::*;
|
||||
|
||||
pub fn init(client: Arc<Client>, _: &mut App) {
|
||||
context_store::init(&client.into());
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct ContextId(String);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
mod context;
|
||||
mod context_editor;
|
||||
mod context_history;
|
||||
mod context_store;
|
||||
pub mod language_model_selector;
|
||||
mod max_mode_tooltip;
|
||||
mod slash_command;
|
||||
mod slash_command_picker;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use client::Client;
|
||||
use gpui::{App, Context};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub use crate::context::*;
|
||||
pub use crate::context_editor::*;
|
||||
pub use crate::context_history::*;
|
||||
pub use crate::context_store::*;
|
||||
pub use crate::slash_command::*;
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut App) {
|
||||
context_store::init(&client.into());
|
||||
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
|
||||
|
||||
cx.observe_new(
|
||||
|workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
|
||||
workspace
|
||||
.register_action(ContextEditor::quote_selection)
|
||||
.register_action(ContextEditor::insert_selection)
|
||||
.register_action(ContextEditor::copy_code)
|
||||
.register_action(ContextEditor::handle_insert_dragged_files);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::Project;
|
||||
use ui::utils::{DateTimeType, format_distance_from_now};
|
||||
use ui::{Avatar, ListItem, ListItemSpacing, prelude::*};
|
||||
use workspace::{Item, Workspace};
|
||||
|
||||
use crate::{
|
||||
AgentPanelDelegate, ContextStore, DEFAULT_TAB_TITLE, RemoteContextMetadata,
|
||||
SavedContextMetadata,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ContextMetadata {
|
||||
Remote(RemoteContextMetadata),
|
||||
Saved(SavedContextMetadata),
|
||||
}
|
||||
|
||||
enum SavedContextPickerEvent {
|
||||
Confirmed(ContextMetadata),
|
||||
}
|
||||
|
||||
pub struct ContextHistory {
|
||||
picker: Entity<Picker<SavedContextPickerDelegate>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
}
|
||||
|
||||
impl ContextHistory {
|
||||
pub fn new(
|
||||
project: Entity<Project>,
|
||||
context_store: Entity<ContextStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let picker = cx.new(|cx| {
|
||||
Picker::uniform_list(
|
||||
SavedContextPickerDelegate::new(project, context_store.clone()),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.modal(false)
|
||||
.max_height(None)
|
||||
});
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.observe_in(&context_store, window, |this, _, window, cx| {
|
||||
this.picker
|
||||
.update(cx, |picker, cx| picker.refresh(window, cx));
|
||||
}),
|
||||
cx.subscribe_in(&picker, window, Self::handle_picker_event),
|
||||
];
|
||||
|
||||
Self {
|
||||
picker,
|
||||
_subscriptions: subscriptions,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_picker_event(
|
||||
&mut self,
|
||||
_: &Entity<Picker<SavedContextPickerDelegate>>,
|
||||
event: &SavedContextPickerEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let SavedContextPickerEvent::Confirmed(context) = event;
|
||||
|
||||
let Some(agent_panel_delegate) = <dyn AgentPanelDelegate>::try_global(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| match context {
|
||||
ContextMetadata::Remote(metadata) => {
|
||||
agent_panel_delegate
|
||||
.open_remote_context(workspace, metadata.id.clone(), window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
ContextMetadata::Saved(metadata) => {
|
||||
agent_panel_delegate
|
||||
.open_saved_context(workspace, metadata.path.clone(), window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContextHistory {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().size_full().child(self.picker.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for ContextHistory {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<()> for ContextHistory {}
|
||||
|
||||
impl Item for ContextHistory {
|
||||
type Event = ();
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||
"History".into()
|
||||
}
|
||||
}
|
||||
|
||||
struct SavedContextPickerDelegate {
|
||||
store: Entity<ContextStore>,
|
||||
project: Entity<Project>,
|
||||
matches: Vec<ContextMetadata>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
|
||||
|
||||
impl SavedContextPickerDelegate {
|
||||
fn new(project: Entity<Project>, store: Entity<ContextStore>) -> Self {
|
||||
Self {
|
||||
project,
|
||||
store,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for SavedContextPickerDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search...".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let search = self.store.read(cx).search(query, cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let matches = search.await;
|
||||
this.update(cx, |this, cx| {
|
||||
let host_contexts = this.delegate.store.read(cx).host_contexts();
|
||||
this.delegate.matches = host_contexts
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(ContextMetadata::Remote)
|
||||
.chain(matches.into_iter().map(ContextMetadata::Saved))
|
||||
.collect();
|
||||
this.delegate.selected_index = 0;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
if let Some(metadata) = self.matches.get(self.selected_index) {
|
||||
cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let context = self.matches.get(ix)?;
|
||||
let item = match context {
|
||||
ContextMetadata::Remote(context) => {
|
||||
let host_user = self.project.read(cx).host().and_then(|collaborator| {
|
||||
self.project
|
||||
.read(cx)
|
||||
.user_store()
|
||||
.read(cx)
|
||||
.get_cached_user(collaborator.user_id)
|
||||
});
|
||||
div()
|
||||
.flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex().flex_1().overflow_x_hidden().child(
|
||||
Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.children(if let Some(host_user) = host_user {
|
||||
vec![
|
||||
Avatar::new(host_user.avatar_uri.clone()).into_any_element(),
|
||||
Label::new(format!("Shared by @{}", host_user.github_login))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.into_any_element(),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
Label::new("Shared by host")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.into_any_element(),
|
||||
]
|
||||
}),
|
||||
)
|
||||
}
|
||||
ContextMetadata::Saved(context) => div()
|
||||
.flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.child(Label::new(context.title.clone()).size(LabelSize::Small))
|
||||
.overflow_x_hidden(),
|
||||
)
|
||||
.child(
|
||||
Label::new(format_distance_from_now(
|
||||
DateTimeType::Local(context.mtime),
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
};
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(item),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1659,13 +1659,13 @@ async fn retry_on_rate_limit<R>(mut request: impl AsyncFnMut() -> Result<R>) ->
|
||||
Ok(result) => return Ok(result),
|
||||
Err(err) => match err.downcast::<LanguageModelCompletionError>() {
|
||||
Ok(err) => match err {
|
||||
LanguageModelCompletionError::RateLimit(duration) => {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after } => {
|
||||
// Wait for the duration supplied, with some jitter to avoid all requests being made at the same time.
|
||||
let jitter = duration.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
let jitter = retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: Rate limit exceeded. Retry after {duration:?} + jitter of {jitter:?}"
|
||||
"Attempt #{attempt}: Rate limit exceeded. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(duration + jitter).await;
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
}
|
||||
_ => return Err(err.into()),
|
||||
|
||||
@@ -108,8 +108,12 @@ pub enum Model {
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn default_fast() -> Self {
|
||||
Self::Claude3_5Haiku
|
||||
pub fn default_fast(region: &str) -> Self {
|
||||
if region.starts_with("us-") {
|
||||
Self::Claude3_5Haiku
|
||||
} else {
|
||||
Self::Claude3Haiku
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_id(id: &str) -> anyhow::Result<Self> {
|
||||
@@ -483,6 +487,8 @@ impl Model {
|
||||
Model::Claude3_5Sonnet
|
||||
| Model::Claude3_7Sonnet
|
||||
| Model::Claude3_7SonnetThinking
|
||||
| Model::ClaudeSonnet4
|
||||
| Model::ClaudeSonnet4Thinking
|
||||
| Model::Claude3Haiku
|
||||
| Model::Claude3Sonnet
|
||||
| Model::MetaLlama321BInstructV1
|
||||
@@ -496,7 +502,11 @@ impl Model {
|
||||
Model::Claude3_5Sonnet
|
||||
| Model::Claude3_5SonnetV2
|
||||
| Model::Claude3Haiku
|
||||
| Model::Claude3Sonnet,
|
||||
| Model::Claude3Sonnet
|
||||
| Model::Claude3_7Sonnet
|
||||
| Model::Claude3_7SonnetThinking
|
||||
| Model::ClaudeSonnet4
|
||||
| Model::ClaudeSonnet4Thinking,
|
||||
"apac",
|
||||
) => Ok(format!("{}.{}", region_group, model_id)),
|
||||
|
||||
@@ -531,6 +541,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_eu_region_inference_ids() -> anyhow::Result<()> {
|
||||
// Test European regions
|
||||
assert_eq!(
|
||||
Model::ClaudeSonnet4.cross_region_inference_id("eu-west-1")?,
|
||||
"eu.anthropic.claude-sonnet-4-20250514-v1:0"
|
||||
);
|
||||
assert_eq!(
|
||||
Model::Claude3Sonnet.cross_region_inference_id("eu-west-1")?,
|
||||
"eu.anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
|
||||
@@ -24,6 +24,7 @@ futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc.workspace = true
|
||||
|
||||
@@ -4,13 +4,14 @@ use crate::{ChannelMessage, channel_buffer::ChannelBuffer, channel_chat::Channel
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use channel_index::ChannelIndex;
|
||||
use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, UserStore};
|
||||
use collections::{HashMap, HashSet, hash_map};
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::{Future, FutureExt, StreamExt, channel::mpsc, future::Shared};
|
||||
use gpui::{
|
||||
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Global, SharedString, Task,
|
||||
WeakEntity,
|
||||
};
|
||||
use language::Capability;
|
||||
use postage::{sink::Sink, watch};
|
||||
use rpc::{
|
||||
TypedEnvelope,
|
||||
proto::{self, ChannelRole, ChannelVisibility},
|
||||
@@ -43,6 +44,7 @@ pub struct ChannelStore {
|
||||
opened_chats: HashMap<ChannelId, OpenEntityHandle<ChannelChat>>,
|
||||
client: Arc<Client>,
|
||||
did_subscribe: bool,
|
||||
channels_loaded: (watch::Sender<bool>, watch::Receiver<bool>),
|
||||
user_store: Entity<UserStore>,
|
||||
_rpc_subscriptions: [Subscription; 2],
|
||||
_watch_connection_status: Task<Option<()>>,
|
||||
@@ -219,6 +221,7 @@ impl ChannelStore {
|
||||
}),
|
||||
channel_states: Default::default(),
|
||||
did_subscribe: false,
|
||||
channels_loaded: watch::channel_with(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +237,48 @@ impl ChannelStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_for_channels(
|
||||
&mut self,
|
||||
timeout: Duration,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let mut channels_loaded_rx = self.channels_loaded.1.clone();
|
||||
if *channels_loaded_rx.borrow() {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let mut status_receiver = self.client.status();
|
||||
if status_receiver.borrow().is_connected() {
|
||||
self.initialize();
|
||||
}
|
||||
|
||||
let mut timer = cx.background_executor().timer(timeout).fuse();
|
||||
cx.spawn(async move |this, cx| {
|
||||
loop {
|
||||
futures::select_biased! {
|
||||
channels_loaded = channels_loaded_rx.next().fuse() => {
|
||||
if let Some(true) = channels_loaded {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
status = status_receiver.next().fuse() => {
|
||||
if let Some(status) = status {
|
||||
if status.is_connected() {
|
||||
this.update(cx, |this, _cx| {
|
||||
this.initialize();
|
||||
}).ok();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ = timer => {
|
||||
return Err(anyhow!("{:?} elapsed without receiving channels", timeout));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Arc<Client> {
|
||||
self.client.clone()
|
||||
}
|
||||
@@ -309,6 +354,7 @@ impl ChannelStore {
|
||||
let channel_store = cx.entity();
|
||||
self.open_channel_resource(
|
||||
channel_id,
|
||||
"notes",
|
||||
|this| &mut this.opened_buffers,
|
||||
async move |channel, cx| {
|
||||
ChannelBuffer::new(channel, client, user_store, channel_store, cx).await
|
||||
@@ -439,6 +485,7 @@ impl ChannelStore {
|
||||
let this = cx.entity();
|
||||
self.open_channel_resource(
|
||||
channel_id,
|
||||
"chat",
|
||||
|this| &mut this.opened_chats,
|
||||
async move |channel, cx| ChannelChat::new(channel, this, user_store, client, cx).await,
|
||||
cx,
|
||||
@@ -453,6 +500,7 @@ impl ChannelStore {
|
||||
fn open_channel_resource<T, F>(
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
resource_name: &'static str,
|
||||
get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenEntityHandle<T>>,
|
||||
load: F,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -462,58 +510,56 @@ impl ChannelStore {
|
||||
T: 'static,
|
||||
{
|
||||
let task = loop {
|
||||
match get_map(self).entry(channel_id) {
|
||||
hash_map::Entry::Occupied(e) => match e.get() {
|
||||
OpenEntityHandle::Open(entity) => {
|
||||
if let Some(entity) = entity.upgrade() {
|
||||
break Task::ready(Ok(entity)).shared();
|
||||
} else {
|
||||
get_map(self).remove(&channel_id);
|
||||
continue;
|
||||
}
|
||||
match get_map(self).get(&channel_id) {
|
||||
Some(OpenEntityHandle::Open(entity)) => {
|
||||
if let Some(entity) = entity.upgrade() {
|
||||
break Task::ready(Ok(entity)).shared();
|
||||
} else {
|
||||
get_map(self).remove(&channel_id);
|
||||
continue;
|
||||
}
|
||||
OpenEntityHandle::Loading(task) => {
|
||||
break task.clone();
|
||||
}
|
||||
},
|
||||
hash_map::Entry::Vacant(e) => {
|
||||
let task = cx
|
||||
.spawn(async move |this, cx| {
|
||||
let channel = this.read_with(cx, |this, _| {
|
||||
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
|
||||
Arc::new(anyhow!("no channel for id: {channel_id}"))
|
||||
})
|
||||
})??;
|
||||
|
||||
load(channel, cx).await.map_err(Arc::new)
|
||||
})
|
||||
.shared();
|
||||
|
||||
e.insert(OpenEntityHandle::Loading(task.clone()));
|
||||
cx.spawn({
|
||||
let task = task.clone();
|
||||
async move |this, cx| {
|
||||
let result = task.await;
|
||||
this.update(cx, |this, _| match result {
|
||||
Ok(model) => {
|
||||
get_map(this).insert(
|
||||
channel_id,
|
||||
OpenEntityHandle::Open(model.downgrade()),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
get_map(this).remove(&channel_id);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
break task;
|
||||
}
|
||||
Some(OpenEntityHandle::Loading(task)) => break task.clone(),
|
||||
None => {}
|
||||
}
|
||||
|
||||
let channels_ready = self.wait_for_channels(Duration::from_secs(10), cx);
|
||||
let task = cx
|
||||
.spawn(async move |this, cx| {
|
||||
channels_ready.await?;
|
||||
let channel = this.read_with(cx, |this, _| {
|
||||
this.channel_for_id(channel_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| Arc::new(anyhow!("no channel for id: {channel_id}")))
|
||||
})??;
|
||||
|
||||
load(channel, cx).await.map_err(Arc::new)
|
||||
})
|
||||
.shared();
|
||||
|
||||
get_map(self).insert(channel_id, OpenEntityHandle::Loading(task.clone()));
|
||||
let task = cx.spawn({
|
||||
async move |this, cx| {
|
||||
let result = task.await;
|
||||
this.update(cx, |this, _| match &result {
|
||||
Ok(model) => {
|
||||
get_map(this)
|
||||
.insert(channel_id, OpenEntityHandle::Open(model.downgrade()));
|
||||
}
|
||||
Err(_) => {
|
||||
get_map(this).remove(&channel_id);
|
||||
}
|
||||
})?;
|
||||
result
|
||||
}
|
||||
});
|
||||
break task.shared();
|
||||
};
|
||||
cx.background_spawn(async move { task.await.map_err(|error| anyhow!("{error}")) })
|
||||
cx.background_spawn(async move {
|
||||
task.await.map_err(|error| {
|
||||
anyhow!("{error}").context(format!("failed to open channel {resource_name}"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool {
|
||||
@@ -1147,6 +1193,8 @@ impl ChannelStore {
|
||||
.or_default()
|
||||
.update_latest_message_id(latest_channel_message.message_id);
|
||||
}
|
||||
|
||||
self.channels_loaded.0.try_send(true).log_err();
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
||||
@@ -134,7 +134,7 @@ fn main() -> Result<()> {
|
||||
util::prevent_root_execution();
|
||||
|
||||
// Exit flatpak sandbox if needed
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
flatpak::try_restart_to_host();
|
||||
flatpak::ld_extra_libs();
|
||||
@@ -158,7 +158,7 @@ fn main() -> Result<()> {
|
||||
paths::set_custom_data_dir(dir);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
let args = flatpak::set_bin_if_no_escape(args);
|
||||
|
||||
let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?;
|
||||
@@ -374,7 +374,7 @@ fn anonymous_fd(path: &str) -> Option<fs::File> {
|
||||
let file = unsafe { fs::File::from_raw_fd(fd) };
|
||||
return Some(file);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
{
|
||||
use std::os::{
|
||||
fd::{self, FromRawFd},
|
||||
@@ -392,7 +392,7 @@ fn anonymous_fd(path: &str) -> Option<fs::File> {
|
||||
let file = unsafe { fs::File::from_raw_fd(fd) };
|
||||
return Some(file);
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))]
|
||||
{
|
||||
_ = path;
|
||||
// not implemented for bsd, windows. Could be, but isn't yet
|
||||
@@ -537,7 +537,7 @@ mod linux {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
mod flatpak {
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -24,6 +24,7 @@ chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
credentials_provider.workspace = true
|
||||
derive_more.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
@@ -57,6 +58,7 @@ worktree.workspace = true
|
||||
telemetry.workspace = true
|
||||
tokio.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -83,10 +83,14 @@ pub fn os_name() -> String {
|
||||
{
|
||||
"macOS".to_string()
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
format!("Linux {}", gpui::guess_compositor())
|
||||
}
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
format!("FreeBSD {}", gpui::guess_compositor())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
@@ -120,8 +124,12 @@ pub fn os_version() -> String {
|
||||
file
|
||||
} else if let Ok(file) = std::fs::read_to_string(&Path::new("/usr/lib/os-release")) {
|
||||
file
|
||||
} else if let Ok(file) = std::fs::read_to_string(&Path::new("/var/run/os-release")) {
|
||||
file
|
||||
} else {
|
||||
log::error!("Failed to load /etc/os-release, /usr/lib/os-release");
|
||||
log::error!(
|
||||
"Failed to load /etc/os-release, /usr/lib/os-release, or /var/run/os-release"
|
||||
);
|
||||
"".to_string()
|
||||
};
|
||||
let mut name = "unknown";
|
||||
|
||||
@@ -2,16 +2,25 @@ use super::{Client, Status, TypedEnvelope, proto};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{HashMap, HashSet, hash_map::Entry};
|
||||
use derive_more::Deref;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use futures::{Future, StreamExt, channel::mpsc};
|
||||
use gpui::{
|
||||
App, AsyncApp, Context, Entity, EventEmitter, SharedString, SharedUri, Task, WeakEntity,
|
||||
};
|
||||
use http_client::http::{HeaderMap, HeaderValue};
|
||||
use postage::{sink::Sink, watch};
|
||||
use rpc::proto::{RequestMessage, UsersResponse};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::{
|
||||
str::FromStr as _,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use text::ReplicaId;
|
||||
use util::{TryFutureExt as _, maybe};
|
||||
use zed_llm_client::{
|
||||
EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME,
|
||||
MODEL_REQUESTS_USAGE_AMOUNT_HEADER_NAME, MODEL_REQUESTS_USAGE_LIMIT_HEADER_NAME, UsageLimit,
|
||||
};
|
||||
|
||||
pub type UserId = u64;
|
||||
|
||||
@@ -104,10 +113,8 @@ pub struct UserStore {
|
||||
current_plan: Option<proto::Plan>,
|
||||
subscription_period: Option<(DateTime<Utc>, DateTime<Utc>)>,
|
||||
trial_started_at: Option<DateTime<Utc>>,
|
||||
model_request_usage_amount: Option<u32>,
|
||||
model_request_usage_limit: Option<proto::UsageLimit>,
|
||||
edit_predictions_usage_amount: Option<u32>,
|
||||
edit_predictions_usage_limit: Option<proto::UsageLimit>,
|
||||
model_request_usage: Option<ModelRequestUsage>,
|
||||
edit_prediction_usage: Option<EditPredictionUsage>,
|
||||
is_usage_based_billing_enabled: Option<bool>,
|
||||
account_too_young: Option<bool>,
|
||||
has_overdue_invoices: Option<bool>,
|
||||
@@ -155,6 +162,18 @@ enum UpdateContacts {
|
||||
Clear(postage::barrier::Sender),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deref)]
|
||||
pub struct ModelRequestUsage(pub RequestUsage);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deref)]
|
||||
pub struct EditPredictionUsage(pub RequestUsage);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RequestUsage {
|
||||
pub limit: UsageLimit,
|
||||
pub amount: i32,
|
||||
}
|
||||
|
||||
impl UserStore {
|
||||
pub fn new(client: Arc<Client>, cx: &Context<Self>) -> Self {
|
||||
let (mut current_user_tx, current_user_rx) = watch::channel();
|
||||
@@ -172,10 +191,8 @@ impl UserStore {
|
||||
current_plan: None,
|
||||
subscription_period: None,
|
||||
trial_started_at: None,
|
||||
model_request_usage_amount: None,
|
||||
model_request_usage_limit: None,
|
||||
edit_predictions_usage_amount: None,
|
||||
edit_predictions_usage_limit: None,
|
||||
model_request_usage: None,
|
||||
edit_prediction_usage: None,
|
||||
is_usage_based_billing_enabled: None,
|
||||
account_too_young: None,
|
||||
has_overdue_invoices: None,
|
||||
@@ -356,10 +373,19 @@ impl UserStore {
|
||||
this.has_overdue_invoices = message.payload.has_overdue_invoices;
|
||||
|
||||
if let Some(usage) = message.payload.usage {
|
||||
this.model_request_usage_amount = Some(usage.model_requests_usage_amount);
|
||||
this.model_request_usage_limit = usage.model_requests_usage_limit;
|
||||
this.edit_predictions_usage_amount = Some(usage.edit_predictions_usage_amount);
|
||||
this.edit_predictions_usage_limit = usage.edit_predictions_usage_limit;
|
||||
// limits are always present even though they are wrapped in Option
|
||||
this.model_request_usage = usage
|
||||
.model_requests_usage_limit
|
||||
.and_then(|limit| {
|
||||
RequestUsage::from_proto(usage.model_requests_usage_amount, limit)
|
||||
})
|
||||
.map(ModelRequestUsage);
|
||||
this.edit_prediction_usage = usage
|
||||
.edit_predictions_usage_limit
|
||||
.and_then(|limit| {
|
||||
RequestUsage::from_proto(usage.model_requests_usage_amount, limit)
|
||||
})
|
||||
.map(EditPredictionUsage);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -367,6 +393,20 @@ impl UserStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_model_request_usage(&mut self, usage: ModelRequestUsage, cx: &mut Context<Self>) {
|
||||
self.model_request_usage = Some(usage);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn update_edit_prediction_usage(
|
||||
&mut self,
|
||||
usage: EditPredictionUsage,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.edit_prediction_usage = Some(usage);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn update_contacts(&mut self, message: UpdateContacts, cx: &Context<Self>) -> Task<Result<()>> {
|
||||
match message {
|
||||
UpdateContacts::Wait(barrier) => {
|
||||
@@ -739,20 +779,12 @@ impl UserStore {
|
||||
self.is_usage_based_billing_enabled
|
||||
}
|
||||
|
||||
pub fn model_request_usage_amount(&self) -> Option<u32> {
|
||||
self.model_request_usage_amount
|
||||
pub fn model_request_usage(&self) -> Option<ModelRequestUsage> {
|
||||
self.model_request_usage
|
||||
}
|
||||
|
||||
pub fn model_request_usage_limit(&self) -> Option<proto::UsageLimit> {
|
||||
self.model_request_usage_limit.clone()
|
||||
}
|
||||
|
||||
pub fn edit_predictions_usage_amount(&self) -> Option<u32> {
|
||||
self.edit_predictions_usage_amount
|
||||
}
|
||||
|
||||
pub fn edit_predictions_usage_limit(&self) -> Option<proto::UsageLimit> {
|
||||
self.edit_predictions_usage_limit.clone()
|
||||
pub fn edit_prediction_usage(&self) -> Option<EditPredictionUsage> {
|
||||
self.edit_prediction_usage
|
||||
}
|
||||
|
||||
pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
|
||||
@@ -917,3 +949,63 @@ impl Collaborator {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestUsage {
|
||||
pub fn over_limit(&self) -> bool {
|
||||
match self.limit {
|
||||
UsageLimit::Limited(limit) => self.amount >= limit,
|
||||
UsageLimit::Unlimited => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_proto(amount: u32, limit: proto::UsageLimit) -> Option<Self> {
|
||||
let limit = match limit.variant? {
|
||||
proto::usage_limit::Variant::Limited(limited) => {
|
||||
UsageLimit::Limited(limited.limit as i32)
|
||||
}
|
||||
proto::usage_limit::Variant::Unlimited(_) => UsageLimit::Unlimited,
|
||||
};
|
||||
Some(RequestUsage {
|
||||
limit,
|
||||
amount: amount as i32,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_headers(
|
||||
limit_name: &str,
|
||||
amount_name: &str,
|
||||
headers: &HeaderMap<HeaderValue>,
|
||||
) -> Result<Self> {
|
||||
let limit = headers
|
||||
.get(limit_name)
|
||||
.with_context(|| format!("missing {limit_name:?} header"))?;
|
||||
let limit = UsageLimit::from_str(limit.to_str()?)?;
|
||||
|
||||
let amount = headers
|
||||
.get(amount_name)
|
||||
.with_context(|| format!("missing {amount_name:?} header"))?;
|
||||
let amount = amount.to_str()?.parse::<i32>()?;
|
||||
|
||||
Ok(Self { limit, amount })
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelRequestUsage {
|
||||
pub fn from_headers(headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
||||
Ok(Self(RequestUsage::from_headers(
|
||||
MODEL_REQUESTS_USAGE_LIMIT_HEADER_NAME,
|
||||
MODEL_REQUESTS_USAGE_AMOUNT_HEADER_NAME,
|
||||
headers,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl EditPredictionUsage {
|
||||
pub fn from_headers(headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
||||
Ok(Self(RequestUsage::from_headers(
|
||||
EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME,
|
||||
EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME,
|
||||
headers,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ zed_llm_client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
agent_settings.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-trait.workspace = true
|
||||
audio.workspace = true
|
||||
|
||||
@@ -11,8 +11,9 @@ use crate::Result;
|
||||
use crate::db::billing_subscription::SubscriptionKind;
|
||||
use crate::llm::AGENT_EXTENDED_TRIAL_FEATURE_FLAG;
|
||||
use crate::stripe_client::{
|
||||
RealStripeClient, StripeCheckoutSessionMode, StripeCheckoutSessionPaymentMethodCollection,
|
||||
StripeClient, StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
|
||||
RealStripeClient, StripeBillingAddressCollection, StripeCheckoutSessionMode,
|
||||
StripeCheckoutSessionPaymentMethodCollection, StripeClient,
|
||||
StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
|
||||
StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
|
||||
StripeCreateMeterEventPayload, StripeCreateSubscriptionItems, StripeCreateSubscriptionParams,
|
||||
StripeCustomerId, StripeMeter, StripePrice, StripePriceId, StripeSubscription,
|
||||
@@ -245,6 +246,7 @@ impl StripeBilling {
|
||||
quantity: Some(1),
|
||||
}]);
|
||||
params.success_url = Some(success_url);
|
||||
params.billing_address_collection = Some(StripeBillingAddressCollection::Required);
|
||||
|
||||
let session = self.client.create_checkout_session(params).await?;
|
||||
Ok(session.url.context("no checkout session URL")?)
|
||||
@@ -298,6 +300,7 @@ impl StripeBilling {
|
||||
quantity: Some(1),
|
||||
}]);
|
||||
params.success_url = Some(success_url);
|
||||
params.billing_address_collection = Some(StripeBillingAddressCollection::Required);
|
||||
|
||||
let session = self.client.create_checkout_session(params).await?;
|
||||
Ok(session.url.context("no checkout session URL")?)
|
||||
|
||||
@@ -148,6 +148,12 @@ pub struct StripeCreateMeterEventPayload<'a> {
|
||||
pub stripe_customer_id: &'a StripeCustomerId,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum StripeBillingAddressCollection {
|
||||
Auto,
|
||||
Required,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StripeCreateCheckoutSessionParams<'a> {
|
||||
pub customer: Option<&'a StripeCustomerId>,
|
||||
@@ -157,6 +163,7 @@ pub struct StripeCreateCheckoutSessionParams<'a> {
|
||||
pub payment_method_collection: Option<StripeCheckoutSessionPaymentMethodCollection>,
|
||||
pub subscription_data: Option<StripeCreateCheckoutSessionSubscriptionData>,
|
||||
pub success_url: Option<&'a str>,
|
||||
pub billing_address_collection: Option<StripeBillingAddressCollection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
|
||||
@@ -8,8 +8,8 @@ use parking_lot::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::stripe_client::{
|
||||
CreateCustomerParams, StripeCheckoutSession, StripeCheckoutSessionMode,
|
||||
StripeCheckoutSessionPaymentMethodCollection, StripeClient,
|
||||
CreateCustomerParams, StripeBillingAddressCollection, StripeCheckoutSession,
|
||||
StripeCheckoutSessionMode, StripeCheckoutSessionPaymentMethodCollection, StripeClient,
|
||||
StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
|
||||
StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
|
||||
StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeMeter, StripeMeterId,
|
||||
@@ -35,6 +35,7 @@ pub struct StripeCreateCheckoutSessionCall {
|
||||
pub payment_method_collection: Option<StripeCheckoutSessionPaymentMethodCollection>,
|
||||
pub subscription_data: Option<StripeCreateCheckoutSessionSubscriptionData>,
|
||||
pub success_url: Option<String>,
|
||||
pub billing_address_collection: Option<StripeBillingAddressCollection>,
|
||||
}
|
||||
|
||||
pub struct FakeStripeClient {
|
||||
@@ -231,6 +232,7 @@ impl StripeClient for FakeStripeClient {
|
||||
payment_method_collection: params.payment_method_collection,
|
||||
subscription_data: params.subscription_data,
|
||||
success_url: params.success_url.map(|url| url.to_string()),
|
||||
billing_address_collection: params.billing_address_collection,
|
||||
});
|
||||
|
||||
Ok(StripeCheckoutSession {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user