Compare commits
13 Commits
livekit-ru
...
add-layout
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6240740605 | ||
|
|
2306d9fd91 | ||
|
|
e7cf1305b8 | ||
|
|
f699083b5b | ||
|
|
956eb5b3b0 | ||
|
|
af237f81a3 | ||
|
|
d6b1949143 | ||
|
|
e5c738daaa | ||
|
|
eba035eb5b | ||
|
|
1c65253b0f | ||
|
|
5d4bc1e492 | ||
|
|
dc84dbb6e2 | ||
|
|
903343e608 |
@@ -13,12 +13,6 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = ["-C", "link-args=-Objc -all_load"]
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-C", "link-args=-Objc -all_load"]
|
||||
|
||||
# This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["--cfg", "windows_slim_errors"]
|
||||
|
||||
@@ -3,6 +3,15 @@ export default {
|
||||
const url = new URL(request.url);
|
||||
url.hostname = "docs-anw.pages.dev";
|
||||
|
||||
// These pages were removed, but may still be served due to Cloudflare's
|
||||
// [asset retention](https://developers.cloudflare.com/pages/configuration/serving-pages/#asset-retention).
|
||||
if (
|
||||
url.pathname === "/docs/assistant/context-servers" ||
|
||||
url.pathname === "/docs/assistant/model-context-protocol"
|
||||
) {
|
||||
return await fetch("https://zed.dev/404");
|
||||
}
|
||||
|
||||
let res = await fetch(url, request);
|
||||
|
||||
if (res.status === 404) {
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -19,7 +19,7 @@ body:
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -21,7 +21,7 @@ body:
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -20,7 +20,7 @@ body:
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
29
.github/workflows/ci.yml
vendored
@@ -245,7 +245,6 @@ jobs:
|
||||
# 25 was chosen arbitrarily.
|
||||
fetch-depth: 25
|
||||
clean: false
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
@@ -262,9 +261,6 @@ jobs:
|
||||
mkdir -p target/
|
||||
# Ignore any errors that occur while drafting release notes to not fail the build.
|
||||
script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true
|
||||
script/create-draft-release target/release-notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
@@ -272,12 +268,18 @@ jobs:
|
||||
- name: Create macOS app bundle
|
||||
run: script/bundle-mac
|
||||
|
||||
- name: Rename binaries
|
||||
- name: Rename single-architecture binaries
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
run: |
|
||||
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (universal) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: target/release/Zed.dmg
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
@@ -303,6 +305,8 @@ jobs:
|
||||
target/zed-remote-server-macos-aarch64.gz
|
||||
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
target/release/Zed.dmg
|
||||
body_path: target/release-notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -349,6 +353,7 @@ jobs:
|
||||
files: |
|
||||
target/zed-remote-server-linux-x86_64.gz
|
||||
target/release/zed-linux-x86_64.tar.gz
|
||||
body: ""
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -395,18 +400,6 @@ jobs:
|
||||
files: |
|
||||
target/zed-remote-server-linux-aarch64.gz
|
||||
target/release/zed-linux-aarch64.tar.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }}
|
||||
needs: [bundle-mac, bundle-linux, bundle-linux-aarch64]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
steps:
|
||||
- name: gh release
|
||||
run: gh release edit $GITHUB_REF_NAME --draft=false
|
||||
body: ""
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
||||
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
||||
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
|
||||
21
.github/workflows/script_checks.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Script
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "script/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
name: "ShellCheck Scripts"
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Shellcheck ./scripts
|
||||
run: |
|
||||
./script/shellcheck-scripts error
|
||||
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
/.direnv
|
||||
.envrc
|
||||
.idea
|
||||
**/target
|
||||
**/cargo-target
|
||||
|
||||
12
.mailmap
@@ -22,14 +22,10 @@ Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
||||
Bennet Bo Fenner <bennet@zed.dev>
|
||||
Bennet Bo Fenner <bennet@zed.dev> <53836821+bennetbo@users.noreply.github.com>
|
||||
Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de>
|
||||
Boris Cherny <boris@anthropic.com>
|
||||
Boris Cherny <boris@anthropic.com> <boris@performancejs.com>
|
||||
Chris Hayes <chris+git@hayes.software>
|
||||
Christian Bergschneider <christian.bergschneider@gmx.de>
|
||||
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
||||
Conrad Irwin <conrad@zed.dev>
|
||||
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||
Dairon Medina <dairon.medina@gmail.com>
|
||||
Danilo Leal <danilo@zed.dev>
|
||||
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
|
||||
Evren Sen <nervenes@icloud.com>
|
||||
@@ -39,7 +35,6 @@ Fernando Tagawa <tagawafernando@gmail.com>
|
||||
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
||||
Greg Morenz <greg-morenz@droid.cafe>
|
||||
Greg Morenz <greg-morenz@droid.cafe> <morenzg@gmail.com>
|
||||
Ihnat Aŭtuška <autushka.ihnat@gmail.com>
|
||||
Ivan Žužak <izuzak@gmail.com>
|
||||
Ivan Žužak <izuzak@gmail.com> <ivan.zuzak@github.com>
|
||||
Joseph T. Lyons <JosephTLyons@gmail.com>
|
||||
@@ -66,13 +61,10 @@ Max Brunsfeld <maxbrunsfeld@gmail.com> <max@zed.dev>
|
||||
Max Linke <maxlinke88@gmail.com>
|
||||
Max Linke <maxlinke88@gmail.com> <kain88-de@users.noreply.github.com>
|
||||
Michael Sloan <michael@zed.dev>
|
||||
Michael Sloan <michael@zed.dev> <mgsloan@gmail.com>
|
||||
Michael Sloan <michael@zed.dev> <mgsloan@google.com>
|
||||
Mikayla Maki <mikayla@zed.dev>
|
||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
|
||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
|
||||
Muhammad Talal Anwar <mail@talal.io>
|
||||
Muhammad Talal Anwar <mail@talal.io> <talalanwar@outlook.com>
|
||||
Nate Butler <iamnbutler@gmail.com>
|
||||
Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
|
||||
Nathan Sobo <nathan@zed.dev>
|
||||
@@ -96,11 +88,7 @@ Robert Clover <git@clo4.net>
|
||||
Robert Clover <git@clo4.net> <robert@clover.gdn>
|
||||
Roy Williams <roy.williams.iii@gmail.com>
|
||||
Roy Williams <roy.williams.iii@gmail.com> <roy@anthropic.com>
|
||||
Sebastijan Kelnerič <sebastijan.kelneric@sebba.dev>
|
||||
Sebastijan Kelnerič <sebastijan.kelneric@sebba.dev> <sebastijan.kelneric@vichava.com>
|
||||
Sergey Onufrienko <sergey@onufrienko.com>
|
||||
Shish <webmaster@shishnet.org>
|
||||
Shish <webmaster@shishnet.org> <shish@shishnet.org>
|
||||
Thorben Kröger <dev@thorben.net>
|
||||
Thorben Kröger <dev@thorben.net> <thorben.kroeger@hexagon.com>
|
||||
Thorsten Ball <thorsten@zed.dev>
|
||||
|
||||
2730
Cargo.lock
generated
102
Cargo.toml
@@ -5,13 +5,10 @@ members = [
|
||||
"crates/anthropic",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/assistant2",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_tool",
|
||||
"crates/assistant_tools",
|
||||
"crates/audio",
|
||||
"crates/auto_update",
|
||||
"crates/auto_update_ui",
|
||||
"crates/breadcrumbs",
|
||||
"crates/call",
|
||||
"crates/channel",
|
||||
@@ -23,8 +20,7 @@ members = [
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/context_server",
|
||||
"crates/context_server_settings",
|
||||
"crates/context_servers",
|
||||
"crates/copilot",
|
||||
"crates/db",
|
||||
"crates/diagnostics",
|
||||
@@ -53,15 +49,11 @@ members = [
|
||||
"crates/http_client",
|
||||
"crates/image_viewer",
|
||||
"crates/indexed_docs",
|
||||
"crates/inline_completion",
|
||||
"crates/inline_completion_button",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_extension",
|
||||
"crates/language_model",
|
||||
"crates/language_model_selector",
|
||||
"crates/language_models",
|
||||
"crates/language_selector",
|
||||
"crates/language_tools",
|
||||
"crates/languages",
|
||||
@@ -86,6 +78,7 @@ members = [
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/proto",
|
||||
"crates/quick_action_bar",
|
||||
"crates/recent_projects",
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
@@ -121,7 +114,6 @@ members = [
|
||||
"crates/terminal_view",
|
||||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme_extension",
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
"crates/time_format",
|
||||
@@ -134,7 +126,6 @@ members = [
|
||||
"crates/util",
|
||||
"crates/vcs_menu",
|
||||
"crates/vim",
|
||||
"crates/vim_mode_setting",
|
||||
"crates/welcome",
|
||||
"crates/workspace",
|
||||
"crates/worktree",
|
||||
@@ -157,6 +148,7 @@ members = [
|
||||
"extensions/haskell",
|
||||
"extensions/html",
|
||||
"extensions/lua",
|
||||
"extensions/ocaml",
|
||||
"extensions/php",
|
||||
"extensions/perplexity",
|
||||
"extensions/prisma",
|
||||
@@ -190,13 +182,10 @@ ai = { path = "crates/ai" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
assistant2 = { path = "crates/assistant2" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_tool = { path = "crates/assistant_tool" }
|
||||
assistant_tools = { path = "crates/assistant_tools" }
|
||||
audio = { path = "crates/audio" }
|
||||
auto_update = { path = "crates/auto_update" }
|
||||
auto_update_ui = { path = "crates/auto_update_ui" }
|
||||
breadcrumbs = { path = "crates/breadcrumbs" }
|
||||
call = { path = "crates/call" }
|
||||
channel = { path = "crates/channel" }
|
||||
@@ -208,8 +197,7 @@ collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
context_server = { path = "crates/context_server" }
|
||||
context_server_settings = { path = "crates/context_server_settings" }
|
||||
context_servers = { path = "crates/context_servers" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
db = { path = "crates/db" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
@@ -228,23 +216,17 @@ git = { path = "crates/git" }
|
||||
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||
go_to_line = { path = "crates/go_to_line" }
|
||||
google_ai = { path = "crates/google_ai" }
|
||||
gpui = { path = "crates/gpui", default-features = false, features = [
|
||||
"http_client",
|
||||
] }
|
||||
gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]}
|
||||
gpui_macros = { path = "crates/gpui_macros" }
|
||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
http_client = { path = "crates/http_client" }
|
||||
image_viewer = { path = "crates/image_viewer" }
|
||||
indexed_docs = { path = "crates/indexed_docs" }
|
||||
inline_completion = { path = "crates/inline_completion" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_extension = { path = "crates/language_extension" }
|
||||
language_model = { path = "crates/language_model" }
|
||||
language_model_selector = { path = "crates/language_model_selector" }
|
||||
language_models = { path = "crates/language_models" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
language_tools = { path = "crates/language_tools" }
|
||||
languages = { path = "crates/languages" }
|
||||
@@ -271,6 +253,7 @@ project = { path = "crates/project" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
proto = { path = "crates/proto" }
|
||||
quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
refineable = { path = "crates/refineable" }
|
||||
release_channel = { path = "crates/release_channel" }
|
||||
@@ -305,7 +288,6 @@ terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_extension = { path = "crates/theme_extension" }
|
||||
theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
@@ -317,7 +299,6 @@ ui_macros = { path = "crates/ui_macros" }
|
||||
util = { path = "crates/util" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
workspace = { path = "crates/workspace" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
@@ -333,7 +314,7 @@ alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
ashpd = { version = "0.10", default-features = false, features = ["async-std"]}
|
||||
ashpd = "0.9.1"
|
||||
async-compat = "0.2.1"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = "0.1"
|
||||
@@ -342,7 +323,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = "0.5.0"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.28"
|
||||
async-tungstenite = "0.24"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
@@ -352,7 +333,7 @@ blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blake3 = "1.5.3"
|
||||
bytes = "1.0"
|
||||
cargo_metadata = "0.19"
|
||||
cargo_metadata = "0.18"
|
||||
cargo_toml = "0.20"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
@@ -382,22 +363,19 @@ heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
html5ever = "0.27.0"
|
||||
hyper = "0.14"
|
||||
http = "1.1"
|
||||
ignore = "0.4.22"
|
||||
image = "0.25.1"
|
||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
indoc = "2"
|
||||
itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { version = "0.3.0" }
|
||||
jupyter-websocket-client = { version = "0.5.0" }
|
||||
libc = "0.2"
|
||||
linkify = "0.10.0"
|
||||
livekit = { git = "https://github.com/zed-industries/rust-sdks", rev="1f6af333fc8872683c30b39354cec312a1026a0a", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
|
||||
linkme = "0.3"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nbformat = { version = "0.7.0" }
|
||||
nbformat = "0.3.2"
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
@@ -407,12 +385,12 @@ parking_lot = "0.12.1"
|
||||
pathdiff = "0.2"
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
profiling = "1"
|
||||
prost = "0.9"
|
||||
prost-build = "0.9"
|
||||
@@ -431,12 +409,12 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.22.0", default-features = false, features = [
|
||||
runtimelib = { version = "0.16.1", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustls = "0.21.12"
|
||||
rustls = "0.20.3"
|
||||
rustls-native-certs = "0.8.0"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
semver = "1.0"
|
||||
@@ -572,10 +550,6 @@ features = [
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
||||
[patch.crates-io]
|
||||
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
debug = "limited"
|
||||
@@ -589,46 +563,6 @@ rustybuzz = { opt-level = 3 }
|
||||
ttf-parser = { opt-level = 3 }
|
||||
wasmtime-cranelift = { opt-level = 3 }
|
||||
wasmtime = { opt-level = 3 }
|
||||
# Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster
|
||||
activity_indicator = { codegen-units = 1 }
|
||||
assets = { codegen-units = 1 }
|
||||
breadcrumbs = { codegen-units = 1 }
|
||||
collections = { codegen-units = 1 }
|
||||
command_palette = { codegen-units = 1 }
|
||||
command_palette_hooks = { codegen-units = 1 }
|
||||
evals = { codegen-units = 1 }
|
||||
extension_cli = { codegen-units = 1 }
|
||||
feature_flags = { codegen-units = 1 }
|
||||
file_icons = { codegen-units = 1 }
|
||||
fsevent = { codegen-units = 1 }
|
||||
image_viewer = { codegen-units = 1 }
|
||||
inline_completion_button = { codegen-units = 1 }
|
||||
install_cli = { codegen-units = 1 }
|
||||
journal = { codegen-units = 1 }
|
||||
menu = { codegen-units = 1 }
|
||||
notifications = { codegen-units = 1 }
|
||||
ollama = { codegen-units = 1 }
|
||||
outline = { codegen-units = 1 }
|
||||
paths = { codegen-units = 1 }
|
||||
prettier = { codegen-units = 1 }
|
||||
project_symbols = { codegen-units = 1 }
|
||||
refineable = { codegen-units = 1 }
|
||||
release_channel = { codegen-units = 1 }
|
||||
reqwest_client = { codegen-units = 1 }
|
||||
rich_text = { codegen-units = 1 }
|
||||
semantic_version = { codegen-units = 1 }
|
||||
session = { codegen-units = 1 }
|
||||
snippet = { codegen-units = 1 }
|
||||
snippets_ui = { codegen-units = 1 }
|
||||
sqlez_macros = { codegen-units = 1 }
|
||||
story = { codegen-units = 1 }
|
||||
supermaven_api = { codegen-units = 1 }
|
||||
telemetry_events = { codegen-units = 1 }
|
||||
theme_selector = { codegen-units = 1 }
|
||||
time_format = { codegen-units = 1 }
|
||||
ui_input = { codegen-units = 1 }
|
||||
vcs_menu = { codegen-units = 1 }
|
||||
zed_actions = { codegen-units = 1 }
|
||||
|
||||
[profile.release]
|
||||
debug = "limited"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>
|
||||
|
Before Width: | Height: | Size: 368 B |
@@ -1,5 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 20H16C14.9391 20 13.9217 19.6629 13.1716 19.0627C12.4214 18.4626 12 17.6487 12 16.8V7.2C12 6.35131 12.4214 5.53737 13.1716 4.93726C13.9217 4.33714 14.9391 4 16 4H17" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 20H8C9.06087 20 10.0783 19.5786 10.8284 18.8284C11.5786 18.0783 12 17.0609 12 16V15" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 4H8C9.06087 4 10.0783 4.42143 10.8284 5.17157C11.5786 5.92172 12 6.93913 12 8V9" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 345 B |
@@ -127,7 +127,6 @@
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"otf": "font",
|
||||
"pcss": "css",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "php",
|
||||
@@ -174,9 +173,6 @@
|
||||
"tsx": "react",
|
||||
"ttf": "font",
|
||||
"txt": "document",
|
||||
"v": "v",
|
||||
"vsh": "v",
|
||||
"vv": "v",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
@@ -383,9 +379,6 @@
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"v": {
|
||||
"icon": "icons/file_icons/v.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.5" d="M10.0469 12.8661L13.3884 3.31889C13.4386 3.1754 13.3167 3.03055 13.1667 3.05554L10.7292 3.46179C10.5875 3.48542 10.4693 3.58324 10.4197 3.71807L7.24789 12.3271C7.12763 12.6536 7.36919 13 7.71706 13H9.8581C9.94309 13 10.0188 12.9463 10.0469 12.8661Z" fill="black"/>
|
||||
<path d="M6.90625 12.7321L3.61161 3.31889C3.56139 3.1754 3.6833 3.03055 3.83326 3.05554L6.27076 3.46179C6.4125 3.48542 6.53067 3.58324 6.58034 3.71807L9.90084 12.7309C9.94895 12.8614 9.85232 13 9.71317 13H7.28379C7.11381 13 6.9624 12.8926 6.90625 12.7321Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 663 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-globe"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
|
||||
|
Before Width: | Height: | Size: 327 B |
3
assets/icons/indicator_x.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 fill-rule="evenodd" clip-rule="evenodd" d="M13.4662 14.9152C13.5801 15.0291 13.7648 15.0291 13.8787 14.9152L14.9145 13.8793C15.0284 13.7654 15.0284 13.5807 14.9145 13.4667L12.9483 11.5004L14.9145 9.53392C15.0285 9.42004 15.0285 9.23533 14.9145 9.12137L13.8787 8.08547C13.7648 7.97154 13.5801 7.97154 13.4662 8.08547L11.5 10.0519L9.53376 8.08545C9.41988 7.97152 9.23517 7.97152 9.12124 8.08545L8.08543 9.12136C7.97152 9.23533 7.97152 9.42004 8.08543 9.53392L10.0517 11.5004L8.08545 13.4667C7.97155 13.5807 7.97155 13.7654 8.08545 13.8793L9.12126 14.9152C9.23517 15.0292 9.41988 15.0292 9.53376 14.9152L11.5 12.9489L13.4662 14.9152Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 756 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-keyboard"><path d="M10 8h.01"/><path d="M12 12h.01"/><path d="M14 8h.01"/><path d="M16 12h.01"/><path d="M18 8h.01"/><path d="M6 8h.01"/><path d="M7 16h10"/><path d="M8 12h.01"/><rect width="20" height="16" x="2" y="4" rx="2"/></svg>
|
||||
|
Before Width: | Height: | Size: 436 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.1" y="2.1" width="6.8" height="6.8" rx="3.4" stroke="black" stroke-width="1.8"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 195 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="3" width="5" height="5" rx="2.5" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 166 B |
@@ -1,10 +0,0 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2080_948)">
|
||||
<path d="M1.52492 8.72287L1.04243 9.55H2H9H9.95757L9.47508 8.72287L5.97508 2.72287L5.5 1.90845L5.02492 2.72287L1.52492 8.72287Z" stroke="black" stroke-width="1.1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2080_948">
|
||||
<rect width="11" height="11" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 412 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 9H9L5.5 3L2 9Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 146 B |
@@ -1,10 +0,0 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2050_903)">
|
||||
<path d="M1.83327 2.89393L1.19687 3.53033L1.83327 4.16672L3.16654 5.5L1.83327 6.83327L1.19687 7.46967L1.83327 8.10606L2.89393 9.16672L3.53033 9.80312L4.16672 9.16672L5.5 7.83345L6.83327 9.16672L7.46967 9.80312L8.10606 9.16672L9.16672 8.10606L9.80312 7.46967L9.16672 6.83327L7.83345 5.5L9.16672 4.16672L9.80312 3.53033L9.16672 2.89393L8.10606 1.83327L7.46967 1.19687L6.83327 1.83327L5.5 3.16654L4.16672 1.83327L3.53033 1.19687L2.89393 1.83327L1.83327 2.89393Z" stroke="black" stroke-width="1.8"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2050_903">
|
||||
<rect width="11" height="11" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 743 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 3L5.5 5.5M8 8L5.5 5.5M5.5 5.5L3 8M5.5 5.5L8 3" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 198 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-phone-incoming"><polyline points="16 2 16 8 22 8"/><line x1="22" x2="16" y1="2" y2="8"/><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
||||
|
Before Width: | Height: | Size: 594 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-swatch-book"><path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"/><path d="M 7 17h.01"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"/></svg>
|
||||
|
Before Width: | Height: | Size: 456 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4L4 12H12L8 4Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 155 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 177 B |
@@ -251,8 +251,6 @@
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"back": "pane::GoBack",
|
||||
"forward": "pane::GoForward",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
@@ -405,7 +403,7 @@
|
||||
"ctrl-shift-p": "command_palette::Toggle",
|
||||
"f1": "command_palette::Toggle",
|
||||
"ctrl-shift-m": "diagnostics::Deploy",
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
||||
"ctrl-?": "assistant::ToggleFocus",
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
@@ -594,7 +592,6 @@
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
@@ -652,28 +649,7 @@
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"bindings": {
|
||||
"ctrl": "file_finder::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && !menu_open",
|
||||
"bindings": {
|
||||
"ctrl-shift-p": "file_finder::SelectPrev",
|
||||
"ctrl-j": "pane::SplitDown",
|
||||
"ctrl-k": "pane::SplitUp",
|
||||
"ctrl-h": "pane::SplitLeft",
|
||||
"ctrl-l": "pane::SplitRight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && menu_open",
|
||||
"bindings": {
|
||||
"j": "pane::SplitDown",
|
||||
"k": "pane::SplitUp",
|
||||
"h": "pane::SplitLeft",
|
||||
"l": "pane::SplitRight"
|
||||
}
|
||||
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
|
||||
},
|
||||
{
|
||||
"context": "TabSwitcher",
|
||||
|
||||
@@ -49,9 +49,8 @@
|
||||
"ctrl-d": "editor::Delete",
|
||||
"tab": "editor::Tab",
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-t": "editor::Transpose",
|
||||
"ctrl-k": "editor::KillRingCut",
|
||||
"ctrl-y": "editor::KillRingYank",
|
||||
"cmd-k q": "editor::Rewrap",
|
||||
"cmd-k cmd-q": "editor::Rewrap",
|
||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||
@@ -93,8 +92,6 @@
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"cmd-up": "editor::MoveToBeginning",
|
||||
"cmd-down": "editor::MoveToEnd",
|
||||
"ctrl-home": "editor::MoveToBeginning",
|
||||
"ctrl-end": "editor::MoveToEnd",
|
||||
"shift-up": "editor::SelectUp",
|
||||
"ctrl-shift-p": "editor::SelectUp",
|
||||
"shift-down": "editor::SelectDown",
|
||||
@@ -209,18 +206,6 @@
|
||||
"alt-enter": "editor::Newline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel2",
|
||||
"bindings": {
|
||||
"cmd-n": "assistant2::NewThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor",
|
||||
"bindings": {
|
||||
"cmd-enter": "assistant2::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"bindings": {
|
||||
@@ -355,7 +340,7 @@
|
||||
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"cmd-|": "editor::MoveToEnclosingBracket",
|
||||
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
"alt-cmd-]": "editor::UnfoldLines",
|
||||
"cmd-k cmd-l": "editor::ToggleFold",
|
||||
@@ -446,7 +431,7 @@
|
||||
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
||||
"cmd-shift-p": "command_palette::Toggle",
|
||||
"cmd-shift-m": "diagnostics::Deploy",
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-shift-b": "outline_panel::ToggleFocus",
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
@@ -616,7 +601,6 @@
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
@@ -665,28 +649,7 @@
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"bindings": {
|
||||
"cmd": "file_finder::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && !menu_open",
|
||||
"bindings": {
|
||||
"cmd-shift-p": "file_finder::SelectPrev",
|
||||
"cmd-j": "pane::SplitDown",
|
||||
"cmd-k": "pane::SplitUp",
|
||||
"cmd-h": "pane::SplitLeft",
|
||||
"cmd-l": "pane::SplitRight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && menu_open",
|
||||
"bindings": {
|
||||
"j": "pane::SplitDown",
|
||||
"k": "pane::SplitUp",
|
||||
"h": "pane::SplitLeft",
|
||||
"l": "pane::SplitRight"
|
||||
}
|
||||
"bindings": { "cmd-shift-p": "file_finder::SelectPrev" }
|
||||
},
|
||||
{
|
||||
"context": "TabSwitcher",
|
||||
@@ -732,11 +695,7 @@
|
||||
"cmd-end": "terminal::ScrollToBottom",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom",
|
||||
"ctrl-shift-space": "terminal::ToggleViMode",
|
||||
"ctrl-k up": "pane::SplitUp",
|
||||
"ctrl-k down": "pane::SplitDown",
|
||||
"ctrl-k left": "pane::SplitLeft",
|
||||
"ctrl-k right": "pane::SplitRight"
|
||||
"ctrl-shift-space": "terminal::ToggleViMode"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"ctrl-shift-[": "pane::ActivatePrevItem",
|
||||
"ctrl-shift-]": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem"
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-tab": "pane::ActivateNextItem",
|
||||
"ctrl-shift-tab": "pane::ActivatePrevItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -16,7 +18,6 @@
|
||||
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
|
||||
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"ctrl-shift-d": "editor::DuplicateLineDown",
|
||||
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
||||
"f12": "editor::GoToDefinition",
|
||||
"ctrl-f12": "editor::GoToDefinitionSplit",
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"cmd-shift-[": "pane::ActivatePrevItem",
|
||||
"cmd-shift-]": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem"
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-tab": "pane::ActivateNextItem",
|
||||
"ctrl-shift-tab": "pane::ActivatePrevItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -19,7 +21,6 @@
|
||||
"cmd-shift-l": "editor::SplitSelectionIntoLines",
|
||||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
"alt-cmd-down": "editor::GoToDefinition",
|
||||
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
|
||||
|
||||
@@ -55,10 +55,6 @@
|
||||
"n": "vim::MoveToNextMatch",
|
||||
"shift-n": "vim::MoveToPrevMatch",
|
||||
"%": "vim::Matching",
|
||||
"] }": ["vim::UnmatchedForward", { "char": "}" } ],
|
||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" } ],
|
||||
"] )": ["vim::UnmatchedForward", { "char": ")" } ],
|
||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" } ],
|
||||
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
|
||||
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
|
||||
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
|
||||
@@ -308,8 +304,7 @@
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-r": ["vim::PushOperator", "Register"],
|
||||
"insert": "vim::ToggleReplace",
|
||||
"ctrl-o": "vim::TemporaryNormal"
|
||||
"insert": "vim::ToggleReplace"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -385,9 +380,8 @@
|
||||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument",
|
||||
"i": "vim::IndentObj",
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }]
|
||||
"a": "vim::AngleBrackets",
|
||||
"g": "vim::Argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -557,12 +551,6 @@
|
||||
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"ctrl-w >": ["vim::ResizePane", "Widen"],
|
||||
"ctrl-w <": ["vim::ResizePane", "Narrow"],
|
||||
"ctrl-w -": ["vim::ResizePane", "Shorten"],
|
||||
"ctrl-w +": ["vim::ResizePane", "Lengthen"],
|
||||
"ctrl-w _": "vim::MaximizePane",
|
||||
"ctrl-w =": "vim::ResetPaneSizes",
|
||||
"ctrl-w g t": "pane::ActivateNextItem",
|
||||
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
|
||||
"ctrl-w g shift-t": "pane::ActivatePrevItem",
|
||||
@@ -589,7 +577,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
"auto_signature_help": false,
|
||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
"show_signature_help_after_edits": false,
|
||||
"show_signature_help_after_edits": true,
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||
@@ -193,9 +193,6 @@
|
||||
// Controls whether inline completions are shown immediately (true)
|
||||
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||
"show_inline_completions": true,
|
||||
// Controls whether inline completions are shown in a given language scope.
|
||||
// Example: ["string", "comment"]
|
||||
"inline_completions_disabled_in": [],
|
||||
// Whether to show tabs and spaces in the editor.
|
||||
// This setting can take three values:
|
||||
//
|
||||
@@ -300,8 +297,6 @@
|
||||
"scroll_beyond_last_line": "one_page",
|
||||
// The number of lines to keep above/below the cursor when scrolling.
|
||||
"vertical_scroll_margin": 3,
|
||||
// Whether to scroll when clicking near the edge of the visible text area.
|
||||
"autoscroll_on_clicks": false,
|
||||
// Scroll sensitivity multiplier. This multiplier is applied
|
||||
// to both the horizontal and vertical delta values while scrolling.
|
||||
"scroll_sensitivity": 1.0,
|
||||
@@ -385,16 +380,6 @@
|
||||
/// "never"
|
||||
"show": null
|
||||
},
|
||||
/// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||
/// This setting can take the following three values:
|
||||
///
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
/// 2. Only mark files with errors:
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
"show_diagnostics": "all",
|
||||
// Settings related to indent guides in the project panel.
|
||||
"indent_guides": {
|
||||
// When to show indent guides in the project panel.
|
||||
@@ -559,8 +544,6 @@
|
||||
"close_position": "right",
|
||||
// Whether to show the file icon for a tab.
|
||||
"file_icons": false,
|
||||
// Whether to always show the close button on tabs.
|
||||
"always_show_close_button": false,
|
||||
// What to do after closing the current tab.
|
||||
//
|
||||
// 1. Activate the tab that was open previously (default)
|
||||
@@ -584,23 +567,7 @@
|
||||
// Settings related to the file finder.
|
||||
"file_finder": {
|
||||
// Whether to show file icons in the file finder.
|
||||
"file_icons": true,
|
||||
// Determines how much space the file finder can take up in relation to the available window width.
|
||||
// There are 5 possible width values:
|
||||
//
|
||||
// 1. Small: This value is essentially a fixed width.
|
||||
// "modal_width": "small"
|
||||
// 2. Medium:
|
||||
// "modal_width": "medium"
|
||||
// 3. Large:
|
||||
// "modal_width": "large"
|
||||
// 4. Extra Large:
|
||||
// "modal_width": "xlarge"
|
||||
// 5. Fullscreen: This value removes any horizontal padding, as it consumes the whole viewport width.
|
||||
// "modal_width": "full"
|
||||
//
|
||||
// Default: small
|
||||
"modal_max_width": "small"
|
||||
"file_icons": true
|
||||
},
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
@@ -669,7 +636,7 @@
|
||||
},
|
||||
// Add files or globs of files that will be excluded by Zed entirely:
|
||||
// they will be skipped during FS scan(s), file tree and file search
|
||||
// will lack the corresponding file entries. Overrides `file_scan_inclusions`.
|
||||
// will lack the corresponding file entries.
|
||||
"file_scan_exclusions": [
|
||||
"**/.git",
|
||||
"**/.svn",
|
||||
@@ -680,11 +647,6 @@
|
||||
"**/.classpath",
|
||||
"**/.settings"
|
||||
],
|
||||
// Add files or globs of files that will be included by Zed, even when
|
||||
// ignored by git. This is useful for files that are not tracked by git,
|
||||
// but are still important to your project. Note that globs that are
|
||||
// overly broad can slow down Zed's file scanning. Overridden by `file_scan_exclusions`.
|
||||
"file_scan_inclusions": [".env*"],
|
||||
// Git gutter behavior configuration.
|
||||
"git": {
|
||||
// Control whether the git gutter is shown. May take 2 values:
|
||||
@@ -845,12 +807,8 @@
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
// Whether to display the terminal title in its toolbar's breadcrumbs.
|
||||
// Only shown if the terminal title is not empty.
|
||||
//
|
||||
// The shell running in the terminal needs to be configured to emit the title.
|
||||
// Example: `echo -e "\e]2;New Title\007";`
|
||||
"breadcrumbs": true
|
||||
// Whether to display the terminal title in its toolbar.
|
||||
"title": true
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
@@ -886,8 +844,15 @@
|
||||
//
|
||||
"file_types": {
|
||||
"Plain Text": ["txt"],
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
|
||||
"Shell Script": [".env.*"]
|
||||
"JSON": ["flake.lock"],
|
||||
"JSONC": [
|
||||
"**/.zed/**/*.json",
|
||||
"**/zed/**/*.json",
|
||||
"**/Zed/**/*.json",
|
||||
"tsconfig.json",
|
||||
"pyrightconfig.json"
|
||||
],
|
||||
"TOML": ["uv.lock"]
|
||||
},
|
||||
/// By default use a recent system version of node, or install our own.
|
||||
/// You can override this to use a version of node that is not in $PATH with:
|
||||
@@ -1075,11 +1040,13 @@
|
||||
"api_url": "https://generativelanguage.googleapis.com"
|
||||
},
|
||||
"ollama": {
|
||||
"api_url": "http://localhost:11434"
|
||||
"api_url": "http://localhost:11434",
|
||||
"low_speed_timeout_in_seconds": 60
|
||||
},
|
||||
"openai": {
|
||||
"version": "1",
|
||||
"api_url": "https://api.openai.com/v1"
|
||||
"api_url": "https://api.openai.com/v1",
|
||||
"low_speed_timeout_in_seconds": 600
|
||||
}
|
||||
},
|
||||
// Zed's Prettier integration settings.
|
||||
@@ -1202,6 +1169,15 @@
|
||||
// }
|
||||
// ]
|
||||
"ssh_connections": [],
|
||||
// Configures context servers for use in the Assistant.
|
||||
"context_servers": {}
|
||||
// Configures the Context Server Protocol binaries
|
||||
//
|
||||
// Examples:
|
||||
// {
|
||||
// "id": "server-1",
|
||||
// "executable": "/path",
|
||||
// "args": ['arg1", "args2"]
|
||||
// }
|
||||
"experimental.context_servers": {
|
||||
"servers": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ extension_host.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
lsp.workspace = true
|
||||
project.workspace = true
|
||||
smallvec.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
@@ -7,8 +7,9 @@ use gpui::{
|
||||
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
|
||||
use lsp::LanguageServerName;
|
||||
use language::{
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
|
||||
};
|
||||
use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
mod supported_countries;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::{pin::Pin, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
||||
use http_client::http::{HeaderMap, HeaderValue};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString};
|
||||
use thiserror::Error;
|
||||
@@ -160,7 +161,10 @@ pub async fn complete(
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Anthropic-Version", "2023-06-01")
|
||||
.header("Anthropic-Beta", "prompt-caching-2024-07-31")
|
||||
.header(
|
||||
"Anthropic-Beta",
|
||||
"tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
|
||||
)
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
@@ -206,8 +210,9 @@ pub async fn stream_completion(
|
||||
api_url: &str,
|
||||
api_key: &str,
|
||||
request: Request,
|
||||
low_speed_timeout: Option<Duration>,
|
||||
) -> Result<BoxStream<'static, Result<Event, AnthropicError>>, AnthropicError> {
|
||||
stream_completion_with_rate_limit_info(client, api_url, api_key, request)
|
||||
stream_completion_with_rate_limit_info(client, api_url, api_key, request, low_speed_timeout)
|
||||
.await
|
||||
.map(|output| output.0)
|
||||
}
|
||||
@@ -259,6 +264,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
api_url: &str,
|
||||
api_key: &str,
|
||||
request: Request,
|
||||
low_speed_timeout: Option<Duration>,
|
||||
) -> Result<
|
||||
(
|
||||
BoxStream<'static, Result<Event, AnthropicError>>,
|
||||
@@ -271,7 +277,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
stream: true,
|
||||
};
|
||||
let uri = format!("{api_url}/v1/messages");
|
||||
let request_builder = HttpRequest::builder()
|
||||
let mut request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Anthropic-Version", "2023-06-01")
|
||||
@@ -281,6 +287,9 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
)
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("Content-Type", "application/json");
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
let serialized_request =
|
||||
serde_json::to_string(&request).context("failed to serialize request")?;
|
||||
let request = request_builder
|
||||
|
||||
@@ -33,7 +33,7 @@ client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
context_server.workspace = true
|
||||
context_servers.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
@@ -50,8 +50,6 @@ indexed_docs.workspace = true
|
||||
indoc.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod assistant_settings;
|
||||
mod context;
|
||||
pub mod context_store;
|
||||
mod inline_assistant;
|
||||
mod model_selector;
|
||||
mod patch;
|
||||
mod prompt_library;
|
||||
mod prompts;
|
||||
@@ -14,12 +15,15 @@ pub mod slash_command_settings;
|
||||
mod slash_command_working_set;
|
||||
mod streaming_diff;
|
||||
mod terminal_inline_assistant;
|
||||
mod tool_working_set;
|
||||
mod tools;
|
||||
|
||||
use crate::slash_command::project_command::ProjectSlashCommandFeatureFlag;
|
||||
pub use crate::slash_command_working_set::{SlashCommandId, SlashCommandWorkingSet};
|
||||
pub use crate::tool_working_set::{ToolId, ToolWorkingSet};
|
||||
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use assistant_tool::ToolRegistry;
|
||||
use client::{proto, Client};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
pub use context::*;
|
||||
@@ -28,10 +32,12 @@ use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::impl_actions;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
pub(crate) use inline_assistant::*;
|
||||
use language_model::{
|
||||
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
||||
};
|
||||
pub(crate) use model_selector::*;
|
||||
pub use patch::*;
|
||||
pub use prompts::PromptBuilder;
|
||||
use prompts::PromptLoadingParams;
|
||||
@@ -209,32 +215,23 @@ pub fn init(
|
||||
});
|
||||
}
|
||||
|
||||
cx.spawn(|mut cx| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let is_search_slash_command_enabled = cx
|
||||
.update(|cx| cx.wait_for_flag::<SearchSlashCommandFeatureFlag>())?
|
||||
.await;
|
||||
let is_project_slash_command_enabled = cx
|
||||
.update(|cx| cx.wait_for_flag::<ProjectSlashCommandFeatureFlag>())?
|
||||
.await;
|
||||
if cx.has_flag::<SearchSlashCommandFeatureFlag>() {
|
||||
cx.spawn(|mut cx| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticDb::new(
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !is_search_slash_command_enabled && !is_project_slash_command_enabled {
|
||||
return Ok(());
|
||||
cx.update(|cx| cx.set_global(semantic_index))
|
||||
}
|
||||
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticDb::new(
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
cx.update(|cx| cx.set_global(semantic_index))
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
context_store::init(&client.clone().into());
|
||||
prompt_library::init(cx);
|
||||
@@ -242,7 +239,7 @@ pub fn init(
|
||||
assistant_slash_command::init(cx);
|
||||
assistant_tool::init(cx);
|
||||
assistant_panel::init(cx);
|
||||
context_server::init(cx);
|
||||
context_servers::init(cx);
|
||||
|
||||
let prompt_builder = prompts::PromptBuilder::new(Some(PromptLoadingParams {
|
||||
fs: fs.clone(),
|
||||
@@ -255,6 +252,7 @@ pub fn init(
|
||||
.map(Arc::new)
|
||||
.unwrap_or_else(|| Arc::new(prompts::PromptBuilder::new(None).unwrap()));
|
||||
register_slash_commands(Some(prompt_builder.clone()), cx);
|
||||
register_tools(cx);
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
@@ -267,7 +265,7 @@ pub fn init(
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
indexed_docs::init(cx);
|
||||
IndexedDocsRegistry::init_global(cx);
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(Assistant::NAMESPACE);
|
||||
@@ -342,7 +340,8 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
||||
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, true);
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||
|
||||
if let Some(prompt_builder) = prompt_builder {
|
||||
cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
|
||||
@@ -417,6 +416,11 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
fn register_tools(cx: &mut AppContext) {
|
||||
let tool_registry = ToolRegistry::global(cx);
|
||||
tool_registry.register_tool(tools::now_tool::NowTool);
|
||||
}
|
||||
|
||||
pub fn humanize_token_count(count: usize) -> String {
|
||||
match count {
|
||||
0..=999 => count.to_string(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::slash_command::file_command::codeblock_fence_for_path;
|
||||
use crate::slash_command_working_set::SlashCommandWorkingSet;
|
||||
use crate::ToolWorkingSet;
|
||||
use crate::{
|
||||
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
||||
humanize_token_count,
|
||||
@@ -16,13 +17,12 @@ use crate::{
|
||||
ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole,
|
||||
DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles,
|
||||
InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
|
||||
MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus,
|
||||
QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus,
|
||||
ToggleModelSelector,
|
||||
MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext,
|
||||
ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
|
||||
RequestType, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::{proto, zed_urls, Client, Status};
|
||||
use collections::{hash_map, BTreeSet, HashMap, HashSet};
|
||||
use editor::{
|
||||
@@ -50,12 +50,11 @@ use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
language_settings::SoftWrap, BufferSnapshot, LanguageRegistry, LspAdapterDelegate, ToOffset,
|
||||
};
|
||||
use language_model::{LanguageModelImage, LanguageModelToolUse};
|
||||
use language_model::{
|
||||
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role,
|
||||
ZED_CLOUD_PROVIDER_ID,
|
||||
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelRegistry, Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelPickerDelegate, LanguageModelSelector};
|
||||
use language_model::{LanguageModelImage, LanguageModelToolUse};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::lsp_store::LocalLspAdapterDelegate;
|
||||
@@ -143,7 +142,7 @@ pub struct AssistantPanel {
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
subscriptions: Vec<Subscription>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
|
||||
model_summary_editor: View<Editor>,
|
||||
authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
|
||||
configuration_subscription: Option<Subscription>,
|
||||
@@ -416,6 +415,7 @@ impl AssistantPanel {
|
||||
ControlFlow::Break(())
|
||||
});
|
||||
|
||||
pane.set_can_split(false, cx);
|
||||
pane.set_can_navigate(true, cx);
|
||||
pane.display_nav_history_buttons(None);
|
||||
pane.set_should_display_tab_bar(|_| true);
|
||||
@@ -447,7 +447,7 @@ impl AssistantPanel {
|
||||
);
|
||||
let _pane = cx.view().clone();
|
||||
let right_children = h_flex()
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
.gap(Spacing::XSmall.rems(cx))
|
||||
.child(
|
||||
IconButton::new("new-chat", IconName::Plus)
|
||||
.on_click(
|
||||
@@ -664,7 +664,7 @@ impl AssistantPanel {
|
||||
// If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
|
||||
// the provider, we want to show a nudge to sign in.
|
||||
let show_zed_ai_notice = client_status.is_signed_out()
|
||||
&& active_provider.map_or(true, |provider| provider.id().0 == ZED_CLOUD_PROVIDER_ID);
|
||||
&& active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID);
|
||||
|
||||
self.show_zed_ai_notice = show_zed_ai_notice;
|
||||
cx.notify();
|
||||
@@ -1315,7 +1315,7 @@ impl AssistantPanel {
|
||||
|
||||
fn restart_context_servers(
|
||||
workspace: &mut Workspace,
|
||||
_action: &context_server::Restart,
|
||||
_action: &context_servers::Restart,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
@@ -1480,6 +1480,7 @@ struct ScrollPosition {
|
||||
}
|
||||
|
||||
struct PatchViewState {
|
||||
footer_block_id: CustomBlockId,
|
||||
crease_id: CreaseId,
|
||||
editor: Option<PatchEditorState>,
|
||||
update_task: Option<Task<()>>,
|
||||
@@ -1924,7 +1925,7 @@ impl ContextEditor {
|
||||
Content::ToolUse {
|
||||
range: tool_use.source_range.clone(),
|
||||
tool_use: LanguageModelToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
id: tool_use.id.to_string(),
|
||||
name: tool_use.name.clone(),
|
||||
input: tool_use.input.clone(),
|
||||
},
|
||||
@@ -1933,7 +1934,7 @@ impl ContextEditor {
|
||||
);
|
||||
});
|
||||
|
||||
Crease::inline(
|
||||
Crease::new(
|
||||
start..end,
|
||||
placeholder,
|
||||
fold_toggle("tool-use"),
|
||||
@@ -2031,7 +2032,7 @@ impl ContextEditor {
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, command.source_range.end)
|
||||
.unwrap();
|
||||
Crease::inline(start..end, placeholder, render_toggle, render_trailer)
|
||||
Crease::new(start..end, placeholder, render_toggle, render_trailer)
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
@@ -2050,6 +2051,30 @@ impl ContextEditor {
|
||||
ContextEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
self.insert_slash_command_output_sections([section.clone()], false, cx);
|
||||
}
|
||||
ContextEvent::SlashCommandFinished {
|
||||
output_range: _output_range,
|
||||
run_commands_in_ranges,
|
||||
} => {
|
||||
for range in run_commands_in_ranges {
|
||||
let commands = self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context
|
||||
.pending_commands_for_range(range.clone(), cx)
|
||||
.to_vec()
|
||||
});
|
||||
|
||||
for command in commands {
|
||||
self.run_command(
|
||||
command.source_range,
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
false,
|
||||
self.workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextEvent::UsePendingTools => {
|
||||
let pending_tool_uses = self
|
||||
.context
|
||||
@@ -2099,7 +2124,7 @@ impl ContextEditor {
|
||||
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
|
||||
let crease = Crease::inline(
|
||||
let crease = Crease::new(
|
||||
start..end,
|
||||
placeholder,
|
||||
fold_toggle("tool-use"),
|
||||
@@ -2128,37 +2153,6 @@ impl ContextEditor {
|
||||
command_id: InvokedSlashCommandId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
{
|
||||
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
|
||||
let run_commands_in_ranges = invoked_slash_command
|
||||
.run_commands_in_ranges
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for range in run_commands_in_ranges {
|
||||
let commands = self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context
|
||||
.pending_commands_for_range(range.clone(), cx)
|
||||
.to_vec()
|
||||
});
|
||||
|
||||
for command in commands {
|
||||
self.run_command(
|
||||
command.source_range,
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
false,
|
||||
self.workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
@@ -2198,14 +2192,18 @@ impl ContextEditor {
|
||||
let crease_end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
|
||||
.unwrap();
|
||||
let crease = Crease::inline(
|
||||
crease_start..crease_end,
|
||||
invoked_slash_command_fold_placeholder(command_id, context),
|
||||
fold_toggle("invoked-slash-command"),
|
||||
|_row, _folded, _cx| Empty.into_any(),
|
||||
let fold_placeholder =
|
||||
invoked_slash_command_fold_placeholder(command_id, context);
|
||||
let crease_ids = editor.insert_creases(
|
||||
[Crease::new(
|
||||
crease_start..crease_end,
|
||||
fold_placeholder.clone(),
|
||||
fold_toggle("invoked-slash-command"),
|
||||
|_row, _folded, _cx| Empty.into_any(),
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
let crease_ids = editor.insert_creases([crease.clone()], cx);
|
||||
editor.fold_creases(vec![crease], false, cx);
|
||||
editor.fold_ranges([(crease_start..crease_end, fold_placeholder)], false, cx);
|
||||
entry.insert(crease_ids[0]);
|
||||
} else {
|
||||
cx.notify()
|
||||
@@ -2227,32 +2225,23 @@ impl ContextEditor {
|
||||
cx: &mut ViewContext<ContextEditor>,
|
||||
) {
|
||||
let this = cx.view().downgrade();
|
||||
let mut removed_crease_ids = Vec::new();
|
||||
let mut removed_block_ids = HashSet::default();
|
||||
let mut editors_to_close = Vec::new();
|
||||
for range in removed {
|
||||
if let Some(state) = self.patches.remove(range) {
|
||||
editors_to_close.extend(state.editor.and_then(|state| state.editor.upgrade()));
|
||||
removed_block_ids.insert(state.footer_block_id);
|
||||
removed_crease_ids.push(state.crease_id);
|
||||
}
|
||||
}
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let multibuffer = &snapshot.buffer_snapshot;
|
||||
let (&excerpt_id, _, _) = multibuffer.as_singleton().unwrap();
|
||||
|
||||
let mut removed_crease_ids = Vec::new();
|
||||
let mut ranges_to_unfold: Vec<Range<Anchor>> = Vec::new();
|
||||
for range in removed {
|
||||
if let Some(state) = self.patches.remove(range) {
|
||||
let patch_start = multibuffer
|
||||
.anchor_in_excerpt(excerpt_id, range.start)
|
||||
.unwrap();
|
||||
let patch_end = multibuffer
|
||||
.anchor_in_excerpt(excerpt_id, range.end)
|
||||
.unwrap();
|
||||
|
||||
editors_to_close.extend(state.editor.and_then(|state| state.editor.upgrade()));
|
||||
ranges_to_unfold.push(patch_start..patch_end);
|
||||
removed_crease_ids.push(state.crease_id);
|
||||
}
|
||||
}
|
||||
editor.unfold_ranges(&ranges_to_unfold, true, false, cx);
|
||||
editor.remove_creases(removed_crease_ids, cx);
|
||||
|
||||
let mut replaced_blocks = HashMap::default();
|
||||
for range in updated {
|
||||
let Some(patch) = self.context.read(cx).patch_for_range(&range, cx).cloned() else {
|
||||
continue;
|
||||
@@ -2265,21 +2254,19 @@ impl ContextEditor {
|
||||
let patch_end = multibuffer
|
||||
.anchor_in_excerpt(excerpt_id, patch.range.end)
|
||||
.unwrap();
|
||||
let render_block: RenderBlock = Arc::new({
|
||||
let render_block: RenderBlock = Box::new({
|
||||
let this = this.clone();
|
||||
let patch_range = range.clone();
|
||||
move |cx: &mut BlockContext<'_, '_>| {
|
||||
let max_width = cx.max_width;
|
||||
let gutter_width = cx.gutter_dimensions.full_width();
|
||||
let block_id = cx.block_id;
|
||||
let selected = cx.selected;
|
||||
this.update(&mut **cx, |this, cx| {
|
||||
this.render_patch_block(
|
||||
this.render_patch_footer(
|
||||
patch_range.clone(),
|
||||
max_width,
|
||||
gutter_width,
|
||||
block_id,
|
||||
selected,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -2289,16 +2276,25 @@ impl ContextEditor {
|
||||
}
|
||||
});
|
||||
|
||||
let height = path_count as u32 + 1;
|
||||
let crease = Crease::block(
|
||||
patch_start..patch_end,
|
||||
height,
|
||||
BlockStyle::Flex,
|
||||
render_block.clone(),
|
||||
);
|
||||
let header_placeholder = FoldPlaceholder {
|
||||
render: {
|
||||
let this = this.clone();
|
||||
let patch_range = range.clone();
|
||||
Arc::new(move |fold_id, _range, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.render_patch_header(patch_range.clone(), fold_id, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| Empty.into_any())
|
||||
})
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let should_refold;
|
||||
if let Some(state) = self.patches.get_mut(&range) {
|
||||
replaced_blocks.insert(state.footer_block_id, render_block);
|
||||
if let Some(editor_state) = &state.editor {
|
||||
if editor_state.opened_patch != patch {
|
||||
state.update_task = Some({
|
||||
@@ -2315,11 +2311,33 @@ impl ContextEditor {
|
||||
should_refold =
|
||||
snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
|
||||
} else {
|
||||
let crease_id = editor.insert_creases([crease.clone()], cx)[0];
|
||||
let block_ids = editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
height: path_count as u32 + 1,
|
||||
style: BlockStyle::Flex,
|
||||
render: render_block,
|
||||
placement: BlockPlacement::Below(patch_start),
|
||||
priority: 0,
|
||||
}],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
|
||||
let new_crease_ids = editor.insert_creases(
|
||||
[Crease::new(
|
||||
patch_start..patch_end,
|
||||
header_placeholder.clone(),
|
||||
fold_toggle("patch-header"),
|
||||
|_, _, _| Empty.into_any_element(),
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
|
||||
self.patches.insert(
|
||||
range.clone(),
|
||||
PatchViewState {
|
||||
crease_id,
|
||||
footer_block_id: block_ids[0],
|
||||
crease_id: new_crease_ids[0],
|
||||
editor: None,
|
||||
update_task: None,
|
||||
},
|
||||
@@ -2330,9 +2348,13 @@ impl ContextEditor {
|
||||
|
||||
if should_refold {
|
||||
editor.unfold_ranges(&[patch_start..patch_end], true, false, cx);
|
||||
editor.fold_creases(vec![crease], false, cx);
|
||||
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
|
||||
}
|
||||
}
|
||||
|
||||
editor.remove_creases(removed_crease_ids, cx);
|
||||
editor.remove_blocks(removed_block_ids, None, cx);
|
||||
editor.replace_blocks(replaced_blocks, None, cx);
|
||||
});
|
||||
|
||||
for editor in editors_to_close {
|
||||
@@ -2363,7 +2385,7 @@ impl ContextEditor {
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
creases.push(
|
||||
Crease::inline(
|
||||
Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
@@ -2652,7 +2674,7 @@ impl ContextEditor {
|
||||
let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
|
||||
|
||||
let render_block = |message: MessageMetadata| -> RenderBlock {
|
||||
Arc::new({
|
||||
Box::new({
|
||||
let context = self.context.clone();
|
||||
|
||||
move |cx| {
|
||||
@@ -3105,7 +3127,7 @@ impl ContextEditor {
|
||||
crease_title,
|
||||
cx.view().downgrade(),
|
||||
);
|
||||
let crease = Crease::inline(
|
||||
let crease = Crease::new(
|
||||
anchor_before..anchor_after,
|
||||
fold_placeholder,
|
||||
render_quote_selection_output_toggle,
|
||||
@@ -3195,29 +3217,31 @@ impl ContextEditor {
|
||||
&snapshot,
|
||||
)
|
||||
.filter_map(|crease| {
|
||||
if let Crease::Inline {
|
||||
range, metadata, ..
|
||||
} = &crease
|
||||
{
|
||||
let metadata = metadata.as_ref()?;
|
||||
let start = range
|
||||
if let Some(metadata) = &crease.metadata {
|
||||
let start = crease
|
||||
.range
|
||||
.start
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
let end = range
|
||||
let end = crease
|
||||
.range
|
||||
.end
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
|
||||
let range_relative_to_selection = start..end;
|
||||
if !range_relative_to_selection.is_empty() {
|
||||
return Some(SelectedCreaseMetadata {
|
||||
|
||||
if range_relative_to_selection.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SelectedCreaseMetadata {
|
||||
range_relative_to_selection,
|
||||
crease: metadata.clone(),
|
||||
});
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
@@ -3298,7 +3322,7 @@ impl ContextEditor {
|
||||
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
Crease::inline(
|
||||
Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
@@ -3340,8 +3364,7 @@ impl ContextEditor {
|
||||
|
||||
self.context.update(cx, |context, cx| {
|
||||
for image in images {
|
||||
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
|
||||
else {
|
||||
let Some(render_image) = image.to_image_data(cx).log_err() else {
|
||||
continue;
|
||||
};
|
||||
let image_id = image.id();
|
||||
@@ -3392,7 +3415,7 @@ impl ContextEditor {
|
||||
placement: BlockPlacement::Above(anchor),
|
||||
height: MAX_HEIGHT_IN_LINES,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Arc::new(move |cx| {
|
||||
render: Box::new(move |cx| {
|
||||
let image_size = size_for_image(
|
||||
&image,
|
||||
size(
|
||||
@@ -3449,13 +3472,33 @@ impl ContextEditor {
|
||||
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
|
||||
}
|
||||
|
||||
fn render_patch_block(
|
||||
fn render_patch_header(
|
||||
&self,
|
||||
range: Range<text::Anchor>,
|
||||
_id: FoldId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
let patch = self.context.read(cx).patch_for_range(&range, cx)?;
|
||||
let theme = cx.theme().clone();
|
||||
Some(
|
||||
h_flex()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.border_b_1()
|
||||
.border_color(theme.status().info_border)
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Diff).size(IconSize::Small))
|
||||
.child(Label::new(patch.title.clone()).size(LabelSize::Small))
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_patch_footer(
|
||||
&mut self,
|
||||
range: Range<text::Anchor>,
|
||||
max_width: Pixels,
|
||||
gutter_width: Pixels,
|
||||
id: BlockId,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
@@ -3466,7 +3509,10 @@ impl ContextEditor {
|
||||
.anchor_in_excerpt(excerpt_id, range.start)
|
||||
.unwrap();
|
||||
|
||||
let theme = cx.theme().clone();
|
||||
if !snapshot.intersects_fold(anchor) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let patch = self.context.read(cx).patch_for_range(&range, cx)?;
|
||||
let paths = patch
|
||||
.paths()
|
||||
@@ -3476,18 +3522,9 @@ impl ContextEditor {
|
||||
Some(
|
||||
v_flex()
|
||||
.id(id)
|
||||
.bg(theme.colors().editor_background)
|
||||
.ml(gutter_width)
|
||||
.pb_1()
|
||||
.w(max_width - gutter_width)
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(theme.colors().border_variant)
|
||||
.overflow_hidden()
|
||||
.hover(|style| style.border_color(theme.colors().text_accent))
|
||||
.when(selected, |this| {
|
||||
this.border_color(theme.colors().text_accent)
|
||||
})
|
||||
.pl(gutter_width)
|
||||
.w(max_width)
|
||||
.py_2()
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
@@ -3497,60 +3534,24 @@ impl ContextEditor {
|
||||
});
|
||||
this.focus_active_patch(cx);
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.overflow_hidden()
|
||||
.text_ellipsis()
|
||||
.border_b_1()
|
||||
.border_color(theme.colors().border_variant)
|
||||
.bg(theme.colors().element_background)
|
||||
.child(
|
||||
Label::new(patch.title.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.children(paths.into_iter().map(|path| {
|
||||
h_flex()
|
||||
.px_2()
|
||||
.pt_1()
|
||||
.gap_1p5()
|
||||
.pl_1()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::File).size(IconSize::Small))
|
||||
.child(Label::new(path).size(LabelSize::Small))
|
||||
}))
|
||||
.when(patch.status == AssistantPatchStatus::Pending, |div| {
|
||||
div.child(
|
||||
h_flex()
|
||||
.pt_1()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(
|
||||
delta,
|
||||
)))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Generating…")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 0.8)),
|
||||
|label, delta| label.alpha(delta),
|
||||
),
|
||||
Label::new("Generating")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 1.)),
|
||||
|label, delta| label.alpha(delta),
|
||||
),
|
||||
)
|
||||
})
|
||||
@@ -3928,7 +3929,7 @@ impl ContextEditor {
|
||||
.child(
|
||||
div()
|
||||
.id("error-message")
|
||||
.max_h_32()
|
||||
.max_h_24()
|
||||
.overflow_y_scroll()
|
||||
.child(Label::new(error_message.clone())),
|
||||
)
|
||||
@@ -4457,13 +4458,13 @@ pub struct ContextEditorToolbarItem {
|
||||
fs: Arc<dyn Fs>,
|
||||
active_context_editor: Option<WeakView<ContextEditor>>,
|
||||
model_summary_editor: View<Editor>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl ContextEditorToolbarItem {
|
||||
pub fn new(
|
||||
workspace: &Workspace,
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
|
||||
model_summary_editor: View<Editor>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -4559,17 +4560,8 @@ impl Render for ContextEditorToolbarItem {
|
||||
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
|
||||
// })
|
||||
.child(
|
||||
LanguageModelSelector::new(
|
||||
{
|
||||
let fs = self.fs.clone();
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
);
|
||||
}
|
||||
},
|
||||
ModelSelector::new(
|
||||
self.fs.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
@@ -4846,7 +4838,7 @@ impl ConfigurationView {
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.p(DynamicSpacing::Base08.rems(cx))
|
||||
.p(Spacing::Large.rems(cx))
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
@@ -4880,7 +4872,7 @@ impl Render for ConfigurationView {
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.p(Spacing::XXLarge.rems(cx))
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap_1()
|
||||
@@ -4894,7 +4886,7 @@ impl Render for ConfigurationView {
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.p(Spacing::XXLarge.rems(cx))
|
||||
.mt_1()
|
||||
.gap_6()
|
||||
.flex_1()
|
||||
|
||||
@@ -5,12 +5,13 @@ use anthropic::Model as AnthropicModel;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, Pixels};
|
||||
use language_model::{CloudModel, LanguageModel};
|
||||
use language_models::{
|
||||
provider::open_ai, AllLanguageModelSettings, AnthropicSettingsContent,
|
||||
AnthropicSettingsContentV1, OllamaSettingsContent, OpenAiSettingsContent,
|
||||
OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, VersionedOpenAiSettingsContent,
|
||||
use language_model::provider::open_ai;
|
||||
use language_model::settings::{
|
||||
AnthropicSettingsContent, AnthropicSettingsContentV1, OllamaSettingsContent,
|
||||
OpenAiSettingsContent, OpenAiSettingsContentV1, VersionedAnthropicSettingsContent,
|
||||
VersionedOpenAiSettingsContent,
|
||||
};
|
||||
use language_model::{settings::AllLanguageModelSettings, CloudModel, LanguageModel};
|
||||
use ollama::Model as OllamaModel;
|
||||
use schemars::{schema::Schema, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -34,17 +35,20 @@ pub enum AssistantProviderContentV1 {
|
||||
OpenAi {
|
||||
default_model: Option<OpenAiModel>,
|
||||
api_url: Option<String>,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
available_models: Option<Vec<OpenAiModel>>,
|
||||
},
|
||||
#[serde(rename = "anthropic")]
|
||||
Anthropic {
|
||||
default_model: Option<AnthropicModel>,
|
||||
api_url: Option<String>,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
},
|
||||
#[serde(rename = "ollama")]
|
||||
Ollama {
|
||||
default_model: Option<OllamaModel>,
|
||||
api_url: Option<String>,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -111,41 +115,47 @@ impl AssistantSettingsContent {
|
||||
if let VersionedAssistantSettingsContent::V1(settings) = settings {
|
||||
if let Some(provider) = settings.provider.clone() {
|
||||
match provider {
|
||||
AssistantProviderContentV1::Anthropic { api_url, .. } => {
|
||||
update_settings_file::<AllLanguageModelSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |content, _| {
|
||||
if content.anthropic.is_none() {
|
||||
content.anthropic =
|
||||
Some(AnthropicSettingsContent::Versioned(
|
||||
VersionedAnthropicSettingsContent::V1(
|
||||
AnthropicSettingsContentV1 {
|
||||
api_url,
|
||||
available_models: None,
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
AssistantProviderContentV1::Ollama { api_url, .. } => {
|
||||
update_settings_file::<AllLanguageModelSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |content, _| {
|
||||
if content.ollama.is_none() {
|
||||
content.ollama = Some(OllamaSettingsContent {
|
||||
api_url,
|
||||
available_models: None,
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
AssistantProviderContentV1::Anthropic {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
..
|
||||
} => update_settings_file::<AllLanguageModelSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |content, _| {
|
||||
if content.anthropic.is_none() {
|
||||
content.anthropic = Some(AnthropicSettingsContent::Versioned(
|
||||
VersionedAnthropicSettingsContent::V1(
|
||||
AnthropicSettingsContentV1 {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models: None,
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
AssistantProviderContentV1::Ollama {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
..
|
||||
} => update_settings_file::<AllLanguageModelSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |content, _| {
|
||||
if content.ollama.is_none() {
|
||||
content.ollama = Some(OllamaSettingsContent {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models: None,
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
AssistantProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
..
|
||||
} => update_settings_file::<AllLanguageModelSettings>(
|
||||
@@ -178,6 +188,7 @@ impl AssistantSettingsContent {
|
||||
VersionedOpenAiSettingsContent::V1(
|
||||
OpenAiSettingsContentV1 {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
},
|
||||
),
|
||||
@@ -287,41 +298,54 @@ impl AssistantSettingsContent {
|
||||
log::warn!("attempted to set zed.dev model on outdated settings");
|
||||
}
|
||||
"anthropic" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
let (api_url, low_speed_timeout_in_seconds) = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Anthropic {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
..
|
||||
}) => (api_url.clone(), *low_speed_timeout_in_seconds),
|
||||
_ => (None, None),
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Anthropic {
|
||||
default_model: AnthropicModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
});
|
||||
}
|
||||
"ollama" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Ollama { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
let (api_url, low_speed_timeout_in_seconds) = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Ollama {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
..
|
||||
}) => (api_url.clone(), *low_speed_timeout_in_seconds),
|
||||
_ => (None, None),
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Ollama {
|
||||
default_model: Some(ollama::Model::new(&model, None, None)),
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
});
|
||||
}
|
||||
"openai" => {
|
||||
let (api_url, available_models) = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
available_models,
|
||||
..
|
||||
}) => (api_url.clone(), available_models.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
let (api_url, low_speed_timeout_in_seconds, available_models) =
|
||||
match &settings.provider {
|
||||
Some(AssistantProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
..
|
||||
}) => (
|
||||
api_url.clone(),
|
||||
*low_speed_timeout_in_seconds,
|
||||
available_models.clone(),
|
||||
),
|
||||
_ => (None, None, None),
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::OpenAi {
|
||||
default_model: OpenAiModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod context_tests;
|
||||
|
||||
use crate::slash_command_working_set::SlashCommandWorkingSet;
|
||||
use crate::ToolWorkingSet;
|
||||
use crate::{
|
||||
prompts::PromptBuilder,
|
||||
slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
|
||||
@@ -11,11 +12,10 @@ use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::{
|
||||
SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult,
|
||||
};
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::{self, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use collections::{HashMap, HashSet};
|
||||
use feature_flags::{FeatureFlagAppExt, ToolUseFeatureFlag};
|
||||
use feature_flags::{FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{future::Shared, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
@@ -25,14 +25,12 @@ use gpui::{
|
||||
|
||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||
use language_model::{
|
||||
logging::report_assistant_event,
|
||||
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
||||
};
|
||||
use language_models::{
|
||||
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
|
||||
report_assistant_event,
|
||||
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role,
|
||||
StopReason,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::contexts_dir;
|
||||
@@ -383,9 +381,13 @@ pub enum ContextEvent {
|
||||
SlashCommandOutputSectionAdded {
|
||||
section: SlashCommandOutputSection<language::Anchor>,
|
||||
},
|
||||
SlashCommandFinished {
|
||||
output_range: Range<language::Anchor>,
|
||||
run_commands_in_ranges: Vec<Range<language::Anchor>>,
|
||||
},
|
||||
UsePendingTools,
|
||||
ToolFinished {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_use_id: Arc<str>,
|
||||
output_range: Range<language::Anchor>,
|
||||
},
|
||||
Operation(ContextOperation),
|
||||
@@ -479,7 +481,7 @@ pub enum Content {
|
||||
},
|
||||
ToolResult {
|
||||
range: Range<language::Anchor>,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_use_id: Arc<str>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -546,7 +548,7 @@ pub struct Context {
|
||||
pub(crate) slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
pub(crate) tools: Arc<ToolWorkingSet>,
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
pending_tool_uses_by_id: HashMap<Arc<str>, PendingToolUse>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
contents: Vec<Content>,
|
||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
@@ -914,7 +916,6 @@ impl Context {
|
||||
InvokedSlashCommand {
|
||||
name: name.into(),
|
||||
range: output_range,
|
||||
run_commands_in_ranges: Vec::new(),
|
||||
status: InvokedSlashCommandStatus::Running(Task::ready(())),
|
||||
transaction: None,
|
||||
timestamp: id.0,
|
||||
@@ -1126,7 +1127,7 @@ impl Context {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn get_tool_use_by_id(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> {
|
||||
pub fn get_tool_use_by_id(&self, id: &Arc<str>) -> Option<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.get(id)
|
||||
}
|
||||
|
||||
@@ -1913,6 +1914,7 @@ impl Context {
|
||||
}
|
||||
|
||||
let mut pending_section_stack: Vec<PendingSection> = Vec::new();
|
||||
let mut run_commands_in_ranges: Vec<Range<language::Anchor>> = Vec::new();
|
||||
let mut last_role: Option<Role> = None;
|
||||
let mut last_section_range = None;
|
||||
|
||||
@@ -1978,13 +1980,7 @@ impl Context {
|
||||
|
||||
let end = this.buffer.read(cx).anchor_before(insert_position);
|
||||
if run_commands_in_text {
|
||||
if let Some(invoked_slash_command) =
|
||||
this.invoked_slash_commands.get_mut(&command_id)
|
||||
{
|
||||
invoked_slash_command
|
||||
.run_commands_in_ranges
|
||||
.push(start..end);
|
||||
}
|
||||
run_commands_in_ranges.push(start..end);
|
||||
}
|
||||
}
|
||||
SlashCommandEvent::EndSection => {
|
||||
@@ -2104,7 +2100,6 @@ impl Context {
|
||||
InvokedSlashCommand {
|
||||
name: name.to_string().into(),
|
||||
range: command_range.clone(),
|
||||
run_commands_in_ranges: Vec::new(),
|
||||
status: InvokedSlashCommandStatus::Running(insert_output_task),
|
||||
transaction: Some(first_transaction),
|
||||
timestamp: command_id.0,
|
||||
@@ -2153,7 +2148,7 @@ impl Context {
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_use_id: Arc<str>,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
@@ -2340,10 +2335,11 @@ impl Context {
|
||||
let source_range = buffer.anchor_after(start_ix)
|
||||
..buffer.anchor_after(end_ix);
|
||||
|
||||
let tool_use_id: Arc<str> = tool_use.id.into();
|
||||
this.pending_tool_uses_by_id.insert(
|
||||
tool_use.id.clone(),
|
||||
tool_use_id.clone(),
|
||||
PendingToolUse {
|
||||
id: tool_use.id,
|
||||
id: tool_use_id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
status: PendingToolUseStatus::Idle,
|
||||
@@ -2387,11 +2383,7 @@ impl Context {
|
||||
});
|
||||
Some(error.to_string())
|
||||
} else {
|
||||
let error_message = error
|
||||
.chain()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let error_message = error.to_string().trim().to_string();
|
||||
cx.emit(ContextEvent::ShowAssistError(SharedString::from(
|
||||
error_message.clone(),
|
||||
)));
|
||||
@@ -2895,7 +2887,7 @@ impl Context {
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
"Generate a concise 3-7 word title for this conversation, omitting punctuation. Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`"
|
||||
"Generate a concise 3-7 word title for this conversation, omitting punctuation"
|
||||
.into(),
|
||||
],
|
||||
cache: false,
|
||||
@@ -3180,7 +3172,6 @@ pub struct ParsedSlashCommand {
|
||||
pub struct InvokedSlashCommand {
|
||||
pub name: SharedString,
|
||||
pub range: Range<language::Anchor>,
|
||||
pub run_commands_in_ranges: Vec<Range<language::Anchor>>,
|
||||
pub status: InvokedSlashCommandStatus,
|
||||
pub transaction: Option<language::TransactionId>,
|
||||
timestamp: clock::Lamport,
|
||||
@@ -3200,9 +3191,19 @@ pub enum PendingSlashCommandStatus {
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub(crate) struct ToolUseFeatureFlag;
|
||||
|
||||
impl FeatureFlag for ToolUseFeatureFlag {
|
||||
const NAME: &'static str = "assistant-tool-use";
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
pub id: Arc<str>,
|
||||
pub name: String,
|
||||
pub input: serde_json::Value,
|
||||
pub status: PendingToolUseStatus,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{AssistantEdit, MessageCacheMetadata};
|
||||
use crate::slash_command_working_set::SlashCommandWorkingSet;
|
||||
use crate::ToolWorkingSet;
|
||||
use crate::{
|
||||
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
|
||||
Context, ContextEvent, ContextId, ContextOperation, InvokedSlashCommandId, MessageId,
|
||||
@@ -10,7 +11,6 @@ use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
|
||||
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
|
||||
};
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
use futures::{
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use crate::slash_command::context_server_command;
|
||||
use crate::SlashCommandId;
|
||||
use crate::{
|
||||
prompts::PromptBuilder, slash_command_working_set::SlashCommandWorkingSet, Context,
|
||||
ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext, SavedContextMetadata,
|
||||
};
|
||||
use crate::{tools, SlashCommandId, ToolId, ToolWorkingSet};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||
use clock::ReplicaId;
|
||||
use collections::HashMap;
|
||||
use context_server::manager::ContextServerManager;
|
||||
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use context_servers::manager::{ContextServerManager, ContextServerSettings};
|
||||
use context_servers::{ContextServerFactoryRegistry, CONTEXT_SERVERS_NAMESPACE};
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
@@ -22,6 +22,7 @@ use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
use rpc::AnyProtoClient;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
ffi::OsStr,
|
||||
@@ -110,11 +111,7 @@ impl ContextStore {
|
||||
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||
let context_server_factory_registry =
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
let context_server_manager = cx.new_model(|cx| {
|
||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||
});
|
||||
let context_server_manager = cx.new_model(|_cx| ContextServerManager::new());
|
||||
let mut this = Self {
|
||||
contexts: Vec::new(),
|
||||
contexts_metadata: Vec::new(),
|
||||
@@ -148,19 +145,91 @@ impl ContextStore {
|
||||
project: project.clone(),
|
||||
prompt_builder,
|
||||
};
|
||||
this.handle_project_changed(project.clone(), cx);
|
||||
this.handle_project_changed(project, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
this.register_context_server_handlers(cx);
|
||||
|
||||
// TODO: At the time when we construct the `ContextStore` we may not have yet initialized the extensions.
|
||||
// In order to register the context servers when the extension is loaded, we're periodically looping to
|
||||
// see if there are context servers to register.
|
||||
//
|
||||
// I tried doing this in a subscription on the `ExtensionStore`, but it never seemed to fire.
|
||||
//
|
||||
// We should find a more elegant way to do this.
|
||||
let context_server_factory_registry =
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
cx.spawn(|context_store, mut cx| async move {
|
||||
loop {
|
||||
let mut servers_to_register = Vec::new();
|
||||
for (_id, factory) in
|
||||
context_server_factory_registry.context_server_factories()
|
||||
{
|
||||
if let Some(server) = factory(&cx).await.log_err() {
|
||||
servers_to_register.push(server);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(_) = context_store
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.context_server_manager.update(cx, |this, cx| {
|
||||
for server in servers_to_register {
|
||||
this.add_server(server, cx).detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
.log_err()
|
||||
else {
|
||||
break;
|
||||
};
|
||||
|
||||
smol::Timer::after(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
this
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.watch_context_server_settings(cx);
|
||||
})
|
||||
.log_err();
|
||||
|
||||
Ok(this)
|
||||
})
|
||||
}
|
||||
|
||||
fn watch_context_server_settings(&self, cx: &mut ModelContext<Self>) {
|
||||
cx.observe_global::<SettingsStore>(move |this, cx| {
|
||||
this.context_server_manager.update(cx, |manager, cx| {
|
||||
let location = this.project.read(cx).worktrees(cx).next().map(|worktree| {
|
||||
settings::SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: Path::new(""),
|
||||
}
|
||||
});
|
||||
let settings = ContextServerSettings::get(location, cx);
|
||||
|
||||
manager.maintain_servers(settings, cx);
|
||||
|
||||
let has_any_context_servers = !manager.servers().is_empty();
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
if has_any_context_servers {
|
||||
filter.show_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
} else {
|
||||
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
async fn handle_advertise_contexts(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::AdvertiseContexts>,
|
||||
@@ -771,7 +840,7 @@ impl ContextStore {
|
||||
contexts.push(SavedContextMetadata {
|
||||
title: title.to_string(),
|
||||
path,
|
||||
mtime: metadata.mtime.timestamp_for_user().into(),
|
||||
mtime: metadata.mtime.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -809,13 +878,13 @@ impl ContextStore {
|
||||
fn handle_context_server_event(
|
||||
&mut self,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
event: &context_server::manager::Event,
|
||||
event: &context_servers::manager::Event,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let slash_command_working_set = self.slash_commands.clone();
|
||||
let tool_working_set = self.tools.clone();
|
||||
match event {
|
||||
context_server::manager::Event::ServerStarted { server_id } => {
|
||||
context_servers::manager::Event::ServerStarted { server_id } => {
|
||||
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
|
||||
let context_server_manager = context_server_manager.clone();
|
||||
cx.spawn({
|
||||
@@ -826,7 +895,7 @@ impl ContextStore {
|
||||
return;
|
||||
};
|
||||
|
||||
if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
|
||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||
let slash_command_ids = prompts
|
||||
.into_iter()
|
||||
@@ -854,12 +923,12 @@ impl ContextStore {
|
||||
}
|
||||
}
|
||||
|
||||
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
|
||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||
let tool_ids = tools.tools.into_iter().map(|tool| {
|
||||
log::info!("registering context server tool: {:?}", tool.name);
|
||||
tool_working_set.insert(
|
||||
Arc::new(ContextServerTool::new(
|
||||
Arc::new(tools::context_server_tool::ContextServerTool::new(
|
||||
context_server_manager.clone(),
|
||||
server.id(),
|
||||
tool,
|
||||
@@ -881,7 +950,7 @@ impl ContextStore {
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
context_server::manager::Event::ServerStopped { server_id } => {
|
||||
context_servers::manager::Event::ServerStopped { server_id } => {
|
||||
if let Some(slash_command_ids) =
|
||||
self.context_server_slash_command_ids.remove(server_id)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
|
||||
AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist, LineDiff, LineOperation, RequestType, StreamingDiff,
|
||||
CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, RequestType, StreamingDiff,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use client::{telemetry::Telemetry, ErrorExt};
|
||||
@@ -24,22 +24,20 @@ use futures::{
|
||||
join, SinkExt, Stream, StreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
|
||||
anchored, deferred, point, AnyElement, AppContext, ClickEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext, Subscription, Task,
|
||||
TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelTextStream, Role,
|
||||
logging::report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
||||
};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
use rope::Rope;
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use smol::future::FutureExt;
|
||||
use std::{
|
||||
cmp,
|
||||
@@ -462,7 +460,7 @@ impl InlineAssistant {
|
||||
style: BlockStyle::Sticky,
|
||||
placement: BlockPlacement::Below(range.end),
|
||||
height: 0,
|
||||
render: Arc::new(|cx| {
|
||||
render: Box::new(|cx| {
|
||||
v_flex()
|
||||
.h_full()
|
||||
.w_full()
|
||||
@@ -1199,9 +1197,8 @@ impl InlineAssistant {
|
||||
placement: BlockPlacement::Above(new_row),
|
||||
height,
|
||||
style: BlockStyle::Flex,
|
||||
render: Arc::new(move |cx| {
|
||||
render: Box::new(move |cx| {
|
||||
div()
|
||||
.block_mouse_down()
|
||||
.bg(cx.theme().status().deleted_background)
|
||||
.size_full()
|
||||
.h(height as f32 * cx.line_height())
|
||||
@@ -1320,7 +1317,7 @@ impl InlineAssistGroup {
|
||||
|
||||
fn build_assist_editor_renderer(editor: &View<PromptEditor>) -> RenderBlock {
|
||||
let editor = editor.clone();
|
||||
Arc::new(move |cx: &mut BlockContext| {
|
||||
Box::new(move |cx: &mut BlockContext| {
|
||||
*editor.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
|
||||
editor.clone().into_any_element()
|
||||
})
|
||||
@@ -1483,8 +1480,6 @@ impl Render for PromptEditor {
|
||||
h_flex()
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.block_mouse_down()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.size_full()
|
||||
@@ -1501,17 +1496,8 @@ impl Render for PromptEditor {
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
LanguageModelSelector::new(
|
||||
{
|
||||
let fs = self.fs.clone();
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
);
|
||||
}
|
||||
},
|
||||
ModelSelector::new(
|
||||
self.fs.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -1531,7 +1517,7 @@ impl Render for PromptEditor {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.info_text(
|
||||
.with_info_text(
|
||||
"Inline edits use context\n\
|
||||
from the currently selected\n\
|
||||
assistant panel tab.",
|
||||
@@ -2576,7 +2562,6 @@ pub struct CodegenAlternative {
|
||||
line_operations: Vec<LineOperation>,
|
||||
request: Option<LanguageModelRequest>,
|
||||
elapsed_time: Option<f64>,
|
||||
completion: Option<String>,
|
||||
message_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -2652,7 +2637,6 @@ impl CodegenAlternative {
|
||||
range,
|
||||
request: None,
|
||||
elapsed_time: None,
|
||||
completion: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2865,9 +2849,6 @@ impl CodegenAlternative {
|
||||
self.diff = Diff::default();
|
||||
self.status = CodegenStatus::Pending;
|
||||
let mut edit_start = self.range.start.to_offset(&snapshot);
|
||||
let completion = Arc::new(Mutex::new(String::new()));
|
||||
let completion_clone = completion.clone();
|
||||
|
||||
self.generation = cx.spawn(|codegen, mut cx| {
|
||||
async move {
|
||||
let stream = stream.await;
|
||||
@@ -2899,7 +2880,6 @@ impl CodegenAlternative {
|
||||
response_latency = Some(request_start.elapsed());
|
||||
}
|
||||
let chunk = chunk?;
|
||||
completion_clone.lock().push_str(&chunk);
|
||||
|
||||
let mut lines = chunk.split('\n').peekable();
|
||||
while let Some(line) = lines.next() {
|
||||
@@ -3069,7 +3049,6 @@ impl CodegenAlternative {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
this.elapsed_time = Some(elapsed_time);
|
||||
this.completion = Some(completion.lock().clone());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
})
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
use feature_flags::ZedPro;
|
||||
|
||||
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
|
||||
use proto::Plan;
|
||||
use workspace::ShowConfiguration;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use feature_flags::ZedPro;
|
||||
use gpui::{Action, AnyElement, AppContext, DismissEvent, SharedString, Task};
|
||||
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{Action, AnyElement, DismissEvent, SharedString, Task};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use proto::Plan;
|
||||
use settings::update_settings_file;
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
|
||||
use workspace::ShowConfiguration;
|
||||
|
||||
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
|
||||
|
||||
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &AppContext) + 'static>;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct LanguageModelSelector<T: PopoverTrigger> {
|
||||
handle: Option<PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>>,
|
||||
on_model_changed: OnModelChanged,
|
||||
pub struct ModelSelector<T: PopoverTrigger> {
|
||||
handle: Option<PopoverMenuHandle<Picker<ModelPickerDelegate>>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
trigger: T,
|
||||
info_text: Option<SharedString>,
|
||||
}
|
||||
|
||||
pub struct LanguageModelPickerDelegate {
|
||||
on_model_changed: OnModelChanged,
|
||||
pub struct ModelPickerDelegate {
|
||||
fs: Arc<dyn Fs>,
|
||||
all_models: Vec<ModelInfo>,
|
||||
filtered_models: Vec<ModelInfo>,
|
||||
selected_index: usize,
|
||||
@@ -35,34 +38,28 @@ struct ModelInfo {
|
||||
is_selected: bool,
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> LanguageModelSelector<T> {
|
||||
pub fn new(
|
||||
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &AppContext) + 'static,
|
||||
trigger: T,
|
||||
) -> Self {
|
||||
LanguageModelSelector {
|
||||
impl<T: PopoverTrigger> ModelSelector<T> {
|
||||
pub fn new(fs: Arc<dyn Fs>, trigger: T) -> Self {
|
||||
ModelSelector {
|
||||
handle: None,
|
||||
on_model_changed: Arc::new(on_model_changed),
|
||||
fs,
|
||||
trigger,
|
||||
info_text: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_handle(
|
||||
mut self,
|
||||
handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
|
||||
) -> Self {
|
||||
pub fn with_handle(mut self, handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>) -> Self {
|
||||
self.handle = Some(handle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn info_text(mut self, text: impl Into<SharedString>) -> Self {
|
||||
pub fn with_info_text(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.info_text = Some(text.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
impl PickerDelegate for ModelPickerDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
@@ -140,7 +137,9 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(model_info) = self.filtered_models.get(self.selected_index) {
|
||||
let model = model_info.model.clone();
|
||||
(self.on_model_changed)(model.clone(), cx);
|
||||
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.set_model(model.clone())
|
||||
});
|
||||
|
||||
// Update the selection status
|
||||
let selected_model_id = model_info.model.id();
|
||||
@@ -297,7 +296,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> RenderOnce for LanguageModelSelector<T> {
|
||||
impl<T: PopoverTrigger> RenderOnce for ModelSelector<T> {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let selected_provider = LanguageModelRegistry::read_global(cx)
|
||||
.active_provider()
|
||||
@@ -332,8 +331,8 @@ impl<T: PopoverTrigger> RenderOnce for LanguageModelSelector<T> {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let delegate = LanguageModelPickerDelegate {
|
||||
on_model_changed: self.on_model_changed.clone(),
|
||||
let delegate = ModelPickerDelegate {
|
||||
fs: self.fs.clone(),
|
||||
all_models: all_models.clone(),
|
||||
filtered_models: all_models,
|
||||
selected_index: 0,
|
||||
@@ -830,7 +830,7 @@ impl PromptLibrary {
|
||||
.overflow_x_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.p(DynamicSpacing::Base04.rems(cx))
|
||||
.p(Spacing::Small.rems(cx))
|
||||
.h_9()
|
||||
.w_full()
|
||||
.flex_none()
|
||||
@@ -871,17 +871,17 @@ impl PromptLibrary {
|
||||
.size_full()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.pl(DynamicSpacing::Base16.rems(cx))
|
||||
.pt(DynamicSpacing::Base08.rems(cx))
|
||||
.pl(Spacing::XXLarge.rems(cx))
|
||||
.pt(Spacing::Large.rems(cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.focus(&focus_handle);
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.group("active-editor-header")
|
||||
.pr(DynamicSpacing::Base16.rems(cx))
|
||||
.pt(DynamicSpacing::Base02.rems(cx))
|
||||
.pb(DynamicSpacing::Base08.rems(cx))
|
||||
.pr(Spacing::XXLarge.rems(cx))
|
||||
.pt(Spacing::XSmall.rems(cx))
|
||||
.pb(Spacing::Large.rems(cx))
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex().gap_1().child(
|
||||
@@ -943,13 +943,13 @@ impl PromptLibrary {
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap(DynamicSpacing::Base16.rems(cx))
|
||||
.gap(Spacing::XXLarge.rems(cx))
|
||||
.child(div()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap(DynamicSpacing::Base16.rems(cx))
|
||||
.gap(Spacing::XXLarge.rems(cx))
|
||||
.children(prompt_editor.token_count.map(
|
||||
|token_count| {
|
||||
let token_count: SharedString =
|
||||
|
||||
@@ -149,7 +149,7 @@ impl PromptBuilder {
|
||||
if file_path.to_string_lossy().ends_with(".hbs") {
|
||||
if let Ok(content) = params.fs.load(&file_path).await {
|
||||
let file_name = file_path.file_stem().unwrap().to_string_lossy();
|
||||
log::debug!("Registering prompt template override: {}", file_name);
|
||||
log::info!("Registering prompt template override: {}", file_name);
|
||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ impl PromptBuilder {
|
||||
for path in Assets.list("prompts")? {
|
||||
if let Some(id) = path.split('/').last().and_then(|s| s.strip_suffix(".hbs")) {
|
||||
if let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() {
|
||||
log::debug!("Registering built-in prompt template: {}", id);
|
||||
log::info!("Registering built-in prompt template: {}", id);
|
||||
let prompt = String::from_utf8_lossy(prompt.as_ref());
|
||||
handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))?
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::assistant_panel::ContextEditor;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::AfterCompletion;
|
||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
|
||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
|
||||
use editor::{CompletionProvider, Editor};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
|
||||
@@ -171,7 +171,8 @@ impl SlashCommandCompletionProvider {
|
||||
let mut flag = self.cancel_flag.lock();
|
||||
flag.store(true, SeqCst);
|
||||
*flag = new_cancel_flag.clone();
|
||||
if let Some(command) = self.slash_commands.command(command_name, cx) {
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
if let Some(command) = commands.command(command_name) {
|
||||
let completions = command.complete_argument(
|
||||
arguments,
|
||||
new_cancel_flag.clone(),
|
||||
|
||||
@@ -4,7 +4,7 @@ use assistant_slash_command::{
|
||||
SlashCommandOutputSection, SlashCommandResult,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use context_server::{
|
||||
use context_servers::{
|
||||
manager::{ContextServer, ContextServerManager},
|
||||
types::Prompt,
|
||||
};
|
||||
@@ -27,7 +27,7 @@ pub struct ContextServerSlashCommand {
|
||||
impl ContextServerSlashCommand {
|
||||
pub fn new(
|
||||
server_manager: Model<ContextServerManager>,
|
||||
server: &Arc<ContextServer>,
|
||||
server: &Arc<dyn ContextServer>,
|
||||
prompt: Prompt,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -95,9 +95,9 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
|
||||
let completion_result = protocol
|
||||
.completion(
|
||||
context_server::types::CompletionReference::Prompt(
|
||||
context_server::types::PromptReference {
|
||||
r#type: context_server::types::PromptReferenceType::Prompt,
|
||||
context_servers::types::CompletionReference::Prompt(
|
||||
context_servers::types::PromptReference {
|
||||
r#type: context_servers::types::PromptReferenceType::Prompt,
|
||||
name: prompt_name,
|
||||
},
|
||||
),
|
||||
@@ -152,7 +152,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
if result
|
||||
.messages
|
||||
.iter()
|
||||
.any(|msg| !matches!(msg.role, context_server::types::Role::User))
|
||||
.any(|msg| !matches!(msg.role, context_servers::types::SamplingRole::User))
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"Prompt contains non-user roles, which is not supported"
|
||||
@@ -164,7 +164,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
.messages
|
||||
.into_iter()
|
||||
.filter_map(|msg| match msg.content {
|
||||
context_server::types::MessageContent::Text { text } => Some(text),
|
||||
context_servers::types::SamplingContent::Text { text } => Some(text),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
|
||||
@@ -69,10 +69,6 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range: 0..text.len(),
|
||||
|
||||
@@ -108,10 +108,6 @@ impl SlashCommand for FetchSlashCommand {
|
||||
"Insert fetched URL contents".into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Globe
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.description()
|
||||
}
|
||||
@@ -166,7 +162,7 @@ impl SlashCommand for FetchSlashCommand {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
icon: IconName::Globe,
|
||||
icon: IconName::AtSign,
|
||||
label: format!("fetch {}", url).into(),
|
||||
metadata: None,
|
||||
}],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use crate::{
|
||||
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType,
|
||||
DEFAULT_CONTEXT_LINES,
|
||||
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent,
|
||||
ModelSelector, RequestType, DEFAULT_CONTEXT_LINES,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
@@ -18,11 +17,10 @@ use gpui::{
|
||||
};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
logging::report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use language_models::report_assistant_event;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
cmp,
|
||||
sync::Arc,
|
||||
@@ -32,7 +30,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
use terminal_view::TerminalView;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};
|
||||
use ui::{prelude::*, IconButtonShape, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::{notifications::NotificationId, Toast, Workspace};
|
||||
|
||||
@@ -614,17 +612,8 @@ impl Render for PromptEditor {
|
||||
.w_12()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelector::new(
|
||||
{
|
||||
let fs = self.fs.clone();
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
);
|
||||
}
|
||||
},
|
||||
.child(ModelSelector::new(
|
||||
self.fs.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -704,7 +693,7 @@ impl PromptEditor {
|
||||
cx,
|
||||
);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
|
||||
editor.set_placeholder_text("Add a prompt…", cx);
|
||||
editor
|
||||
});
|
||||
|
||||
@@ -737,14 +726,6 @@ impl PromptEditor {
|
||||
this
|
||||
}
|
||||
|
||||
fn placeholder_text(cx: &WindowContext) -> String {
|
||||
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
|
||||
.map(|keybinding| format!(" • {keybinding} for context"))
|
||||
.unwrap_or_default();
|
||||
|
||||
format!("Generate…{context_keybinding} • ↓↑ for history")
|
||||
}
|
||||
|
||||
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor_subscriptions.clear();
|
||||
self.editor_subscriptions
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_tool::{Tool, ToolRegistry};
|
||||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{Tool, ToolRegistry};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct ToolId(usize);
|
||||
2
crates/assistant/src/tools.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod context_server_tool;
|
||||
pub mod now_tool;
|
||||
@@ -2,11 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use assistant_tool::Tool;
|
||||
use context_servers::manager::ContextServerManager;
|
||||
use context_servers::types;
|
||||
use gpui::{Model, Task};
|
||||
|
||||
use crate::manager::ContextServerManager;
|
||||
use crate::types;
|
||||
|
||||
pub struct ContextServerTool {
|
||||
server_manager: Model<ContextServerManager>,
|
||||
server_id: Arc<str>,
|
||||
@@ -75,21 +74,11 @@ impl Tool for ContextServerTool {
|
||||
);
|
||||
let response = protocol.run_tool(tool_name, arguments).await?;
|
||||
|
||||
let mut result = String::new();
|
||||
for content in response.content {
|
||||
match content {
|
||||
types::ToolResponseContent::Text { text } => {
|
||||
result.push_str(&text);
|
||||
}
|
||||
types::ToolResponseContent::Image { .. } => {
|
||||
log::warn!("Ignoring image content from tool response");
|
||||
}
|
||||
types::ToolResponseContent::Resource { .. } => {
|
||||
log::warn!("Ignoring resource content from tool response");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
let tool_result = match response.tool_result {
|
||||
serde_json::Value::String(s) => s,
|
||||
_ => serde_json::to_string(&response.tool_result)?,
|
||||
};
|
||||
Ok(tool_result)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@@ -30,7 +30,7 @@ impl Tool for NowTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Returns the current datetime in RFC 3339 format. Only use this tool when the user specifically asks for it or the current task would benefit from knowing the current datetime.".into()
|
||||
"Returns the current datetime in RFC 3339 format.".into()
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
@@ -1,34 +0,0 @@
|
||||
[package]
|
||||
name = "assistant2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
proto.workspace = true
|
||||
settings.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
@@ -1,45 +0,0 @@
|
||||
mod assistant_panel;
|
||||
mod message_editor;
|
||||
mod thread;
|
||||
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
use gpui::{actions, AppContext};
|
||||
|
||||
pub use crate::assistant_panel::AssistantPanel;
|
||||
|
||||
actions!(
|
||||
assistant2,
|
||||
[ToggleFocus, NewThread, ToggleModelSelector, Chat]
|
||||
);
|
||||
|
||||
const NAMESPACE: &str = "assistant2";
|
||||
|
||||
/// Initializes the `assistant2` crate.
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
assistant_panel::init(cx);
|
||||
feature_gate_assistant2_actions(cx);
|
||||
}
|
||||
|
||||
fn feature_gate_assistant2_actions(cx: &mut AppContext) {
|
||||
const ASSISTANT1_NAMESPACE: &str = "assistant";
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(NAMESPACE);
|
||||
});
|
||||
|
||||
cx.observe_flag::<Assistant2FeatureFlag, _>(move |is_enabled, cx| {
|
||||
if is_enabled {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.show_namespace(NAMESPACE);
|
||||
filter.hide_namespace(ASSISTANT1_NAMESPACE);
|
||||
});
|
||||
} else {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(NAMESPACE);
|
||||
filter.show_namespace(ASSISTANT1_NAMESPACE);
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use gpui::{
|
||||
prelude::*, px, Action, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
|
||||
FocusableView, Model, Pixels, Subscription, Task, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, Role};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::message_editor::MessageEditor;
|
||||
use crate::thread::{Message, Thread, ThreadEvent};
|
||||
use crate::{NewThread, ToggleFocus, ToggleModelSelector};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
});
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakView<Workspace>,
|
||||
thread: Model<Thread>,
|
||||
message_editor: View<MessageEditor>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
pub fn load(
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let tools = Arc::new(ToolWorkingSet::default());
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
cx.new_view(|cx| Self::new(workspace, tools, cx))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn new(workspace: &Workspace, tools: Arc<ToolWorkingSet>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let thread = cx.new_model(|cx| Thread::new(tools.clone(), cx));
|
||||
let subscriptions = vec![
|
||||
cx.observe(&thread, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&thread, Self::handle_thread_event),
|
||||
];
|
||||
|
||||
Self {
|
||||
workspace: workspace.weak_handle(),
|
||||
thread: thread.clone(),
|
||||
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
|
||||
tools,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let tools = self.thread.read(cx).tools().clone();
|
||||
let thread = cx.new_model(|cx| Thread::new(tools, cx));
|
||||
let subscriptions = vec![
|
||||
cx.observe(&thread, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&thread, Self::handle_thread_event),
|
||||
];
|
||||
|
||||
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread.clone(), cx));
|
||||
self.thread = thread;
|
||||
self._subscriptions = subscriptions;
|
||||
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
fn handle_thread_event(
|
||||
&mut self,
|
||||
_: Model<Thread>,
|
||||
event: &ThreadEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ThreadEvent::StreamedCompletion => {}
|
||||
ThreadEvent::UsePendingTools => {
|
||||
let pending_tool_uses = self
|
||||
.thread
|
||||
.read(cx)
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.filter(|tool_use| tool_use.status.is_idle())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for tool_use in pending_tool_uses {
|
||||
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_tool_output(
|
||||
tool_use.assistant_message_id,
|
||||
tool_use.id.clone(),
|
||||
task,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ThreadEvent::ToolFinished { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for AssistantPanel {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.message_editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for AssistantPanel {}
|
||||
|
||||
impl Panel for AssistantPanel {
|
||||
fn persistent_name() -> &'static str {
|
||||
"AssistantPanel2"
|
||||
}
|
||||
|
||||
fn position(&self, _cx: &WindowContext) -> DockPosition {
|
||||
DockPosition::Right
|
||||
}
|
||||
|
||||
fn position_is_valid(&self, _: DockPosition) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_position(&mut self, _position: DockPosition, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
fn size(&self, _cx: &WindowContext) -> Pixels {
|
||||
px(640.)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, _size: Option<Pixels>, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
|
||||
|
||||
fn remote_id() -> Option<proto::PanelId> {
|
||||
Some(proto::PanelId::AssistantPanel)
|
||||
}
|
||||
|
||||
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
|
||||
Some(IconName::ZedAssistant)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
Some("Assistant Panel")
|
||||
}
|
||||
|
||||
fn toggle_action(&self) -> Box<dyn Action> {
|
||||
Box::new(ToggleFocus)
|
||||
}
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
h_flex()
|
||||
.id("assistant-toolbar")
|
||||
.justify_between()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.h(Tab::container_height(cx))
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(h_flex().child(Label::new("Thread Title Goes Here")))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.child(self.render_language_model_selector(cx))
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new("new-thread", IconName::Plus)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New Thread",
|
||||
&NewThread,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, _cx| {
|
||||
println!("New Thread");
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Open History", cx))
|
||||
.on_click(move |_event, _cx| {
|
||||
println!("Open History");
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
|
||||
.on_click(move |_event, _cx| {
|
||||
println!("Configure Assistant");
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
|
||||
LanguageModelSelector::new(
|
||||
|model, _cx| {
|
||||
println!("Selected {:?}", model.name());
|
||||
},
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
div()
|
||||
.overflow_x_hidden()
|
||||
.flex_grow()
|
||||
.whitespace_nowrap()
|
||||
.child(match (active_provider, active_model) {
|
||||
(Some(provider), Some(model)) => h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(
|
||||
model.icon().unwrap_or_else(|| provider.icon()),
|
||||
)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model.name().0)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element(),
|
||||
_ => Label::new("No model selected")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_message(&self, message: Message, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let (role_icon, role_name) = match message.role {
|
||||
Role::User => (IconName::Person, "You"),
|
||||
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
|
||||
Role::System => (IconName::Settings, "System"),
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.p_1p5()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Icon::new(role_icon).size(IconSize::Small))
|
||||
.child(Label::new(role_name).size(LabelSize::Small)),
|
||||
),
|
||||
)
|
||||
.child(v_flex().p_1p5().child(Label::new(message.text.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let messages = self.thread.read(cx).messages().cloned().collect::<Vec<_>>();
|
||||
|
||||
v_flex()
|
||||
.key_context("AssistantPanel2")
|
||||
.justify_between()
|
||||
.size_full()
|
||||
.on_action(cx.listener(|this, _: &NewThread, cx| {
|
||||
this.new_thread(cx);
|
||||
}))
|
||||
.child(self.render_toolbar(cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.id("message-list")
|
||||
.gap_2()
|
||||
.size_full()
|
||||
.p_2()
|
||||
.overflow_y_scroll()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.children(
|
||||
messages
|
||||
.into_iter()
|
||||
.map(|message| self.render_message(message, cx)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(self.message_editor.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{AppContext, FocusableView, Model, TextStyle, View};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding};
|
||||
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::Chat;
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Model<Thread>,
|
||||
editor: View<Editor>,
|
||||
use_tools: bool,
|
||||
}
|
||||
|
||||
impl MessageEditor {
|
||||
pub fn new(thread: Model<Thread>, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
thread,
|
||||
editor: cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(80, cx);
|
||||
editor.set_placeholder_text("Ask anything…", cx);
|
||||
|
||||
editor
|
||||
}),
|
||||
use_tools: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
||||
self.send_to_model(RequestKind::Chat, cx);
|
||||
}
|
||||
|
||||
fn send_to_model(
|
||||
&mut self,
|
||||
request_kind: RequestKind,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<()> {
|
||||
let provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
if provider
|
||||
.as_ref()
|
||||
.map_or(false, |provider| provider.must_accept_terms(cx))
|
||||
{
|
||||
cx.notify();
|
||||
return None;
|
||||
}
|
||||
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
let model = model_registry.active_model()?;
|
||||
|
||||
let user_message = self.editor.update(cx, |editor, cx| {
|
||||
let text = editor.text(cx);
|
||||
editor.clear(cx);
|
||||
text
|
||||
});
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message(user_message);
|
||||
let mut request = thread.to_completion_request(request_kind, cx);
|
||||
|
||||
if self.use_tools {
|
||||
request.tools = thread
|
||||
.tools()
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
thread.stream_completion(request, model, cx)
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for MessageEditor {
|
||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MessageEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let font_size = TextSize::Default.rems(cx);
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
|
||||
v_flex()
|
||||
.key_context("MessageEditor")
|
||||
.on_action(cx.listener(Self::chat))
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.p_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_size: font_size.into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: line_height.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.child(
|
||||
Button::new("add-context", "Add Context")
|
||||
.style(ButtonStyle::Filled)
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start),
|
||||
)
|
||||
.child(CheckboxWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools"),
|
||||
self.use_tools.into(),
|
||||
cx.listener(|this, selection, _cx| {
|
||||
this.use_tools = match selection {
|
||||
Selection::Selected => true,
|
||||
Selection::Unselected | Selection::Indeterminate => false,
|
||||
};
|
||||
}),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Button::new("codebase", "Codebase").style(ButtonStyle::Filled))
|
||||
.child(Label::new("or"))
|
||||
.child(
|
||||
ButtonLike::new("chat")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Chat"))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use collections::HashMap;
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use gpui::{AppContext, EventEmitter, ModelContext, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelToolResult, LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
|
||||
StopReason,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::post_inc;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RequestKind {
|
||||
Chat,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct MessageId(usize);
|
||||
|
||||
impl MessageId {
|
||||
fn post_inc(&mut self) -> Self {
|
||||
Self(post_inc(&mut self.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// A message in a [`Thread`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Message {
|
||||
pub id: MessageId,
|
||||
pub role: Role,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// A thread of conversation with the LLM.
|
||||
pub struct Thread {
|
||||
messages: Vec<Message>,
|
||||
next_message_id: MessageId,
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
tool_uses_by_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
||||
tool_results_by_message: HashMap<MessageId, Vec<LanguageModelToolResult>>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
messages: Vec::new(),
|
||||
next_message_id: MessageId(0),
|
||||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
tools,
|
||||
tool_uses_by_message: HashMap::default(),
|
||||
tool_results_by_message: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn messages(&self) -> impl Iterator<Item = &Message> {
|
||||
self.messages.iter()
|
||||
}
|
||||
|
||||
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
|
||||
&self.tools
|
||||
}
|
||||
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn insert_user_message(&mut self, text: impl Into<String>) {
|
||||
self.messages.push(Message {
|
||||
id: self.next_message_id.post_inc(),
|
||||
role: Role::User,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn to_completion_request(
|
||||
&self,
|
||||
_request_kind: RequestKind,
|
||||
_cx: &AppContext,
|
||||
) -> LanguageModelRequest {
|
||||
let mut request = LanguageModelRequest {
|
||||
messages: vec![],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
};
|
||||
|
||||
for message in &self.messages {
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: message.role,
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
};
|
||||
|
||||
if let Some(tool_results) = self.tool_results_by_message.get(&message.id) {
|
||||
for tool_result in tool_results {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolResult(tool_result.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if !message.text.is_empty() {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::Text(message.text.clone()));
|
||||
}
|
||||
|
||||
if let Some(tool_uses) = self.tool_uses_by_message.get(&message.id) {
|
||||
for tool_use in tool_uses {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolUse(tool_use.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
request.messages.push(request_message);
|
||||
}
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
pub fn stream_completion(
|
||||
&mut self,
|
||||
request: LanguageModelRequest,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let pending_completion_id = post_inc(&mut self.completion_count);
|
||||
|
||||
let task = cx.spawn(|thread, mut cx| async move {
|
||||
let stream = model.stream_completion(request, &cx);
|
||||
let stream_completion = async {
|
||||
let mut events = stream.await?;
|
||||
let mut stop_reason = StopReason::EndTurn;
|
||||
|
||||
while let Some(event) = events.next().await {
|
||||
let event = event?;
|
||||
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
match event {
|
||||
LanguageModelCompletionEvent::StartMessage { .. } => {
|
||||
thread.messages.push(Message {
|
||||
id: thread.next_message_id.post_inc(),
|
||||
role: Role::Assistant,
|
||||
text: String::new(),
|
||||
});
|
||||
}
|
||||
LanguageModelCompletionEvent::Stop(reason) => {
|
||||
stop_reason = reason;
|
||||
}
|
||||
LanguageModelCompletionEvent::Text(chunk) => {
|
||||
if let Some(last_message) = thread.messages.last_mut() {
|
||||
if last_message.role == Role::Assistant {
|
||||
last_message.text.push_str(&chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
LanguageModelCompletionEvent::ToolUse(tool_use) => {
|
||||
if let Some(last_assistant_message) = thread
|
||||
.messages
|
||||
.iter()
|
||||
.rfind(|message| message.role == Role::Assistant)
|
||||
{
|
||||
thread
|
||||
.tool_uses_by_message
|
||||
.entry(last_assistant_message.id)
|
||||
.or_default()
|
||||
.push(tool_use.clone());
|
||||
|
||||
thread.pending_tool_uses_by_id.insert(
|
||||
tool_use.id.clone(),
|
||||
PendingToolUse {
|
||||
assistant_message_id: last_assistant_message.id,
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
status: PendingToolUseStatus::Idle,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.emit(ThreadEvent::StreamedCompletion);
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
smol::future::yield_now().await;
|
||||
}
|
||||
|
||||
thread.update(&mut cx, |thread, _cx| {
|
||||
thread
|
||||
.pending_completions
|
||||
.retain(|completion| completion.id != pending_completion_id);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(stop_reason)
|
||||
};
|
||||
|
||||
let result = stream_completion.await;
|
||||
|
||||
thread
|
||||
.update(&mut cx, |_thread, cx| {
|
||||
let error_message = if let Some(error) = result.as_ref().err() {
|
||||
let error_message = error
|
||||
.chain()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
Some(error_message)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(error_message) = error_message {
|
||||
eprintln!("Completion failed: {error_message:?}");
|
||||
}
|
||||
|
||||
if let Ok(stop_reason) = result {
|
||||
match stop_reason {
|
||||
StopReason::ToolUse => {
|
||||
cx.emit(ThreadEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
||||
self.pending_completions.push(PendingCompletion {
|
||||
id: pending_completion_id,
|
||||
_task: task,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
assistant_message_id: MessageId,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let insert_output_task = cx.spawn(|thread, mut cx| {
|
||||
let tool_use_id = tool_use_id.clone();
|
||||
async move {
|
||||
let output = output.await;
|
||||
thread
|
||||
.update(&mut cx, |thread, cx| {
|
||||
// The tool use was requested by an Assistant message,
|
||||
// so we want to attach the tool results to the next
|
||||
// user message.
|
||||
let next_user_message = MessageId(assistant_message_id.0 + 1);
|
||||
|
||||
let tool_results = thread
|
||||
.tool_results_by_message
|
||||
.entry(next_user_message)
|
||||
.or_default();
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
tool_results.push(LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.to_string(),
|
||||
content: output,
|
||||
is_error: false,
|
||||
});
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
|
||||
}
|
||||
Err(err) => {
|
||||
tool_results.push(LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.to_string(),
|
||||
content: err.to_string(),
|
||||
is_error: true,
|
||||
});
|
||||
|
||||
if let Some(tool_use) =
|
||||
thread.pending_tool_uses_by_id.get_mut(&tool_use_id)
|
||||
{
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: insert_output_task.shared(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ThreadEvent {
|
||||
StreamedCompletion,
|
||||
UsePendingTools,
|
||||
ToolFinished {
|
||||
#[allow(unused)]
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
},
|
||||
}
|
||||
|
||||
impl EventEmitter<ThreadEvent> for Thread {}
|
||||
|
||||
struct PendingCompletion {
|
||||
id: usize,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
/// The ID of the Assistant message in which the tool use was requested.
|
||||
pub assistant_message_id: MessageId,
|
||||
pub name: String,
|
||||
pub input: serde_json::Value,
|
||||
pub status: PendingToolUseStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PendingToolUseStatus {
|
||||
Idle,
|
||||
Running { _task: Shared<Task<()>> },
|
||||
Error(#[allow(unused)] String),
|
||||
}
|
||||
|
||||
impl PendingToolUseStatus {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Idle)
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,8 @@ path = "src/assistant_slash_command.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
extension.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
@@ -24,7 +22,6 @@ language_model.workspace = true
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
mod extension_slash_command;
|
||||
mod slash_command_registry;
|
||||
|
||||
pub use crate::extension_slash_command::*;
|
||||
pub use crate::slash_command_registry::*;
|
||||
use anyhow::Result;
|
||||
use futures::stream::{self, BoxStream};
|
||||
use futures::StreamExt;
|
||||
@@ -10,6 +7,7 @@ use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, Wind
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||
pub use language_model::Role;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use slash_command_registry::*;
|
||||
use std::{
|
||||
ops::Range,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
@@ -18,7 +16,6 @@ use workspace::{ui::IconName, Workspace};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
SlashCommandRegistry::default_global(cx);
|
||||
extension_slash_command::init(cx);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandRegistry, SlashCommandResult,
|
||||
};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let proxy = ExtensionHostProxy::default_global(cx);
|
||||
proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
|
||||
slash_command_registry: SlashCommandRegistry::global(cx),
|
||||
});
|
||||
}
|
||||
|
||||
struct SlashCommandRegistryProxy {
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
}
|
||||
|
||||
impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
|
||||
fn register_slash_command(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
command: extension::SlashCommand,
|
||||
) {
|
||||
self.slash_command_registry
|
||||
.register_command(ExtensionSlashCommand::new(extension, command), false)
|
||||
}
|
||||
}
|
||||
|
||||
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
|
||||
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);
|
||||
|
||||
#[async_trait]
|
||||
impl WorktreeDelegate for WorktreeDelegateAdapter {
|
||||
fn id(&self) -> u64 {
|
||||
self.0.worktree_id().to_proto()
|
||||
}
|
||||
|
||||
fn root_path(&self) -> String {
|
||||
self.0.worktree_root_path().to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
|
||||
self.0.read_text_file(path).await
|
||||
}
|
||||
|
||||
async fn which(&self, binary_name: String) -> Option<String> {
|
||||
self.0
|
||||
.which(binary_name.as_ref())
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
async fn shell_env(&self) -> Vec<(String, String)> {
|
||||
self.0.shell_env().await.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionSlashCommand {
|
||||
extension: Arc<dyn Extension>,
|
||||
command: extension::SlashCommand,
|
||||
}
|
||||
|
||||
impl ExtensionSlashCommand {
|
||||
pub fn new(extension: Arc<dyn Extension>, command: extension::SlashCommand) -> Self {
|
||||
Self { extension, command }
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for ExtensionSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
self.command.name.clone()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
self.command.description.clone()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.command.tooltip_text.clone()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
self.command.requires_argument
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let command = self.command.clone();
|
||||
let arguments = arguments.to_owned();
|
||||
cx.background_executor().spawn(async move {
|
||||
let completions = self
|
||||
.extension
|
||||
.complete_slash_command_argument(command, arguments)
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|completion| ArgumentCompletion {
|
||||
label: completion.label.into(),
|
||||
new_text: completion.new_text,
|
||||
replace_previous_arguments: false,
|
||||
after_completion: completion.run_command.into(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let command = self.command.clone();
|
||||
let arguments = arguments.to_owned();
|
||||
let output = cx.background_executor().spawn(async move {
|
||||
let delegate =
|
||||
delegate.map(|delegate| Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _);
|
||||
let output = self
|
||||
.extension
|
||||
.run_slash_command(command, arguments, delegate)
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(output)
|
||||
});
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let output = output.await?;
|
||||
Ok(SlashCommandOutput {
|
||||
text: output.text,
|
||||
sections: output
|
||||
.sections
|
||||
.into_iter()
|
||||
.map(|section| SlashCommandOutputSection {
|
||||
range: section.range,
|
||||
icon: IconName::Code,
|
||||
label: section.label.into(),
|
||||
metadata: None,
|
||||
})
|
||||
.collect(),
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.to_event_stream())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
mod tool_registry;
|
||||
mod tool_working_set;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -7,8 +6,7 @@ use anyhow::Result;
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub use crate::tool_registry::*;
|
||||
pub use crate::tool_working_set::*;
|
||||
pub use tool_registry::*;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
ToolRegistry::default_global(cx);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "assistant_tools"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant_tools.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
chrono.workspace = true
|
||||
gpui.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
workspace.workspace = true
|
||||
@@ -1,13 +0,0 @@
|
||||
mod now_tool;
|
||||
|
||||
use assistant_tool::ToolRegistry;
|
||||
use gpui::AppContext;
|
||||
|
||||
use crate::now_tool::NowTool;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
assistant_tool::init(cx);
|
||||
|
||||
let registry = ToolRegistry::global(cx);
|
||||
registry.register_tool(NowTool);
|
||||
}
|
||||
@@ -18,5 +18,5 @@ collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
gpui.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rodio = { version = "0.20.0", default-features = false, features = ["wav"] }
|
||||
rodio = { version = "0.19.0", default-features = false, features = ["wav"] }
|
||||
util.workspace = true
|
||||
|
||||
@@ -16,16 +16,21 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
log.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
menu.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
util.workspace = true
|
||||
which.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
mod update_notification;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{Client, TelemetrySettings};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use db::RELEASE_CHANNEL;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||
SemanticVersion, Task, WindowContext,
|
||||
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use paths::remote_servers_dir;
|
||||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use serde::Deserialize;
|
||||
use serde_derive::Serialize;
|
||||
use smol::{fs, io::AsyncReadExt};
|
||||
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use smol::{fs::File, process::Command};
|
||||
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||
use std::{
|
||||
env::{
|
||||
self,
|
||||
@@ -24,13 +32,24 @@ use std::{
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use update_notification::UpdateNotification;
|
||||
use util::ResultExt;
|
||||
use which::which;
|
||||
use workspace::notifications::NotificationId;
|
||||
use workspace::Workspace;
|
||||
|
||||
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
|
||||
const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes,]);
|
||||
actions!(
|
||||
auto_update,
|
||||
[
|
||||
Check,
|
||||
DismissErrorMessage,
|
||||
ViewReleaseNotes,
|
||||
ViewReleaseNotesLocally
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct UpdateRequestBody {
|
||||
@@ -127,6 +146,12 @@ struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
|
||||
|
||||
impl Global for GlobalAutoUpdate {}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ReleaseNotesBody {
|
||||
title: String,
|
||||
release_notes: String,
|
||||
}
|
||||
|
||||
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
||||
AutoUpdateSetting::register(cx);
|
||||
|
||||
@@ -136,6 +161,10 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
||||
workspace.register_action(|_, action, cx| {
|
||||
view_release_notes(action, cx);
|
||||
});
|
||||
|
||||
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
|
||||
view_release_notes_locally(workspace, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -235,6 +264,121 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
None
|
||||
}
|
||||
|
||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
|
||||
let url = match release_channel {
|
||||
ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
|
||||
ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(url) = url {
|
||||
cx.open_url(url);
|
||||
return;
|
||||
}
|
||||
|
||||
let version = AppVersion::global(cx).to_string();
|
||||
|
||||
let client = client::Client::global(cx).http_client();
|
||||
let url = client.build_url(&format!(
|
||||
"/api/release_notes/v2/{}/{}",
|
||||
release_channel.dev_name(),
|
||||
version
|
||||
));
|
||||
|
||||
let markdown = workspace
|
||||
.app_state()
|
||||
.languages
|
||||
.language_for_name("Markdown");
|
||||
|
||||
workspace
|
||||
.with_local_workspace(cx, move |_, cx| {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let markdown = markdown.await.log_err();
|
||||
let response = client.get(&url, Default::default(), true).await;
|
||||
let Some(mut response) = response.log_err() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await.ok();
|
||||
|
||||
let body: serde_json::Result<ReleaseNotesBody> =
|
||||
serde_json::from_slice(body.as_slice());
|
||||
|
||||
if let Ok(body) = body {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let buffer = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer("", markdown, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, body.release_notes)], None, cx)
|
||||
});
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let tab_description = SharedString::from(body.title.to_string());
|
||||
let editor = cx.new_view(|cx| {
|
||||
Editor::for_multibuffer(buffer, Some(project), true, cx)
|
||||
});
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
|
||||
MarkdownPreviewMode::Default,
|
||||
editor,
|
||||
workspace_handle,
|
||||
language_registry,
|
||||
Some(tab_description),
|
||||
cx,
|
||||
);
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(view.clone()),
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
|
||||
let updater = AutoUpdater::get(cx)?;
|
||||
let version = updater.read(cx).current_version;
|
||||
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
|
||||
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let should_show_notification = should_show_notification.await?;
|
||||
if should_show_notification {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
workspace.show_notification(
|
||||
NotificationId::unique::<UpdateNotification>(),
|
||||
cx,
|
||||
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
|
||||
);
|
||||
updater.update(cx, |updater, cx| {
|
||||
updater
|
||||
.set_should_show_update_notification(false, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl AutoUpdater {
|
||||
pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
|
||||
cx.default_global::<GlobalAutoUpdate>().0.clone()
|
||||
@@ -279,10 +423,6 @@ impl AutoUpdater {
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn current_version(&self) -> SemanticVersion {
|
||||
self.current_version
|
||||
}
|
||||
|
||||
pub fn status(&self) -> AutoUpdateStatus {
|
||||
self.status.clone()
|
||||
}
|
||||
@@ -506,7 +646,7 @@ impl AutoUpdater {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_should_show_update_notification(
|
||||
fn set_should_show_update_notification(
|
||||
&self,
|
||||
should_show: bool,
|
||||
cx: &AppContext,
|
||||
@@ -528,7 +668,7 @@ impl AutoUpdater {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
|
||||
fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
|
||||
cx.background_executor().spawn(async move {
|
||||
Ok(KEY_VALUE_STORE
|
||||
.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "auto_update_ui"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/auto_update_ui.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
client.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
menu.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,147 +0,0 @@
|
||||
mod update_notification;
|
||||
|
||||
use auto_update::AutoUpdater;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext};
|
||||
use http_client::HttpClient;
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use serde::Deserialize;
|
||||
use smol::io::AsyncReadExt;
|
||||
use util::ResultExt as _;
|
||||
use workspace::notifications::NotificationId;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::update_notification::UpdateNotification;
|
||||
|
||||
actions!(auto_update, [ViewReleaseNotesLocally]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
|
||||
view_release_notes_locally(workspace, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ReleaseNotesBody {
|
||||
title: String,
|
||||
release_notes: String,
|
||||
}
|
||||
|
||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
|
||||
let url = match release_channel {
|
||||
ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
|
||||
ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(url) = url {
|
||||
cx.open_url(url);
|
||||
return;
|
||||
}
|
||||
|
||||
let version = AppVersion::global(cx).to_string();
|
||||
|
||||
let client = client::Client::global(cx).http_client();
|
||||
let url = client.build_url(&format!(
|
||||
"/api/release_notes/v2/{}/{}",
|
||||
release_channel.dev_name(),
|
||||
version
|
||||
));
|
||||
|
||||
let markdown = workspace
|
||||
.app_state()
|
||||
.languages
|
||||
.language_for_name("Markdown");
|
||||
|
||||
workspace
|
||||
.with_local_workspace(cx, move |_, cx| {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let markdown = markdown.await.log_err();
|
||||
let response = client.get(&url, Default::default(), true).await;
|
||||
let Some(mut response) = response.log_err() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await.ok();
|
||||
|
||||
let body: serde_json::Result<ReleaseNotesBody> =
|
||||
serde_json::from_slice(body.as_slice());
|
||||
|
||||
if let Ok(body) = body {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let buffer = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer("", markdown, cx)
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, body.release_notes)], None, cx)
|
||||
});
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let tab_description = SharedString::from(body.title.to_string());
|
||||
let editor = cx.new_view(|cx| {
|
||||
Editor::for_multibuffer(buffer, Some(project), true, cx)
|
||||
});
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
|
||||
MarkdownPreviewMode::Default,
|
||||
editor,
|
||||
workspace_handle,
|
||||
language_registry,
|
||||
Some(tab_description),
|
||||
cx,
|
||||
);
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(view.clone()),
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
|
||||
let updater = AutoUpdater::get(cx)?;
|
||||
let version = updater.read(cx).current_version();
|
||||
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
|
||||
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let should_show_notification = should_show_notification.await?;
|
||||
if should_show_notification {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
workspace.show_notification(
|
||||
NotificationId::unique::<UpdateNotification>(),
|
||||
cx,
|
||||
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
|
||||
);
|
||||
updater.update(cx, |updater, cx| {
|
||||
updater
|
||||
.set_should_show_update_notification(false, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
|
||||
None
|
||||
}
|
||||
@@ -27,7 +27,6 @@ anyhow.workspace = true
|
||||
audio.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
@@ -18,11 +18,6 @@ use room::Event;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub use live_kit_client::play_remote_video_track;
|
||||
pub use live_kit_client::{
|
||||
track::RemoteVideoTrack, RemoteVideoTrackView, RemoteVideoTrackViewEvent,
|
||||
};
|
||||
pub use participant::ParticipantLocation;
|
||||
pub use room::Room;
|
||||
|
||||
@@ -31,10 +26,6 @@ struct GlobalActiveCall(Model<ActiveCall>);
|
||||
impl Global for GlobalActiveCall {}
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||
live_kit_client::init(
|
||||
cx.background_executor().dispatcher.clone(),
|
||||
cx.http_client(),
|
||||
);
|
||||
CallSettings::register(cx);
|
||||
|
||||
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
#![cfg_attr(target_os = "windows", allow(unused))]
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use client::{proto, ParticipantIndex, User};
|
||||
use client::ParticipantIndex;
|
||||
use client::{proto, User};
|
||||
use collections::HashMap;
|
||||
use gpui::WeakModel;
|
||||
use live_kit_client::AudioStream;
|
||||
pub use live_kit_client::Frame;
|
||||
pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
|
||||
use project::Project;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub use live_kit_client::id::TrackSid;
|
||||
pub use live_kit_client::track::{RemoteAudioTrack, RemoteVideoTrack};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ParticipantLocation {
|
||||
SharedProject { project_id: u64 },
|
||||
@@ -43,6 +39,7 @@ pub struct LocalParticipant {
|
||||
pub role: proto::ChannelRole,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteParticipant {
|
||||
pub user: Arc<User>,
|
||||
pub peer_id: proto::PeerId,
|
||||
@@ -52,17 +49,6 @@ pub struct RemoteParticipant {
|
||||
pub participant_index: ParticipantIndex,
|
||||
pub muted: bool,
|
||||
pub speaking: bool,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub video_tracks: HashMap<TrackSid, RemoteVideoTrack>,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub audio_tracks: HashMap<TrackSid, (RemoteAudioTrack, AudioStream)>,
|
||||
}
|
||||
|
||||
impl RemoteParticipant {
|
||||
pub fn has_video_tracks(&self) -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
return !self.video_tracks.is_empty();
|
||||
#[cfg(target_os = "windows")]
|
||||
return false;
|
||||
}
|
||||
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
client::init_settings(cx);
|
||||
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let clock = Arc::new(FakeSystemClock::default());
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
let client = Client::new(clock, http.clone(), cx);
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
|
||||
@@ -16,15 +16,11 @@ doctest = false
|
||||
name = "cli"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
no-bundled-uninstall = []
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
collections.workspace = true
|
||||
ipc-channel = "0.19"
|
||||
ipc-channel = "0.18"
|
||||
once_cell.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
@@ -33,7 +29,7 @@ serde.workspace = true
|
||||
util.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
exec.workspace = true
|
||||
fork.workspace = true
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
fn main() {
|
||||
if std::env::var("ZED_UPDATE_EXPLANATION").is_ok() {
|
||||
println!(r#"cargo:rustc-cfg=feature="no-bundled-uninstall""#);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
#![cfg_attr(
|
||||
any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
|
||||
allow(dead_code)
|
||||
)]
|
||||
#![cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
@@ -59,13 +56,6 @@ struct Args {
|
||||
/// Run zed in dev-server mode
|
||||
#[arg(long)]
|
||||
dev_server_token: Option<String>,
|
||||
/// Uninstall Zed from user system
|
||||
#[cfg(all(
|
||||
any(target_os = "linux", target_os = "macos"),
|
||||
not(feature = "no-bundled-uninstall")
|
||||
))]
|
||||
#[arg(long)]
|
||||
uninstall: bool,
|
||||
}
|
||||
|
||||
fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
|
||||
@@ -98,7 +88,7 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// 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();
|
||||
@@ -116,7 +106,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
let args = Args::parse();
|
||||
|
||||
#[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")?;
|
||||
@@ -126,29 +116,6 @@ fn main() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(target_os = "linux", target_os = "macos"),
|
||||
not(feature = "no-bundled-uninstall")
|
||||
))]
|
||||
if args.uninstall {
|
||||
static UNINSTALL_SCRIPT: &[u8] = include_bytes!("../../../script/uninstall.sh");
|
||||
|
||||
let tmp_dir = tempfile::tempdir()?;
|
||||
let script_path = tmp_dir.path().join("uninstall.sh");
|
||||
fs::write(&script_path, UNINSTALL_SCRIPT)?;
|
||||
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?;
|
||||
|
||||
let status = std::process::Command::new("sh")
|
||||
.arg(&script_path)
|
||||
.env("ZED_CHANNEL", &*release_channel::RELEASE_CHANNEL_NAME)
|
||||
.status()
|
||||
.context("Failed to execute uninstall script")?;
|
||||
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
|
||||
let (server, server_name) =
|
||||
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
|
||||
let url = format!("zed-cli://{server_name}");
|
||||
@@ -253,7 +220,7 @@ fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux {
|
||||
use std::{
|
||||
env,
|
||||
@@ -377,7 +344,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;
|
||||
@@ -548,7 +515,7 @@ mod mac_os {
|
||||
},
|
||||
LocalPath {
|
||||
executable: PathBuf,
|
||||
short_version_string: String,
|
||||
plist: InfoPlist,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -587,9 +554,17 @@ mod mac_os {
|
||||
}
|
||||
_ => {
|
||||
println!("Bundle path {bundle_path:?} has no *.app extension, attempting to locate a dev build");
|
||||
let plist_path = bundle_path
|
||||
.parent()
|
||||
.with_context(|| format!("Bundle path {bundle_path:?} has no parent"))?
|
||||
.join("WebRTC.framework/Resources/Info.plist");
|
||||
let plist =
|
||||
plist::from_file::<_, InfoPlist>(&plist_path).with_context(|| {
|
||||
format!("Reading dev bundle plist file at {plist_path:?}")
|
||||
})?;
|
||||
Ok(Bundle::LocalPath {
|
||||
executable: bundle_path,
|
||||
short_version_string: "test-dev-version".to_string(),
|
||||
plist,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -599,16 +574,9 @@ mod mac_os {
|
||||
impl InstalledApp for Bundle {
|
||||
fn zed_version_string(&self) -> String {
|
||||
let is_dev = matches!(self, Self::LocalPath { .. });
|
||||
let version = match self {
|
||||
Bundle::App { plist, .. } => &plist.bundle_short_version_string,
|
||||
Bundle::LocalPath {
|
||||
short_version_string,
|
||||
..
|
||||
} => &short_version_string,
|
||||
};
|
||||
format!(
|
||||
"Zed {}{} – {}",
|
||||
version,
|
||||
self.plist().bundle_short_version_string,
|
||||
if is_dev { " (dev)" } else { "" },
|
||||
self.path().display(),
|
||||
)
|
||||
@@ -690,6 +658,13 @@ mod mac_os {
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
fn plist(&self) -> &InfoPlist {
|
||||
match self {
|
||||
Self::App { plist, .. } => plist,
|
||||
Self::LocalPath { plist, .. } => plist,
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
match self {
|
||||
Self::App { app_bundle, .. } => app_bundle,
|
||||
|
||||
@@ -42,6 +42,7 @@ serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
sha2.workspace = true
|
||||
smol.workspace = true
|
||||
sysinfo.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
text.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
@@ -889,7 +889,7 @@ impl Client {
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let executor = cx.background_executor();
|
||||
log::debug!("add connection to peer");
|
||||
log::info!("add connection to peer");
|
||||
let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, {
|
||||
let executor = executor.clone();
|
||||
move |duration| executor.timer(duration)
|
||||
@@ -897,12 +897,12 @@ impl Client {
|
||||
let handle_io = executor.spawn(handle_io);
|
||||
|
||||
let peer_id = async {
|
||||
log::debug!("waiting for server hello");
|
||||
log::info!("waiting for server hello");
|
||||
let message = incoming
|
||||
.next()
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("no hello message received"))?;
|
||||
log::debug!("got server hello");
|
||||
log::info!("got server hello");
|
||||
let hello_message_type_name = message.payload_type_name().to_string();
|
||||
let hello = message
|
||||
.into_any()
|
||||
@@ -928,7 +928,7 @@ impl Client {
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
log::info!(
|
||||
"set status to connected (connection id: {:?}, peer id: {:?})",
|
||||
connection_id,
|
||||
peer_id
|
||||
@@ -1067,8 +1067,6 @@ impl Client {
|
||||
let proxy = http.proxy().cloned();
|
||||
let credentials = credentials.clone();
|
||||
let rpc_url = self.rpc_url(http, release_channel);
|
||||
let system_id = self.telemetry.system_id();
|
||||
let metrics_id = self.telemetry.metrics_id();
|
||||
cx.background_executor().spawn(async move {
|
||||
use HttpOrHttps::*;
|
||||
|
||||
@@ -1120,12 +1118,6 @@ impl Client {
|
||||
"x-zed-release-channel",
|
||||
HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
|
||||
);
|
||||
if let Some(system_id) = system_id {
|
||||
request_headers.insert("x-zed-system-id", HeaderValue::from_str(&system_id)?);
|
||||
}
|
||||
if let Some(metrics_id) = metrics_id {
|
||||
request_headers.insert("x-zed-metrics-id", HeaderValue::from_str(&metrics_id)?);
|
||||
}
|
||||
|
||||
match url_scheme {
|
||||
Https => {
|
||||
@@ -1788,7 +1780,7 @@ mod tests {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::new()),
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
@@ -1829,7 +1821,7 @@ mod tests {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::new()),
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
@@ -1908,7 +1900,7 @@ mod tests {
|
||||
let dropped_auth_count = Arc::new(Mutex::new(0));
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::new()),
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
@@ -1951,7 +1943,7 @@ mod tests {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::new()),
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
@@ -2011,7 +2003,7 @@ mod tests {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::new()),
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
@@ -2046,7 +2038,7 @@ mod tests {
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::new()),
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ mod event_coalescer;
|
||||
|
||||
use crate::{ChannelId, TelemetrySettings};
|
||||
use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use clock::SystemClock;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::Future;
|
||||
@@ -14,11 +15,12 @@ use settings::{Settings, SettingsStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::time::Instant;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||
use telemetry_events::{
|
||||
ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event,
|
||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, ReplEvent, SettingEvent,
|
||||
ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event,
|
||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
|
||||
SettingEvent,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||
@@ -44,7 +46,7 @@ struct TelemetryState {
|
||||
flush_events_task: Option<Task<()>>,
|
||||
log_file: Option<File>,
|
||||
is_staff: Option<bool>,
|
||||
first_event_date_time: Option<Instant>,
|
||||
first_event_date_time: Option<DateTime<Utc>>,
|
||||
event_coalescer: EventCoalescer,
|
||||
max_queue_size: usize,
|
||||
worktree_id_map: WorktreeIdMap,
|
||||
@@ -98,7 +100,7 @@ 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())
|
||||
}
|
||||
@@ -127,7 +129,7 @@ pub fn os_version() -> String {
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::path::Path;
|
||||
|
||||
@@ -224,8 +226,6 @@ impl Telemetry {
|
||||
cx.background_executor()
|
||||
.spawn({
|
||||
let state = state.clone();
|
||||
let os_version = os_version();
|
||||
state.lock().os_version = Some(os_version.clone());
|
||||
async move {
|
||||
if let Some(tempfile) = File::create(Self::log_file_path()).log_err() {
|
||||
state.lock().log_file = Some(tempfile);
|
||||
@@ -293,13 +293,55 @@ impl Telemetry {
|
||||
state.session_id = Some(session_id);
|
||||
state.app_version = release_channel::AppVersion::global(cx).to_string();
|
||||
state.os_name = os_name();
|
||||
|
||||
drop(state);
|
||||
|
||||
let this = self.clone();
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
let mut system = System::new_with_specifics(
|
||||
RefreshKind::new().with_cpu(CpuRefreshKind::everything()),
|
||||
);
|
||||
|
||||
let refresh_kind = ProcessRefreshKind::new().with_cpu().with_memory();
|
||||
let current_process = Pid::from_u32(std::process::id());
|
||||
system.refresh_processes_specifics(
|
||||
sysinfo::ProcessesToUpdate::Some(&[current_process]),
|
||||
refresh_kind,
|
||||
);
|
||||
|
||||
// Waiting some amount of time before the first query is important to get a reasonable value
|
||||
// https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
|
||||
const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(4 * 60);
|
||||
|
||||
loop {
|
||||
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
|
||||
|
||||
let current_process = Pid::from_u32(std::process::id());
|
||||
system.refresh_processes_specifics(
|
||||
sysinfo::ProcessesToUpdate::Some(&[current_process]),
|
||||
refresh_kind,
|
||||
);
|
||||
let Some(process) = system.process(current_process) else {
|
||||
log::error!(
|
||||
"Failed to find own process {current_process:?} in system process table"
|
||||
);
|
||||
// TODO: Fire an error telemetry event
|
||||
return;
|
||||
};
|
||||
|
||||
this.report_memory_event(process.memory(), process.virtual_memory());
|
||||
this.report_cpu_event(process.cpu_usage(), system.cpus().len() as u32);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn metrics_enabled(self: &Arc<Self>) -> bool {
|
||||
let state = self.state.lock();
|
||||
let enabled = state.settings.metrics;
|
||||
drop(state);
|
||||
enabled
|
||||
return enabled;
|
||||
}
|
||||
|
||||
pub fn set_authenticated_user_info(
|
||||
@@ -374,6 +416,28 @@ impl Telemetry {
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_cpu_event(self: &Arc<Self>, usage_as_percentage: f32, core_count: u32) {
|
||||
let event = Event::Cpu(CpuEvent {
|
||||
usage_as_percentage,
|
||||
core_count,
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_memory_event(
|
||||
self: &Arc<Self>,
|
||||
memory_in_bytes: u64,
|
||||
virtual_memory_in_bytes: u64,
|
||||
) {
|
||||
let event = Event::Memory(MemoryEvent {
|
||||
memory_in_bytes,
|
||||
virtual_memory_in_bytes,
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_app_event(self: &Arc<Self>, operation: String) -> Event {
|
||||
let event = Event::App(AppEvent { operation });
|
||||
|
||||
@@ -405,10 +469,7 @@ impl Telemetry {
|
||||
|
||||
if let Some((start, end, environment)) = period_data {
|
||||
let event = Event::Edit(EditEvent {
|
||||
duration: end
|
||||
.saturating_duration_since(start)
|
||||
.min(Duration::from_secs(60 * 60 * 24))
|
||||
.as_millis() as i64,
|
||||
duration: end.timestamp_millis() - start.timestamp_millis(),
|
||||
environment: environment.to_string(),
|
||||
is_via_ssh,
|
||||
});
|
||||
@@ -506,10 +567,9 @@ impl Telemetry {
|
||||
let date_time = self.clock.utc_now();
|
||||
|
||||
let milliseconds_since_first_event = match state.first_event_date_time {
|
||||
Some(first_event_date_time) => date_time
|
||||
.saturating_duration_since(first_event_date_time)
|
||||
.min(Duration::from_secs(60 * 60 * 24))
|
||||
.as_millis() as i64,
|
||||
Some(first_event_date_time) => {
|
||||
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
|
||||
}
|
||||
None => {
|
||||
state.first_event_date_time = Some(date_time);
|
||||
0
|
||||
@@ -533,10 +593,6 @@ impl Telemetry {
|
||||
self.state.lock().metrics_id.clone()
|
||||
}
|
||||
|
||||
pub fn system_id(self: &Arc<Self>) -> Option<Arc<str>> {
|
||||
self.state.lock().system_id.clone()
|
||||
}
|
||||
|
||||
pub fn installation_id(self: &Arc<Self>) -> Option<Arc<str>> {
|
||||
self.state.lock().installation_id.clone()
|
||||
}
|
||||
@@ -646,6 +702,7 @@ pub fn calculate_json_checksum(json: &impl AsRef<[u8]>) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::TimeZone;
|
||||
use clock::FakeSystemClock;
|
||||
use gpui::TestAppContext;
|
||||
use http_client::FakeHttpClient;
|
||||
@@ -653,7 +710,9 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||
));
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let system_id = Some("system_id".to_string());
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
@@ -684,7 +743,7 @@ mod tests {
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
clock.advance(Duration::from_millis(100));
|
||||
clock.advance(chrono::Duration::milliseconds(100));
|
||||
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
assert_eq!(
|
||||
@@ -700,7 +759,7 @@ mod tests {
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
clock.advance(Duration::from_millis(100));
|
||||
clock.advance(chrono::Duration::milliseconds(100));
|
||||
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
assert_eq!(
|
||||
@@ -716,7 +775,7 @@ mod tests {
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
clock.advance(Duration::from_millis(100));
|
||||
clock.advance(chrono::Duration::milliseconds(100));
|
||||
|
||||
// Adding a 4th event should cause a flush
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
@@ -737,7 +796,9 @@ mod tests {
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||
));
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let system_id = Some("system_id".to_string());
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use clock::SystemClock;
|
||||
|
||||
const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20);
|
||||
@@ -9,8 +10,8 @@ const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct PeriodData {
|
||||
environment: &'static str,
|
||||
start: Instant,
|
||||
end: Option<Instant>,
|
||||
start: DateTime<Utc>,
|
||||
end: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
pub struct EventCoalescer {
|
||||
@@ -26,8 +27,9 @@ impl EventCoalescer {
|
||||
pub fn log_event(
|
||||
&mut self,
|
||||
environment: &'static str,
|
||||
) -> Option<(Instant, Instant, &'static str)> {
|
||||
) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> {
|
||||
let log_time = self.clock.utc_now();
|
||||
let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap();
|
||||
|
||||
let Some(state) = &mut self.state else {
|
||||
self.state = Some(PeriodData {
|
||||
@@ -41,7 +43,7 @@ impl EventCoalescer {
|
||||
let period_end = state
|
||||
.end
|
||||
.unwrap_or(state.start + SIMULATED_DURATION_FOR_SINGLE_EVENT);
|
||||
let within_timeout = log_time - period_end < COALESCE_TIMEOUT;
|
||||
let within_timeout = log_time - period_end < coalesce_timeout;
|
||||
let environment_is_same = state.environment == environment;
|
||||
let should_coaelesce = !within_timeout || !environment_is_same;
|
||||
|
||||
@@ -68,13 +70,16 @@ impl EventCoalescer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::TimeZone;
|
||||
use clock::FakeSystemClock;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_same_context_exceeding_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
@@ -93,7 +98,7 @@ mod tests {
|
||||
})
|
||||
);
|
||||
|
||||
let within_timeout_adjustment = COALESCE_TIMEOUT / 2;
|
||||
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
|
||||
|
||||
// Ensure that many calls within the timeout don't start a new period
|
||||
for _ in 0..100 {
|
||||
@@ -113,7 +118,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let period_end = clock.utc_now();
|
||||
let exceed_timeout_adjustment = COALESCE_TIMEOUT * 2;
|
||||
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
|
||||
// Logging an event exceeding the timeout should start a new period
|
||||
clock.advance(exceed_timeout_adjustment);
|
||||
let new_period_start = clock.utc_now();
|
||||
@@ -132,7 +137,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_different_environment_under_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
@@ -151,7 +158,7 @@ mod tests {
|
||||
})
|
||||
);
|
||||
|
||||
let within_timeout_adjustment = COALESCE_TIMEOUT / 2;
|
||||
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
|
||||
clock.advance(within_timeout_adjustment);
|
||||
let period_end = clock.utc_now();
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
@@ -186,7 +193,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_switching_environment_while_within_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
@@ -205,7 +214,7 @@ mod tests {
|
||||
})
|
||||
);
|
||||
|
||||
let within_timeout_adjustment = COALESCE_TIMEOUT / 2;
|
||||
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
|
||||
clock.advance(within_timeout_adjustment);
|
||||
let period_end = clock.utc_now();
|
||||
let environment_2 = "environment_2";
|
||||
@@ -231,7 +240,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_switching_environment_while_exceeding_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
@@ -250,7 +261,7 @@ mod tests {
|
||||
})
|
||||
);
|
||||
|
||||
let exceed_timeout_adjustment = COALESCE_TIMEOUT * 2;
|
||||
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
|
||||
clock.advance(exceed_timeout_adjustment);
|
||||
let period_end = clock.utc_now();
|
||||
let environment_2 = "environment_2";
|
||||
|
||||
@@ -16,6 +16,7 @@ doctest = false
|
||||
test-support = ["dep:parking_lot"]
|
||||
|
||||
[dependencies]
|
||||
chrono.workspace = true
|
||||
parking_lot = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
smallvec.workspace = true
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use std::time::Instant;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub trait SystemClock: Send + Sync {
|
||||
/// Returns the current date and time in UTC.
|
||||
fn utc_now(&self) -> Instant;
|
||||
fn utc_now(&self) -> DateTime<Utc>;
|
||||
}
|
||||
|
||||
pub struct RealSystemClock;
|
||||
|
||||
impl SystemClock for RealSystemClock {
|
||||
fn utc_now(&self) -> Instant {
|
||||
Instant::now()
|
||||
fn utc_now(&self) -> DateTime<Utc> {
|
||||
Utc::now()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeSystemClockState {
|
||||
now: Instant,
|
||||
now: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -24,30 +24,36 @@ pub struct FakeSystemClock {
|
||||
state: parking_lot::Mutex<FakeSystemClockState>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl Default for FakeSystemClock {
|
||||
fn default() -> Self {
|
||||
Self::new(Utc::now())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FakeSystemClock {
|
||||
pub fn new() -> Self {
|
||||
let state = FakeSystemClockState {
|
||||
now: Instant::now(),
|
||||
};
|
||||
pub fn new(now: DateTime<Utc>) -> Self {
|
||||
let state = FakeSystemClockState { now };
|
||||
|
||||
Self {
|
||||
state: parking_lot::Mutex::new(state),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_now(&self, now: Instant) {
|
||||
pub fn set_now(&self, now: DateTime<Utc>) {
|
||||
self.state.lock().now = now;
|
||||
}
|
||||
|
||||
pub fn advance(&self, duration: std::time::Duration) {
|
||||
/// Advances the [`FakeSystemClock`] by the specified [`Duration`](chrono::Duration).
|
||||
pub fn advance(&self, duration: chrono::Duration) {
|
||||
self.state.lock().now += duration;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl SystemClock for FakeSystemClock {
|
||||
fn utc_now(&self) -> Instant {
|
||||
fn utc_now(&self) -> DateTime<Utc> {
|
||||
self.state.lock().now
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ async-stripe.workspace = true
|
||||
async-tungstenite.workspace = true
|
||||
aws-config = { version = "1.1.5" }
|
||||
aws-sdk-s3 = { version = "1.15.0" }
|
||||
aws-sdk-kinesis = "1.51.0"
|
||||
axum = { version = "0.6", features = ["json", "headers", "ws"] }
|
||||
axum-extra = { version = "0.4", features = ["erased-json"] }
|
||||
base64.workspace = true
|
||||
@@ -79,8 +78,6 @@ uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assistant = { workspace = true, features = ["test-support"] }
|
||||
assistant_tool.workspace = true
|
||||
context_server.workspace = true
|
||||
async-trait.workspace = true
|
||||
audio.workspace = true
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
@@ -91,7 +88,6 @@ collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
extension.workspace = true
|
||||
file_finder.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
git = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -174,31 +174,6 @@ spec:
|
||||
secretKeyRef:
|
||||
name: blob-store
|
||||
key: bucket
|
||||
- name: KINESIS_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: kinesis
|
||||
key: access_key
|
||||
- name: KINESIS_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: kinesis
|
||||
key: secret_key
|
||||
- name: KINESIS_STREAM
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: kinesis
|
||||
key: stream
|
||||
- name: KINESIS_REGION
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: kinesis
|
||||
key: region
|
||||
- name: BLOB_STORE_BUCKET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: blob-store
|
||||
key: bucket
|
||||
- name: CLICKHOUSE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
@@ -61,39 +61,6 @@ impl std::fmt::Display for CloudflareIpCountryHeader {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemIdHeader(String);
|
||||
|
||||
impl Header for SystemIdHeader {
|
||||
fn name() -> &'static HeaderName {
|
||||
static SYSTEM_ID_HEADER: OnceLock<HeaderName> = OnceLock::new();
|
||||
SYSTEM_ID_HEADER.get_or_init(|| HeaderName::from_static("x-zed-system-id"))
|
||||
}
|
||||
|
||||
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
I: Iterator<Item = &'i axum::http::HeaderValue>,
|
||||
{
|
||||
let system_id = values
|
||||
.next()
|
||||
.ok_or_else(axum::headers::Error::invalid)?
|
||||
.to_str()
|
||||
.map_err(|_| axum::headers::Error::invalid())?;
|
||||
|
||||
Ok(Self(system_id.to_string()))
|
||||
}
|
||||
|
||||
fn encode<E: Extend<axum::http::HeaderValue>>(&self, _values: &mut E) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SystemIdHeader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routes(rpc_server: Arc<rpc::Server>) -> Router<(), Body> {
|
||||
Router::new()
|
||||
.route("/user", get(get_authenticated_user))
|
||||
|
||||
@@ -11,11 +11,9 @@ use axum::{
|
||||
routing::post,
|
||||
Extension, Router, TypedHeader,
|
||||
};
|
||||
use chrono::Duration;
|
||||
use rpc::ExtensionMetadata;
|
||||
use semantic_version::SemanticVersion;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde_json::json;
|
||||
use serde::{Serialize, Serializer};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use telemetry_events::{
|
||||
@@ -23,7 +21,6 @@ use telemetry_events::{
|
||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, Panic,
|
||||
ReplEvent, SettingEvent,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
const CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
|
||||
@@ -391,6 +388,13 @@ pub async fn post_events(
|
||||
country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
|
||||
body: Bytes,
|
||||
) -> Result<()> {
|
||||
let Some(clickhouse_client) = app.clickhouse_client.clone() else {
|
||||
Err(Error::http(
|
||||
StatusCode::NOT_IMPLEMENTED,
|
||||
"not supported".into(),
|
||||
))?
|
||||
};
|
||||
|
||||
let Some(expected) = calculate_json_checksum(app.clone(), &body) else {
|
||||
return Err(Error::http(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
@@ -412,34 +416,6 @@ pub async fn post_events(
|
||||
};
|
||||
let country_code = country_code_header.map(|h| h.to_string());
|
||||
|
||||
let first_event_at = chrono::Utc::now()
|
||||
- chrono::Duration::milliseconds(last_event.milliseconds_since_first_event);
|
||||
|
||||
if let Some(kinesis_client) = app.kinesis_client.clone() {
|
||||
if let Some(stream) = app.config.kinesis_stream.clone() {
|
||||
let mut request = kinesis_client.put_records().stream_name(stream);
|
||||
for row in for_snowflake(request_body.clone(), first_event_at, country_code.clone()) {
|
||||
if let Some(data) = serde_json::to_vec(&row).log_err() {
|
||||
request = request.records(
|
||||
aws_sdk_kinesis::types::PutRecordsRequestEntry::builder()
|
||||
.partition_key(request_body.system_id.clone().unwrap_or_default())
|
||||
.data(data.into())
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
request.send().await.log_err();
|
||||
}
|
||||
};
|
||||
|
||||
let Some(clickhouse_client) = app.clickhouse_client.clone() else {
|
||||
Err(Error::http(
|
||||
StatusCode::NOT_IMPLEMENTED,
|
||||
"not supported".into(),
|
||||
))?
|
||||
};
|
||||
|
||||
let first_event_at = chrono::Utc::now()
|
||||
- chrono::Duration::milliseconds(last_event.milliseconds_since_first_event);
|
||||
|
||||
@@ -483,7 +459,20 @@ pub async fn post_events(
|
||||
checksum_matched,
|
||||
))
|
||||
}
|
||||
Event::Cpu(_) | Event::Memory(_) => continue,
|
||||
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
@@ -934,7 +923,6 @@ pub struct CpuEventRow {
|
||||
}
|
||||
|
||||
impl CpuEventRow {
|
||||
#[allow(unused)]
|
||||
fn from_event(
|
||||
event: CpuEvent,
|
||||
wrapper: &EventWrapper,
|
||||
@@ -989,7 +977,6 @@ pub struct MemoryEventRow {
|
||||
}
|
||||
|
||||
impl MemoryEventRow {
|
||||
#[allow(unused)]
|
||||
fn from_event(
|
||||
event: MemoryEvent,
|
||||
wrapper: &EventWrapper,
|
||||
@@ -1377,250 +1364,3 @@ pub fn calculate_json_checksum(app: Arc<AppState>, json: &impl AsRef<[u8]>) -> O
|
||||
summer.update(checksum_seed);
|
||||
Some(summer.finalize().into_iter().collect())
|
||||
}
|
||||
|
||||
fn for_snowflake(
|
||||
body: EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
country_code: Option<String>,
|
||||
) -> impl Iterator<Item = SnowflakeRow> {
|
||||
body.events.into_iter().flat_map(move |event| {
|
||||
let timestamp =
|
||||
first_event_at + Duration::milliseconds(event.milliseconds_since_first_event);
|
||||
let (event_type, mut event_properties) = match &event.event {
|
||||
Event::Editor(e) => (
|
||||
match e.operation.as_str() {
|
||||
"open" => "Editor Opened".to_string(),
|
||||
"save" => "Editor Saved".to_string(),
|
||||
_ => format!("Unknown Editor Event: {}", e.operation),
|
||||
},
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::InlineCompletion(e) => (
|
||||
format!(
|
||||
"Inline Completion {}",
|
||||
if e.suggestion_accepted {
|
||||
"Accepted"
|
||||
} else {
|
||||
"Discarded"
|
||||
}
|
||||
),
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::Call(e) => {
|
||||
let event_type = match e.operation.trim() {
|
||||
"unshare project" => "Project Unshared".to_string(),
|
||||
"open channel notes" => "Channel Notes Opened".to_string(),
|
||||
"share project" => "Project Shared".to_string(),
|
||||
"join channel" => "Channel Joined".to_string(),
|
||||
"hang up" => "Call Ended".to_string(),
|
||||
"accept incoming" => "Incoming Call Accepted".to_string(),
|
||||
"invite" => "Participant Invited".to_string(),
|
||||
"disable microphone" => "Microphone Disabled".to_string(),
|
||||
"enable microphone" => "Microphone Enabled".to_string(),
|
||||
"enable screen share" => "Screen Share Enabled".to_string(),
|
||||
"disable screen share" => "Screen Share Disabled".to_string(),
|
||||
"decline incoming" => "Incoming Call Declined".to_string(),
|
||||
_ => format!("Unknown Call Event: {}", e.operation),
|
||||
};
|
||||
|
||||
(event_type, serde_json::to_value(e).unwrap())
|
||||
}
|
||||
Event::Assistant(e) => (
|
||||
match e.phase {
|
||||
telemetry_events::AssistantPhase::Response => "Assistant Responded".to_string(),
|
||||
telemetry_events::AssistantPhase::Invoked => "Assistant Invoked".to_string(),
|
||||
telemetry_events::AssistantPhase::Accepted => {
|
||||
"Assistant Response Accepted".to_string()
|
||||
}
|
||||
telemetry_events::AssistantPhase::Rejected => {
|
||||
"Assistant Response Rejected".to_string()
|
||||
}
|
||||
},
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::Cpu(_) | Event::Memory(_) => return None,
|
||||
Event::App(e) => {
|
||||
let mut properties = json!({});
|
||||
let event_type = match e.operation.trim() {
|
||||
// App
|
||||
"open" => "App Opened".to_string(),
|
||||
"first open" => "App First Opened".to_string(),
|
||||
"first open for release channel" => {
|
||||
"App First Opened For Release Channel".to_string()
|
||||
}
|
||||
"close" => "App Closed".to_string(),
|
||||
|
||||
// Project
|
||||
"open project" => "Project Opened".to_string(),
|
||||
"open node project" => {
|
||||
properties["project_type"] = json!("node");
|
||||
"Project Opened".to_string()
|
||||
}
|
||||
"open pnpm project" => {
|
||||
properties["project_type"] = json!("pnpm");
|
||||
"Project Opened".to_string()
|
||||
}
|
||||
"open yarn project" => {
|
||||
properties["project_type"] = json!("yarn");
|
||||
"Project Opened".to_string()
|
||||
}
|
||||
|
||||
// SSH
|
||||
"create ssh server" => "SSH Server Created".to_string(),
|
||||
"create ssh project" => "SSH Project Created".to_string(),
|
||||
"open ssh project" => "SSH Project Opened".to_string(),
|
||||
|
||||
// Welcome Page
|
||||
"welcome page: change keymap" => "Welcome Keymap Changed".to_string(),
|
||||
"welcome page: change theme" => "Welcome Theme Changed".to_string(),
|
||||
"welcome page: close" => "Welcome Page Closed".to_string(),
|
||||
"welcome page: edit settings" => "Welcome Settings Edited".to_string(),
|
||||
"welcome page: install cli" => "Welcome CLI Installed".to_string(),
|
||||
"welcome page: open" => "Welcome Page Opened".to_string(),
|
||||
"welcome page: open extensions" => "Welcome Extensions Page Opened".to_string(),
|
||||
"welcome page: sign in to copilot" => "Welcome Copilot Signed In".to_string(),
|
||||
"welcome page: toggle diagnostic telemetry" => {
|
||||
"Welcome Diagnostic Telemetry Toggled".to_string()
|
||||
}
|
||||
"welcome page: toggle metric telemetry" => {
|
||||
"Welcome Metric Telemetry Toggled".to_string()
|
||||
}
|
||||
"welcome page: toggle vim" => "Welcome Vim Mode Toggled".to_string(),
|
||||
"welcome page: view docs" => "Welcome Documentation Viewed".to_string(),
|
||||
|
||||
// Extensions
|
||||
"extensions page: open" => "Extensions Page Opened".to_string(),
|
||||
"extensions: install extension" => "Extension Installed".to_string(),
|
||||
"extensions: uninstall extension" => "Extension Uninstalled".to_string(),
|
||||
|
||||
// Misc
|
||||
"markdown preview: open" => "Markdown Preview Opened".to_string(),
|
||||
"project diagnostics: open" => "Project Diagnostics Opened".to_string(),
|
||||
"project search: open" => "Project Search Opened".to_string(),
|
||||
"repl sessions: open" => "REPL Session Started".to_string(),
|
||||
|
||||
// Feature Upsell
|
||||
"feature upsell: toggle vim" => {
|
||||
properties["source"] = json!("Feature Upsell");
|
||||
"Vim Mode Toggled".to_string()
|
||||
}
|
||||
_ => e
|
||||
.operation
|
||||
.strip_prefix("feature upsell: viewed docs (")
|
||||
.and_then(|s| s.strip_suffix(')'))
|
||||
.map_or_else(
|
||||
|| format!("Unknown App Event: {}", e.operation),
|
||||
|docs_url| {
|
||||
properties["url"] = json!(docs_url);
|
||||
properties["source"] = json!("Feature Upsell");
|
||||
"Documentation Viewed".to_string()
|
||||
},
|
||||
),
|
||||
};
|
||||
(event_type, properties)
|
||||
}
|
||||
Event::Setting(e) => (
|
||||
"Settings Changed".to_string(),
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::Extension(e) => (
|
||||
"Extension Loaded".to_string(),
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::Edit(e) => (
|
||||
"Editor Edited".to_string(),
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::Action(e) => (
|
||||
"Action Invoked".to_string(),
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
Event::Repl(e) => (
|
||||
"Kernel Status Changed".to_string(),
|
||||
serde_json::to_value(e).unwrap(),
|
||||
),
|
||||
};
|
||||
|
||||
if let serde_json::Value::Object(ref mut map) = event_properties {
|
||||
map.insert("app_version".to_string(), body.app_version.clone().into());
|
||||
map.insert("os_name".to_string(), body.os_name.clone().into());
|
||||
map.insert("os_version".to_string(), body.os_version.clone().into());
|
||||
map.insert("architecture".to_string(), body.architecture.clone().into());
|
||||
map.insert(
|
||||
"release_channel".to_string(),
|
||||
body.release_channel.clone().into(),
|
||||
);
|
||||
map.insert("signed_in".to_string(), event.signed_in.into());
|
||||
if let Some(country_code) = country_code.as_ref() {
|
||||
map.insert("country".to_string(), country_code.clone().into());
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: most amplitude user properties are read out of our event_properties
|
||||
// dictionary. See https://app.amplitude.com/data/zed/Zed/sources/detail/production/falcon%3A159998
|
||||
// for how that is configured.
|
||||
let user_properties = Some(serde_json::json!({
|
||||
"is_staff": body.is_staff,
|
||||
}));
|
||||
|
||||
Some(SnowflakeRow {
|
||||
time: timestamp,
|
||||
user_id: body.metrics_id.clone(),
|
||||
device_id: body.system_id.clone(),
|
||||
event_type,
|
||||
event_properties,
|
||||
user_properties,
|
||||
insert_id: Some(Uuid::new_v4().to_string()),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SnowflakeRow {
|
||||
pub time: chrono::DateTime<chrono::Utc>,
|
||||
pub user_id: Option<String>,
|
||||
pub device_id: Option<String>,
|
||||
pub event_type: String,
|
||||
pub event_properties: serde_json::Value,
|
||||
pub user_properties: Option<serde_json::Value>,
|
||||
pub insert_id: Option<String>,
|
||||
}
|
||||
|
||||
impl SnowflakeRow {
|
||||
pub fn new(
|
||||
event_type: impl Into<String>,
|
||||
metrics_id: Option<Uuid>,
|
||||
is_staff: bool,
|
||||
system_id: Option<String>,
|
||||
event_properties: serde_json::Value,
|
||||
) -> Self {
|
||||
Self {
|
||||
time: chrono::Utc::now(),
|
||||
event_type: event_type.into(),
|
||||
device_id: system_id,
|
||||
user_id: metrics_id.map(|id| id.to_string()),
|
||||
insert_id: Some(uuid::Uuid::new_v4().to_string()),
|
||||
event_properties,
|
||||
user_properties: Some(json!({"is_staff": is_staff})),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn write(
|
||||
self,
|
||||
client: &Option<aws_sdk_kinesis::Client>,
|
||||
stream: &Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some((client, stream)) = client.as_ref().zip(stream.as_ref()) else {
|
||||
return Ok(());
|
||||
};
|
||||
let row = serde_json::to_vec(&self)?;
|
||||
client
|
||||
.put_record()
|
||||
.stream_name(stream)
|
||||
.partition_key(&self.user_id.unwrap_or_default())
|
||||
.data(row.into())
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use serde::Serialize;
|
||||
|
||||
/// A number of cents.
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -14,7 +12,6 @@ use serde::Serialize;
|
||||
derive_more::AddAssign,
|
||||
derive_more::Sub,
|
||||
derive_more::SubAssign,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct Cents(pub u32);
|
||||
|
||||
|
||||
@@ -170,10 +170,6 @@ pub struct Config {
|
||||
pub blob_store_access_key: Option<String>,
|
||||
pub blob_store_secret_key: Option<String>,
|
||||
pub blob_store_bucket: Option<String>,
|
||||
pub kinesis_region: Option<String>,
|
||||
pub kinesis_stream: Option<String>,
|
||||
pub kinesis_access_key: Option<String>,
|
||||
pub kinesis_secret_key: Option<String>,
|
||||
pub zed_environment: Arc<str>,
|
||||
pub openai_api_key: Option<Arc<str>>,
|
||||
pub google_ai_api_key: Option<Arc<str>>,
|
||||
@@ -242,10 +238,6 @@ impl Config {
|
||||
stripe_api_key: None,
|
||||
supermaven_admin_api_key: None,
|
||||
user_backfiller_github_access_token: None,
|
||||
kinesis_region: None,
|
||||
kinesis_access_key: None,
|
||||
kinesis_secret_key: None,
|
||||
kinesis_stream: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +276,6 @@ pub struct AppState {
|
||||
pub rate_limiter: Arc<RateLimiter>,
|
||||
pub executor: Executor,
|
||||
pub clickhouse_client: Option<::clickhouse::Client>,
|
||||
pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
@@ -341,11 +332,6 @@ impl AppState {
|
||||
.clickhouse_url
|
||||
.as_ref()
|
||||
.and_then(|_| build_clickhouse_client(&config).log_err()),
|
||||
kinesis_client: if config.kinesis_access_key.is_some() {
|
||||
build_kinesis_client(&config).await.log_err()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
config,
|
||||
};
|
||||
Ok(Arc::new(this))
|
||||
@@ -395,35 +381,6 @@ async fn build_blob_store_client(config: &Config) -> anyhow::Result<aws_sdk_s3::
|
||||
Ok(aws_sdk_s3::Client::new(&s3_config))
|
||||
}
|
||||
|
||||
async fn build_kinesis_client(config: &Config) -> anyhow::Result<aws_sdk_kinesis::Client> {
|
||||
let keys = aws_sdk_s3::config::Credentials::new(
|
||||
config
|
||||
.kinesis_access_key
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("missing kinesis_access_key"))?,
|
||||
config
|
||||
.kinesis_secret_key
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("missing kinesis_secret_key"))?,
|
||||
None,
|
||||
None,
|
||||
"env",
|
||||
);
|
||||
|
||||
let kinesis_config = aws_config::defaults(BehaviorVersion::latest())
|
||||
.region(Region::new(
|
||||
config
|
||||
.kinesis_region
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("missing blob_store_region"))?,
|
||||
))
|
||||
.credentials_provider(keys)
|
||||
.load()
|
||||
.await;
|
||||
|
||||
Ok(aws_sdk_kinesis::Client::new(&kinesis_config))
|
||||
}
|
||||
|
||||
fn build_clickhouse_client(config: &Config) -> anyhow::Result<::clickhouse::Client> {
|
||||
Ok(::clickhouse::Client::default()
|
||||
.with_url(
|
||||
|
||||
@@ -3,11 +3,9 @@ pub mod db;
|
||||
mod telemetry;
|
||||
mod token;
|
||||
|
||||
use crate::api::events::SnowflakeRow;
|
||||
use crate::api::CloudflareIpCountryHeader;
|
||||
use crate::build_kinesis_client;
|
||||
use crate::{
|
||||
build_clickhouse_client, db::UserId, executor::Executor, Cents, Config, Error, Result,
|
||||
api::CloudflareIpCountryHeader, build_clickhouse_client, db::UserId, executor::Executor, Cents,
|
||||
Config, Error, Result,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use authorization::authorize_access_to_language_model;
|
||||
@@ -30,7 +28,6 @@ use rpc::{
|
||||
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
||||
};
|
||||
use rpc::{ListModelsResponse, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
@@ -48,7 +45,6 @@ pub struct LlmState {
|
||||
pub executor: Executor,
|
||||
pub db: Arc<LlmDatabase>,
|
||||
pub http_client: ReqwestClient,
|
||||
pub kinesis_client: Option<aws_sdk_kinesis::Client>,
|
||||
pub clickhouse_client: Option<clickhouse::Client>,
|
||||
active_user_count_by_model:
|
||||
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
|
||||
@@ -81,11 +77,6 @@ impl LlmState {
|
||||
executor,
|
||||
db,
|
||||
http_client,
|
||||
kinesis_client: if config.kinesis_access_key.is_some() {
|
||||
build_kinesis_client(&config).await.log_err()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
clickhouse_client: config
|
||||
.clickhouse_url
|
||||
.as_ref()
|
||||
@@ -276,6 +267,7 @@ async fn perform_completion(
|
||||
anthropic::ANTHROPIC_API_URL,
|
||||
api_key,
|
||||
request,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
@@ -365,6 +357,7 @@ async fn perform_completion(
|
||||
open_ai::OPEN_AI_API_URL,
|
||||
api_key,
|
||||
serde_json::from_str(params.provider_request.get())?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -397,6 +390,7 @@ async fn perform_completion(
|
||||
google_ai::API_URL,
|
||||
api_key,
|
||||
serde_json::from_str(params.provider_request.get())?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -530,50 +524,25 @@ async fn check_usage_limit(
|
||||
UsageMeasure::TokensPerDay => "tokens_per_day",
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
target: "user rate limit",
|
||||
user_id = claims.user_id,
|
||||
login = claims.github_user_login,
|
||||
authn.jti = claims.jti,
|
||||
is_staff = claims.is_staff,
|
||||
provider = provider.to_string(),
|
||||
model = model.name,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
tokens_this_day = usage.tokens_this_day,
|
||||
users_in_recent_minutes = users_in_recent_minutes,
|
||||
users_in_recent_days = users_in_recent_days,
|
||||
max_requests_per_minute = per_user_max_requests_per_minute,
|
||||
max_tokens_per_minute = per_user_max_tokens_per_minute,
|
||||
max_tokens_per_day = per_user_max_tokens_per_day,
|
||||
);
|
||||
|
||||
SnowflakeRow::new(
|
||||
"Language Model Rate Limited",
|
||||
claims.metrics_id,
|
||||
claims.is_staff,
|
||||
claims.system_id.clone(),
|
||||
json!({
|
||||
"usage": usage,
|
||||
"users_in_recent_minutes": users_in_recent_minutes,
|
||||
"users_in_recent_days": users_in_recent_days,
|
||||
"max_requests_per_minute": per_user_max_requests_per_minute,
|
||||
"max_tokens_per_minute": per_user_max_tokens_per_minute,
|
||||
"max_tokens_per_day": per_user_max_tokens_per_day,
|
||||
"plan": match claims.plan {
|
||||
Plan::Free => "free".to_string(),
|
||||
Plan::ZedPro => "zed_pro".to_string(),
|
||||
},
|
||||
"model": model.name.clone(),
|
||||
"provider": provider.to_string(),
|
||||
"usage_measure": resource.to_string(),
|
||||
}),
|
||||
)
|
||||
.write(&state.kinesis_client, &state.config.kinesis_stream)
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
if let Some(client) = state.clickhouse_client.as_ref() {
|
||||
tracing::info!(
|
||||
target: "user rate limit",
|
||||
user_id = claims.user_id,
|
||||
login = claims.github_user_login,
|
||||
authn.jti = claims.jti,
|
||||
is_staff = claims.is_staff,
|
||||
provider = provider.to_string(),
|
||||
model = model.name,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
tokens_this_day = usage.tokens_this_day,
|
||||
users_in_recent_minutes = users_in_recent_minutes,
|
||||
users_in_recent_days = users_in_recent_days,
|
||||
max_requests_per_minute = per_user_max_requests_per_minute,
|
||||
max_tokens_per_minute = per_user_max_tokens_per_minute,
|
||||
max_tokens_per_day = per_user_max_tokens_per_day,
|
||||
);
|
||||
|
||||
report_llm_rate_limit(
|
||||
client,
|
||||
LlmRateLimitEventRow {
|
||||
@@ -686,27 +655,6 @@ impl<S> Drop for TokenCountingStream<S> {
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
);
|
||||
|
||||
let properties = json!({
|
||||
"plan": match claims.plan {
|
||||
Plan::Free => "free".to_string(),
|
||||
Plan::ZedPro => "zed_pro".to_string(),
|
||||
},
|
||||
"model": model,
|
||||
"provider": provider,
|
||||
"usage": usage,
|
||||
"tokens": tokens
|
||||
});
|
||||
SnowflakeRow::new(
|
||||
"Language Model Used",
|
||||
claims.metrics_id,
|
||||
claims.is_staff,
|
||||
claims.system_id.clone(),
|
||||
properties,
|
||||
)
|
||||
.write(&state.kinesis_client, &state.config.kinesis_stream)
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
if let Some(clickhouse_client) = state.clickhouse_client.as_ref() {
|
||||
report_llm_usage(
|
||||
clickhouse_client,
|
||||
|
||||
@@ -9,7 +9,7 @@ use strum::IntoEnumIterator as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Default, serde::Serialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Default)]
|
||||
pub struct TokenUsage {
|
||||
pub input: usize,
|
||||
pub input_cache_creation: usize,
|
||||
@@ -23,7 +23,7 @@ impl TokenUsage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct Usage {
|
||||
pub requests_this_minute: usize,
|
||||
pub tokens_this_minute: usize,
|
||||
|
||||
@@ -8,7 +8,6 @@ use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -17,10 +16,6 @@ pub struct LlmTokenClaims {
|
||||
pub exp: u64,
|
||||
pub jti: String,
|
||||
pub user_id: u64,
|
||||
#[serde(default)]
|
||||
pub system_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub metrics_id: Option<Uuid>,
|
||||
pub github_user_login: String,
|
||||
pub is_staff: bool,
|
||||
pub has_llm_closed_beta_feature_flag: bool,
|
||||
@@ -41,7 +36,6 @@ impl LlmTokenClaims {
|
||||
has_llm_closed_beta_feature_flag: bool,
|
||||
has_llm_subscription: bool,
|
||||
plan: rpc::proto::Plan,
|
||||
system_id: Option<String>,
|
||||
config: &Config,
|
||||
) -> Result<String> {
|
||||
let secret = config
|
||||
@@ -55,8 +49,6 @@ impl LlmTokenClaims {
|
||||
exp: (now + LLM_TOKEN_LIFETIME).timestamp() as u64,
|
||||
jti: uuid::Uuid::new_v4().to_string(),
|
||||
user_id: user.id.to_proto(),
|
||||
system_id,
|
||||
metrics_id: Some(user.metrics_id),
|
||||
github_user_login: user.github_login.clone(),
|
||||
is_staff,
|
||||
has_llm_closed_beta_feature_flag,
|
||||
|
||||