From 3135354d8566367a19e1d997efd249dec3a3a36e Mon Sep 17 00:00:00 2001 From: Ilya Zedgenizov <34807886+Dead4W@users.noreply.github.com> Date: Mon, 16 Oct 2023 00:48:57 +0300 Subject: [PATCH] Merge main to saber (#1) * Use accurate token counts (#1024) * Use accurate token counts The `total` token count is now based upon an identical format conversation sent to the LLM during response generation. This results in counts that should be accurate, and prevent token limit errors entirely. * Match token counts precisely, and add baseline count * Calculate `total` as summation of other section token counts * add message to response if it ended when token limit exceeded, no translations --------- Co-authored-by: anastasiia * Send full redirect target to cognito (#1034) * Update Helm chart (#1033) * update helm chart * remove secret.yaml * put back secret.yaml * mandatory Semantic in /search (#1039) * Upgrade qdrant version to 1.6 (#1037) * Upgrade qdrant version * Update qdrant binaries * Fix race in credentials polling (#1042) Previously the assumption here was that this path is locked & safe when there is a furnished github cred in the system. However, when the user logs out, the `unwrap()` call can blow up the task, and this may cause issues. * Enable paid features for desktop users (#1038) * Add pro features to default builds * Check user's status through `User` object * Adapt webserver layer for paid feature gate * Just enforce schema right out of the gate * Fix date parsing logic * allow paid users sync branches * Fix clippy features * Disable branch switching for local repos * cloud implies pro * Update dockerfile --------- Co-authored-by: anastasiia * Update flake (#1044) * Debug logs for initialisation (#1036) * debug logs for initialisation * Scope logging * DB too * Clippy error --------- Co-authored-by: rsdy * Allow indexing of large files (#1040) * WIP * Support indexing large files, but lazy load them from local file system * bump tantivy to v0.21 (#1043) * bump tantivy to v0.22 * address clippy * fix broken tests * bump version to 0.5.6 (#1047) --------- Co-authored-by: calyptobai <111788964+calyptobai@users.noreply.github.com> Co-authored-by: anastasiia Co-authored-by: rsdy Co-authored-by: Gabriel Gordon-Hall Co-authored-by: rsdy Co-authored-by: akshay Co-authored-by: Anastasiia Solop <35258279+anastasiya1155@users.noreply.github.com> Co-authored-by: Ilya Zedgenizov --- .github/workflows/dependencies.yml | 16 +- .github/workflows/server-test.yml | 3 +- Cargo.lock | 202 ++++++--- Cargo.toml | 1 + Dockerfile | 2 +- apps/desktop/src-tauri/Cargo.toml | 6 +- .../src-tauri/bin/qdrant-aarch64-apple-darwin | 4 +- .../src-tauri/bin/qdrant-x86_64-apple-darwin | 4 +- .../bin/qdrant-x86_64-pc-windows-msvc.exe | 4 +- .../bin/qdrant-x86_64-unknown-linux-gnu | 4 +- apps/desktop/src-tauri/tauri.conf.json | 4 +- client/package.json | 4 +- .../PageTemplate/BranchSelector.tsx | 4 +- client/src/locales/en.json | 1 + client/src/locales/es.json | 398 +++++++++++++++++ client/src/locales/ja.json | 395 +++++++++++++++++ client/src/locales/zh-CN.json | 403 ++++++++++++++++++ .../RightPanel/Conversation/Input.tsx | 25 +- .../RightPanel/Conversation/index.tsx | 4 + flake.lock | 31 +- flake.nix | 6 +- helm/bloop/templates/deployment.yaml | 2 +- helm/bloop/templates/secret.yaml | 4 +- helm/bloop/values.yaml | 8 +- nix/onnxruntime.nix | 4 +- server/bleep/Cargo.toml | 10 +- server/bleep/benches/queries.rs | 2 +- server/bleep/src/agent/tools/answer.rs | 5 +- server/bleep/src/analytics.rs | 3 + server/bleep/src/background.rs | 2 +- server/bleep/src/background/sync.rs | 4 +- server/bleep/src/collector/bytes_filter.rs | 18 +- server/bleep/src/collector/frequency.rs | 17 +- server/bleep/src/commits.rs | 12 +- server/bleep/src/config.rs | 4 +- server/bleep/src/db.rs | 27 +- server/bleep/src/ee.rs | 4 + server/bleep/src/ee/webserver.rs | 8 +- server/bleep/src/indexes.rs | 2 +- server/bleep/src/indexes/file.rs | 56 ++- server/bleep/src/indexes/reader.rs | 5 +- server/bleep/src/indexes/schema.rs | 4 + .../bleep/src/intelligence/code_navigation.rs | 6 +- .../src/intelligence/scope_resolution.rs | 2 +- server/bleep/src/lib.rs | 21 +- server/bleep/src/periodic/logrotate.rs | 3 +- server/bleep/src/periodic/remotes.rs | 16 +- server/bleep/src/query/compiler.rs | 8 +- server/bleep/src/query/execute.rs | 4 +- server/bleep/src/query/ranking.rs | 33 +- server/bleep/src/remotes.rs | 8 +- server/bleep/src/remotes/github.rs | 4 +- server/bleep/src/repo/iterator.rs | 25 +- server/bleep/src/repo/iterator/filters.rs | 16 +- server/bleep/src/repo/iterator/fs.rs | 14 +- server/bleep/src/repo/iterator/git.rs | 7 +- server/bleep/src/semantic.rs | 42 +- server/bleep/src/semantic/chunk.rs | 11 +- server/bleep/src/semantic/embedder.rs | 9 +- server/bleep/src/snippet.rs | 8 +- server/bleep/src/webserver/aaa.rs | 41 +- server/bleep/src/webserver/config.rs | 5 +- server/bleep/src/webserver/middleware.rs | 137 ++++-- server/bleep/src/webserver/quota.rs | 25 +- server/bleep/src/webserver/repos.rs | 77 ++-- server/bleep/src/webserver/search.rs | 9 +- server/bleep/src/webserver/studio.rs | 61 ++- 67 files changed, 1894 insertions(+), 420 deletions(-) create mode 100644 client/src/locales/es.json create mode 100644 client/src/locales/ja.json create mode 100644 client/src/locales/zh-CN.json diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 4d4147ac07..3d829d0421 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -24,15 +24,15 @@ jobs: include: - target: x86_64-unknown-linux-gnu - os: ubuntu-latest - cross: false + os: ubuntu-latest + cross: false - target: x86_64-apple-darwin - os: macos-11 - cross: false + os: macos-11 + cross: false - target: aarch64-apple-darwin - os: macos-11 + os: macos-11 cross: true - target: x86_64-pc-windows-msvc @@ -44,7 +44,7 @@ jobs: - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: - toolchain: stable + toolchain: 1.70.0 target: ${{ matrix.target }} - name: Install Protoc @@ -54,9 +54,9 @@ jobs: - name: Build qdrant env: - VERSION: 1.5.1 + VERSION: 1.6.0 run: | - cargo install --target ${{ matrix.target }} --git https://github.com/qdrant/qdrant --tag v${{ env.VERSION }} --locked --root . qdrant + cargo install --target ${{ matrix.target }} --git https://github.com/qdrant/qdrant --tag v${{ env.VERSION }} --root . qdrant - name: Upload binaries uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/server-test.yml b/.github/workflows/server-test.yml index 23109fdc59..3e1d14486e 100644 --- a/.github/workflows/server-test.yml +++ b/.github/workflows/server-test.yml @@ -4,6 +4,7 @@ on: pull_request: branches: [main] paths: + - "flake.*" - "server/**" - ".github/workflows/server**" @@ -49,7 +50,7 @@ jobs: run: cargo --locked fmt -p bleep -- --check - name: Clippy - run: cargo --locked clippy -p bleep --features=ee + run: cargo --locked clippy -p bleep --features=ee-pro,ee-cloud - name: Tests run: cargo --locked test -p bleep --release diff --git a/Cargo.lock b/Cargo.lock index d16e2faf8b..d090fb5076 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "bleep" -version = "0.5.5" +version = "0.5.6" dependencies = [ "anyhow", "async-stream", @@ -554,6 +554,7 @@ dependencies = [ "smallvec", "sqlx", "tantivy", + "tantivy-columnar", "tempdir", "thiserror", "thread-priority", @@ -599,7 +600,7 @@ dependencies = [ [[package]] name = "bloop" -version = "0.5.5" +version = "0.5.6" dependencies = [ "anyhow", "bleep", @@ -749,6 +750,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -1768,17 +1770,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fail" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5e43d0f78a42ad591453aedb1d7ae631ce7ee445c7643691055a9ed8d3b01c" -dependencies = [ - "log", - "once_cell", - "rand 0.8.5", -] - [[package]] name = "failure" version = "0.1.8" @@ -1826,20 +1817,6 @@ dependencies = [ "serde", ] -[[package]] -name = "fastfield_codecs" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374a3a53c1bd5fb31b10084229290eafb0a05f260ec90f1f726afffda4877a8a" -dependencies = [ - "fastdivide", - "itertools 0.10.5", - "log", - "ownedbytes", - "tantivy-bitpacker", - "tantivy-common", -] - [[package]] name = "fastrand" version = "2.0.1" @@ -1955,13 +1932,13 @@ dependencies = [ ] [[package]] -name = "fs2" -version = "0.4.3" +name = "fs4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" dependencies = [ - "libc", - "winapi", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -3170,9 +3147,6 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.6", -] [[package]] name = "hashbrown" @@ -3803,6 +3777,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -4045,11 +4028,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.8" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.14.0", ] [[package]] @@ -4063,9 +4046,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.9.5" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a8cbbb2831780bc3b9c15a41f5b49222ef756b6730a95f3decfdd15903eb5a3" +checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8" [[package]] name = "mac" @@ -4290,12 +4273,9 @@ dependencies = [ [[package]] name = "murmurhash32" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d736ff882f0e85fe9689fb23db229616c4c00aee2b3ac282f666d8f20eb25d4a" -dependencies = [ - "byteorder", -] +checksum = "d9380db4c04d219ac5c51d14996bbf2c2e9a15229771b53f8671eb6c83cf44df" [[package]] name = "nanorand" @@ -4794,9 +4774,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "ownedbytes" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e957eaa64a299f39755416e5b3128c505e9d63a91d0453771ad2ccd3907f8db" +checksum = "6e8a72b918ae8198abb3a18c190288123e1d442b6b9a7d709305fd194688b4b7" dependencies = [ "stable_deref_trait", ] @@ -6474,6 +6454,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "sketches-ddsketch" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a406c1882ed7f29cd5e248c9848a80e7cb6ae0fea82346d2746f2f941c07e1" +dependencies = [ + "serde", +] + [[package]] name = "slab" version = "0.4.9" @@ -6868,49 +6857,49 @@ dependencies = [ [[package]] name = "tantivy" -version = "0.19.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb26a6b22c84d8be41d99a14016d6f04d30d8d31a2ea411a8ab553af5cc490d" +checksum = "c1d4675fed6fe2218ce11445374e181e864a8ffd0f28e7e0591ccfc38cd000ae" dependencies = [ - "aho-corasick 0.7.20", + "aho-corasick 1.1.1", "arc-swap", "async-trait", - "base64 0.13.1", + "base64 0.21.4", "bitpacking", "byteorder", "census", "crc32fast", "crossbeam-channel", "downcast-rs", - "fail", "fastdivide", - "fastfield_codecs", - "fs2", + "fs4", "htmlescape", - "itertools 0.10.5", + "itertools 0.11.0", "levenshtein_automata", "log", "lru", "lz4_flex", "measure_time", - "memmap2 0.5.10", + "memmap2 0.7.1", "murmurhash32", "num_cpus", "once_cell", "oneshot", - "ownedbytes", "rayon", "regex 1.9.5", "rust-stemmers", "rustc-hash", "serde", "serde_json", + "sketches-ddsketch", "smallvec", - "stable_deref_trait", "tantivy-bitpacker", + "tantivy-columnar", "tantivy-common", "tantivy-fst", "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", "tempfile", "thiserror", "time", @@ -6920,18 +6909,40 @@ dependencies = [ [[package]] name = "tantivy-bitpacker" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e71a0c95b82d4292b097a09b989a6380d28c3a86800c841a2d03bae1fc8b9fa6" +checksum = "cecb164321482301f514dd582264fa67f70da2d7eb01872ccd71e35e0d96655a" +dependencies = [ + "bitpacking", +] + +[[package]] +name = "tantivy-columnar" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d85f8019af9a78b3118c11298b36ffd21c2314bd76bbcd9d12e00124cbb7e70" +dependencies = [ + "fastdivide", + "fnv", + "itertools 0.11.0", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", +] [[package]] name = "tantivy-common" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14fef4182bb60df9a4b92cd8ecab39ba2e50a05542934af17eef1f49660705cb" +checksum = "af4a3a975e604a2aba6b1106a04505e1e7a025e6def477fab6e410b4126471e1" dependencies = [ + "async-trait", "byteorder", "ownedbytes", + "serde", + "time", ] [[package]] @@ -6947,13 +6958,41 @@ dependencies = [ [[package]] name = "tantivy-query-grammar" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343e3ada4c1c480953f6960f8a21ce9c76611480ffdd4f4e230fdddce0fc5331" +checksum = "1d39c5a03100ac10c96e0c8b07538e2ab8b17da56434ab348309b31f23fada77" dependencies = [ - "combine", - "once_cell", - "regex 1.9.5", + "nom", +] + +[[package]] +name = "tantivy-sstable" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0c1bb43e5e8b8e05eb8009610344dbf285f06066c844032fbb3e546b3c71df" +dependencies = [ + "tantivy-common", + "tantivy-fst", + "zstd", +] + +[[package]] +name = "tantivy-stacker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2c078595413f13f218cf6f97b23dcfd48936838f1d3d13a1016e05acd64ed6c" +dependencies = [ + "murmurhash32", + "tantivy-common", +] + +[[package]] +name = "tantivy-tokenizer-api" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "347b6fb212b26d3505d224f438e3c4b827ab8bd847fe9953ad5ac6b8f9443b66" +dependencies = [ + "serde", ] [[package]] @@ -8844,3 +8883,32 @@ dependencies = [ "crossbeam-utils", "flate2", ] + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index d27d7c0f77..ed6c3f3a7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "server/bleep", "apps/desktop/src-tauri" diff --git a/Dockerfile b/Dockerfile index b07bce43ce..70c15848f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ COPY server server COPY apps/desktop/src-tauri apps/desktop/src-tauri COPY Cargo.lock Cargo.toml . RUN --mount=target=/root/.cache/sccache,type=cache --mount=target=/build/target,type=cache \ - cargo --locked build -p bleep --release --features=ee && \ + cargo --locked build -p bleep --release --features=ee-cloud && \ cp /build/target/release/bleep / && \ sccache --show-stats && \ mkdir /dylib && \ diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 16c949d2f4..ba0623c903 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bloop" -version = "0.5.5" +version = "0.5.6" description = "Search code. Fast." authors = ["Bloop AI Developers"] license = "Apache-2.0" @@ -18,7 +18,7 @@ tauri-build = { version = "1.4.1", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.5.0", features = ["dialog-open", "fs-all", "http-all", "native-tls-vendored", "os-all", "path-all", "process-all", "shell-all", "updater", "window-all"] } -bleep = { path = "../../../server/bleep", package = "bleep" } +bleep = { path = "../../../server/bleep", package = "bleep", features = ["ee-pro"] } anyhow = "1.0.75" tokio = { version = "1.32.0", features = ["rt-multi-thread"] } tracing = "0.1.37" @@ -41,4 +41,4 @@ default = ["custom-protocol"] # this feature is used for production builds where `devPath` points to the filesystem # DO NOT remove this custom-protocol = ["tauri/custom-protocol"] -_ee = ["bleep/ee"] +_ee = ["bleep/ee-pro", "bleep/ee-cloud"] diff --git a/apps/desktop/src-tauri/bin/qdrant-aarch64-apple-darwin b/apps/desktop/src-tauri/bin/qdrant-aarch64-apple-darwin index e186b27d34..763f593f0f 100755 --- a/apps/desktop/src-tauri/bin/qdrant-aarch64-apple-darwin +++ b/apps/desktop/src-tauri/bin/qdrant-aarch64-apple-darwin @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:318d6e7a256897f6785a7330386e91d35ea6ce3daf465749fd84c60da1b01d57 -size 46813668 +oid sha256:d5bcdc8dba2c6c54f50d08f877720b4d511ef59e700e83f1cab567d080b7aa9f +size 53184068 diff --git a/apps/desktop/src-tauri/bin/qdrant-x86_64-apple-darwin b/apps/desktop/src-tauri/bin/qdrant-x86_64-apple-darwin index 9b067ac4c2..05e9eaeddd 100755 --- a/apps/desktop/src-tauri/bin/qdrant-x86_64-apple-darwin +++ b/apps/desktop/src-tauri/bin/qdrant-x86_64-apple-darwin @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19ebec464622e0729c53457185dc1217218ecc3b6645b7c822fdacaae6a9d41e -size 33410448 +oid sha256:52b7d9b242909659252a7092b3a67d958f46d400ac24e9f0a55002918008e802 +size 56945176 diff --git a/apps/desktop/src-tauri/bin/qdrant-x86_64-pc-windows-msvc.exe b/apps/desktop/src-tauri/bin/qdrant-x86_64-pc-windows-msvc.exe index 726e283d6d..925be54455 100644 --- a/apps/desktop/src-tauri/bin/qdrant-x86_64-pc-windows-msvc.exe +++ b/apps/desktop/src-tauri/bin/qdrant-x86_64-pc-windows-msvc.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a75f7684117a84726a3799dbda3a7f5a1cc901ca29fee8e2ac0fc981bd30f30d -size 40742912 +oid sha256:0650075152d8fc4da892a5aa7bfa3cb7809d3f9638d19f578773791a1dcb6505 +size 48054272 diff --git a/apps/desktop/src-tauri/bin/qdrant-x86_64-unknown-linux-gnu b/apps/desktop/src-tauri/bin/qdrant-x86_64-unknown-linux-gnu index 37bddbeea8..4b358da793 100755 --- a/apps/desktop/src-tauri/bin/qdrant-x86_64-unknown-linux-gnu +++ b/apps/desktop/src-tauri/bin/qdrant-x86_64-unknown-linux-gnu @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f38b9da80c9a92cecadf92027282d702bbbfcadea278051d3eb1b1ef82cdb532 -size 37316912 +oid sha256:9f761873c38a827ea28e060ad22d3d41b3865a5ae27f9ddc6e1ef96a2000a422 +size 60662808 diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index adefcd7fd5..737c307157 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "bloop", - "version": "0.5.5" + "version": "0.5.6" }, "tauri": { "allowlist": { @@ -103,4 +103,4 @@ } ] } -} \ No newline at end of file +} diff --git a/client/package.json b/client/package.json index af4483e25c..7c7ac3ac3a 100644 --- a/client/package.json +++ b/client/package.json @@ -1,7 +1,7 @@ { "name": "@bloop/client", "private": true, - "version": "0.5.5", + "version": "0.5.6", "scripts": { "dev": "vite", "build": "tsc && vite build", @@ -13,4 +13,4 @@ "test": "jest --collect-coverage --passWithNoTests", "chromatic": "npx chromatic --project-token=6115d726666b" } -} \ No newline at end of file +} diff --git a/client/src/components/PageTemplate/BranchSelector.tsx b/client/src/components/PageTemplate/BranchSelector.tsx index ded68ba6e0..510d8eacf7 100644 --- a/client/src/components/PageTemplate/BranchSelector.tsx +++ b/client/src/components/PageTemplate/BranchSelector.tsx @@ -12,6 +12,7 @@ import { UIContext } from '../../context/uiContext'; import TextInput from '../TextInput'; import { RepoType, SyncStatus } from '../../types/general'; import { DeviceContext } from '../../context/deviceContext'; +import { PersonalQuotaContext } from '../../context/personalQuotaContext'; import BranchItem from './BranchItem'; let eventSource: EventSource; @@ -27,6 +28,7 @@ const BranchSelector = () => { const { apiUrl, isSelfServe } = useContext(DeviceContext); const { tab } = useContext(UIContext.Tab); + const { isSubscribed } = useContext(PersonalQuotaContext.Values); const { repositories, setRepositories } = useContext(RepositoriesContext); const { selectedBranch, setSelectedBranch } = useContext( SearchContext.SelectedBranch, @@ -191,7 +193,7 @@ const BranchSelector = () => { size="medium" type="button" onClick={() => { - if (isSelfServe) { + if (isSelfServe || isSubscribed) { setOpen((prev) => !prev); } else { setCloudFeaturePopupOpen(true); diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 6ee48c656c..aa476f61ad 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -393,6 +393,7 @@ "You've upgraded your account!": "You've upgraded your account!", "Unlimited usage and premium features are activated.": "Unlimited usage and premium features are activated.", "Let's go": "Let's go", + "Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.": "Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate." "Login": "Login", "Password": "Password" } diff --git a/client/src/locales/es.json b/client/src/locales/es.json new file mode 100644 index 0000000000..e3980b5186 --- /dev/null +++ b/client/src/locales/es.json @@ -0,0 +1,398 @@ +{ + "Documentation": "Documentación", + "Showing # result_zero": "No hubo coincidencias para su búsqueda. ¡Pruebe diferentes combinaciones!", + "Showing # result_one": "Mostrando {{count}} resultado", + "Showing # result_other": "Mostrando {{count}} resultados", + "bloop crashed unexpectedly": "bloop colapsó inesperadamente", + "By submitting this crash report you agree to send it to bloop for investigation.": "Al enviar este informe de error, está aceptando que bloop pueda investigarlo.", + "Close": "Cerrar", + "Describe the bug to help us reproduce it...": "Describe el error para ayudarnos a reproducirlo ...", + "Discord": "Discord", + "Email address": "Dirección de correo electrónico", + "Email is not valid": "El correo no es válido", + "Full name": "Nombre completo", + "General": "General", + "Got it!": "¡Entendido!", + "Offline": "Desconectado", + "Online": "En línea", + "Preferences": "Ajustes", + "Problem details and System configuration": "Detalles del problema y configuración del sistema", + "Provide any steps necessary to reproduce the problem...": "Proporcione los pasos necesarios para reproducir el problema ...", + "Report a bug": "Reportar un error", + "Sign In": "Iniciar sesión", + "Sign in with GitHub": "Iniciar sesión con Github", + "Status": "Estado", + "Submit bug report": "Enviar informe de error", + "Submit crash report": "Enviar informe de bloqueo", + "Thank you!": "¡Gracias!", + "Use GitHub to sign in to your account": "Use GitHub para iniciar sesión", + "We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.": "Queremos hacer de esta la mejor experiencia para usted. Si encontró un error, envíenos un informe de error. Nuestro equipo lo investigará lo antes posible.", + "We’ll investigate and reach out back soon if necessary.": "Lo investigaremos y le comunicaremos pronto si es necesario.", + "Syncing repository": "Sincronizando repositorio", + "Public repository": "Repositorio público", + "key": "clave", + "Add a repository from your local machine": "Añada un repositorio local", + "Your GitHub repository": "Su repositorio de Github", + "Any repository from your private GitHub account": "Cualquier repositorio de su cuenta privada de Github", + "Local repository": "Repositorio local", + "Any public repository hosted on GitHub": "Cualquier repositorio público de GitHub", + "Add": "Añadir", + "All repositories": "Todos los repositorios", + "Sign out": "Cerrar sesión", + "Settings": "Ajustes", + "Manage how you will be called in bloop": "Indique cómo se le llamará en bloop", + "First and last name": "Primer y segundo apellido", + "First name": "Nombre de pila", + "Your name": "Su nombre", + "Last name": "Apellido", + "Your last name": "Tu apellido", + "Used to sign in, syncing and product updates": "Se utiliza para iniciar sesión, sincronizar y actualizaciones de producto", + "Email": "Correo electrónico", + "Your email address": "Su dirección de correo electrónico", + "Save changes": "Guardar cambios", + "Theme": "Tema", + "Select your interface color scheme": "Seleccione la paleta de colores para la interfaz", + "System Preference": "Preferencia del sistema", + "Atom One Dark Pro": "Atom One Dark Pro", + "Solarized Light": "Solarized Light", + "Default": "Tema Predeterminado", + "VSCode Dark": "VSCode Dark", + "Abyss": "Abyss", + "Darcula": "Darcula", + "Dracula": "Dracula", + "Material": "Material", + "GitHub Dark": "GitHub Dark", + "Gruvbox Dark": "Gruvbox Dark", + "Kimbie": "Kimbie", + "Solarized Dark": "Solarized Dark", + "Tomorrow Night Blue": "Tomorrow Night Blue", + "Default Light": "Claro Predeterminado", + "Monokai": "Monokai", + "Night Owl": "Night Owl", + "GitHub Light": "GitHub Light", + "Gruvbox Light": "Gruvbox Light", + "VSCode Light": "VSCode Light", + "Quiet Light": "Quiet Light", + "No repositories": "Sin repositorios", + "As soon as you add a repository it will appear here.": "Cuando añada un repositorio, aparecerá aquí.", + "Last updated ": "Última actualización", + "Re-sync": "Volver a sincronizar", + "Remove": "Eliminar", + "Cloning...": "Clonando...", + "Queued...": "En cola...", + "Cancel": "Cancelar", + "Indexing...": "Indexando...", + "We are syncing your repository to bloop. This might take a couple of minutes": "Estamos sincronizando su repositorio en bloop. Esto podría llevar unos minutos", + "complete": "completo", + "Confirm": "Confirmar", + "Cancelling...": "Cancelando...", + "Cancelled": "Cancelado", + "cancelling": "cancelando", + "done": "hecho", + "cancelled": "cancelado", + "syncing": "sincronizando", + "indexing": "indexando", + "Error": "Error", + "Remote removed ": "Remoto eliminado", + "Not synced": "No sincronizado", + "Removed": "Eliminado", + "Select any private repository you would like to sync": "Seleccione el repositorio privado que desee sincronizar", + "Private repository": "Repositorio privado", + "Search repository...": "Buscar repositorio...", + "Alphabetically": "Alfabéticamente", + "Last updated": "Última actualización", + "No results...": "No hay resultados...", + "Nothing matched your search. Try a different combination!": "No hay coincidencias con tu búsqueda. ¡Prueba una combinación diferente!", + "Already synced": "Ya sincronizado", + "Sync": "Sincronizar", + "Scan a folder to sync it’s repositories.": "Escanee una carpeta para sincronizar sus repositorios. Solo se sincronizarán los directorios con una carpeta .git", + "Select folder": "Seleccione la carpeta", + "Sync local repositories": "Repositorios locales sincronizados", + "Scan a folder": "Escanear una carpeta", + "Change folder": "Cambiar carpeta", + "Select the folders you want to add to bloop. You can always sync, unsync or remove unwanted repositories later.": "Seleccione las carpetas que desee añadir a bloop. Siempre puede sincronizar, dejar de sincronizar o eliminar repositorios no deseados más adelante.", + "Repository url...": "URL de repositorio ...", + "Sync repository": "Sincronizar repositorio", + "Paste a link to any public repository you would like to index.": "Pegue el enlace del repositorio público que desee indexar.", + "This is not a public repository / We couldn't find this repository": "No es un repositorio público / No pudimos encontrar el repositorio", + "Verifying access...": "Verificando acceso...", + "Synced": "Sincronizado", + "Files in": "Archivos", + "Files": "Archivos", + "Copy search query to clipboard": "Copiar texto de búsqueda en el portapapeles", + "Search for code using regex": "Buscar código usando regex", + "Clear search history": "Limpiar historial de búsqueda", + "Search branches...": "Buscar ramas...", + "Switch branch": "Cambiar de rama", + "Upgrade now": "Mejorar ahora", + "Upgrade plan": "Mejorar plan", + "to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.": "Para explorar sin obstáculos el código en todas las ramas en sus repositorios de GitHub, maximizando sus capacidades y posbilidades sobre código.", + "GitHub Branches": "Ramas de Github", + "Fold everything": "Colapsar todo", + "Reset filters": "Restablecer filtros", + "Hide filters": "Ocultar filtros", + "Filter lang...": "Filtrar por lenguaje...", + "Select all": "Seleccionar todo", + "File Type": "Tipo de archivo", + "View all results": "Ver todos los resultados", + "Query suggestions": "Sugerencias de búsqueda", + "Result suggestions": "Sugerencias de resultados", + "Show fewer results": "Mostrar menos resultados", + "# match_one": "{{count}} coincidencia", + "# match_other": "{{count}} coincidencias", + "Today": "Hoy", + "Expand everything": "Expandir todo", + "Language": "Idioma", + "Results": "Resultados", + "No results": "No hay resultados", + "Suggested combinations": "Combinaciones sugeridas", + "Show filters": "Mostrar filtros", + "Filters": "Filtros", + "Apply filters": "Aplicar filtros", + "Show # more match_one": "Mostrar {{count}} coincidencia más", + "Show # more match_other": "Mostrar {{count}} coincidencias más", + "Show less": "Mostrar menos", + "Previous page": "Página anterior", + "Next page": "Página siguiente", + "Showing page {{page}} of {{totalPages}}": "Mostrando la página {{page}} de {{totalPages}}", + "Deselect all": "Deseleccionar todo", + "File": "Archivo", + "Open in sidebar": "Abrir en la barra lateral", + "Open in full view": "Abrir en vista completa", + "Open in modal": "Abrir en modal", + "The line of code where identifier is defined": "La línea de código donde se define el identificador", + "The line of code where the identifier is referenced": "La línea de código donde se hace referencia al identificador", + "We weren't able to identify any references at the moment": "No pudimos identificar ninguna referencia en este momento", + "No references or definitions found": "No se encontraron referencias ni definiciones", + "Definitions": "Definiciones", + "References": "Referencias", + "definition": "definición", + "reference": "referencia", + "In this file": "En este archivo", + "All conversations": "Todas las conversaciones", + "Sorry, this repository is not ready for search": "Lo siento, este repositorio no está listo para la búsqueda", + "Wait for the repository to finish syncing and try again": "Espere a que el repositorio se termine de sincronizar e intente nuevamente", + "Create new": "Crear nuevo", + "Show": "Mostrar", + "Hide": "Ocultar", + "Conversations": "Conversaciones", + "Delete": "Borrar", + "Answer Ready": "Respuesta lista", + "View": "Vista", + "Reading ": "Leyendo", + "avatar": "avatar", + "Submit": "Enviar", + "Save": "Guardar", + "Generating response...": "Generando respuesta ...", + "Responding...": "Respondiendo ...", + "Reading": "Leyendo", + "Bad": "Mala", + "Good": "Bien", + "Show more": "Mostrar más", + "How would you rate this response?": "¿Cómo calificaría esta respuesta?", + "What was the issue with this response? How could it be improved?": "¿Cuál fue el problema con esta respuesta? ¿Cómo puede ser mejorada?", + "Failed to get a response from OpenAI. Try again in a few moments.": "No se pudo obtener una respuesta de OpenAI. Prueba otra vez en unos instantes.", + "Bad response": "Mala respuesta", + "Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}": "Explique las líneas {{lineStart}} - {{lineEnd}} en {{filePath}}", + "Select less code": "Seleccione menos código", + "Ask bloop": "Preguntar a bloop", + "Explain": "Explicar", + "Something went wrong": "Algo salió mal", + "chats in bloop": "Chats en bloop", + "Setup bloop": "Configuración de bloop", + "Please log into your GitHub account to complete setup": "Inicie sesión con su cuenta de GitHub para completar la configuración", + "Select color theme:": "Seleccione el tema:", + "Connect account": "Conectar cuenta", + "Continue": "Continuar", + "Back": "Atrás", + "Code expires in {{tokenExpireIn}}": "El código expira en {{tokenExpireIn}}", + "After launching the GitHub login window, you’ll need to perform the following steps:": "Después de la ventana de inicio de sesión de GitHub, deberá realizar los siguientes pasos:", + "or visit: ": "o visitar:", + "Authorise bloop": "Autorizar bloop", + "Enter the device code": "Introduzca el código del dispositivo", + "Note: GitHub OAuth login doesn't support granular repo or organisation level access. The token has a wide scope, but only repos you explicitly choose will be synced, and your account credentials are always stored locally.": "Nota: El inicio de sesión de GitHub OAuth no permite getionar de manera específica el nivel de acceso al repositiorio o de una organización. El token tiene un acceso amplio, pero solo los repositorios escogidos explícitamente se sincronizarán, y las credenciales de su cuenta siempre se almacenan localmente.", + "Launch GitHub Login": "Iniciar sesión con GitHub", + "Copy": "Copiar", + "Copied": "Copiado", + "Waiting for authentication...": "Esperando la autenticación ...", + "Code has expired": "El código ha expirado", + "Generate new code": "Generar nuevo código", + "Relaunch GitHub auth": "Autenticarse de nuevo con Github", + "Disconnect": "Desconectar", + "Terms & conditions": "Términos y condiciones", + "By continuing you accept our": "Al continuar, aceptas nuestros", + "and ": "y", + "Privacy policy": "Política de privacidad", + "Welcome to bloop": "Bienvenido a bloop", + "Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.": "Haga preguntas sobre sus bases de código en lenguaje natural, al igual que hablaría con ChatGPT. Comience sincronizando un repositorio, luego abra el repositorio y comience a chatear.", + "Generate code using AI": "Generar código usando IA", + "Unlock the value of your existing code, using AI": "Desbloquee el valor de su código existente, utilizando IA", + "Search code in natural language": "Buscar código en lenguaje natural", + "Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.": "¡El estudio de código te ayuda a escribir scripts, crear pruebas unitarias, depurar problemas o generar cualquier otra cosa que puedas imaginar usando IA", + "Available in 10+ languages to help you find references and definitions": "Disponible en más de 10 idiomas para ayudarle a encontrar referencias y definiciones", + "Got it": "Entiendo", + "Precise code navigation": "Navegación de código precisa", + "Show search steps": "Mostrar pasos de búsqueda", + "Hide search steps": "Ocultar pasos de búsqueda", + "To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for being a valued user of our app.": "Para actualizar la aplicación, visite nuestra página de lanzamientos en GitHub y descargue la última versión manualmente. Gracias por ser un valioso usuario de nuestra aplicación.", + "Update Required": "Actualización necesaria", + "Restart the app": "Reiniciar la aplicación", + "or ": "o", + "visit the downloads page": "Visite la página de descarga", + "Open dropdown": "Abrir desplegable", + "Prompt guide": "Guía de prompts", + "Like ChatGPT, bloop responds best to certain prompts. We’ve compiled a really quick guide on how better to prompt bloop.": "Al igual que ChatGPT, bloop responde mejor a ciertas indicaciones. Hemos recopilado consejos en una breve guía sobre cómo potenciar el uso de bloop.", + "Skip (Not recommended)": "Saltar (no recomendado)", + "We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.": "No pudimos responder a su pregunta. Puede intentar volver a preguntar en unos momentos o reformular su pregunta.", + "Stop generating": "Parar la generación", + "Take a quick look": "Echar un vistazo", + "Go back": "Volver", + "Repo home": "Repositorio", + "Go forward": "Siguiente", + "History": "Historia", + "Yesterday": "Ayer", + "Regex search...": "Búsqueda regex...", + "Searching...": "Buscando...", + "The folder is empty": "La carpeta está vacía", + "We haven't found any files to index in this folder": "No hemos encontrado ningún archivo para indexar en la carpeta", + "We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.": "¡Hemos añadido algunas mejoras increíbles para bloop! Para seguir disfrutando de todas las funcionalidades, incluida la función de búsqueda de lenguaje natural, actualice la aplicación a la última versión.", + "To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.": "Para actualizar la aplicación, visite nuestra página de lanzamientos en GitHub y descargue la última versión manualmente. Gracias por usar bloop.", + "View in {{viewer}}": "Ver en {{viewer}}", + "Open file": "Abrir documento", + "Edit": "Editar", + "Click to copy": "Haga clic para copiar", + "Editing a previously submitted question will discard all answers and questions following it.": "La edición de una pregunta enviada anteriormente descartará todas las respuestas y preguntas posteriores.", + "Copy link": "Copiar link", + "Search using RegExp": "Buscar con RegExp", + "We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub": "Hemos actualizado nuestro servicio de autores para que el Bloop sea más seguro, por favor reanice a su cliente con GitHub", + "Loading...": "Cargando...", + "New code studio": "Nuevo estudio de código", + "Rename": "Rebautizar", + "Last modified": "Última modificación", + "Studio project": "Proyecto de estudio", + "Use generative AI with a user defined context": "Utilice AI generativo con un contexto definido por el usuario", + "All studio projects": "Todos los proyectos de estudio", + "Give a short descriptive name for your new code studio.": "Dé un breve nombre descriptivo para su nuevo estudio de código.", + "Name": "Nombre", + "Context files": "Archivos de contexto", + "Studio Projects": "Proyectos de estudio", + "# of #_one": "{{count}} de {{total}}", + "# of #_other": "{{count}} de {{total}}", + "Add file": "Agregar archivo", + "Studio conversation": "Conversación de estudio", + "My templates": "Mis plantillas", + "Use templates": "Usar plantillas", + "User": "Usuaria", + "Assistant": "Asistente", + "Start typing...": "Empiece a escribir ...", + "View history": "Ver historial", + "Clear conversation": "Borrar conversación", + "Generate": "Generar", + "In Studio Projects you can use generative AI with a user defined context to get more accurate responses. Press <2><0><1> to search for a files or press <6>Open in Studio when creating semantic searches to open in a Studio Project.": "En los proyectos de estudio, puede usar IA generativa con un contexto definido por el usuario para obtener respuestas más precisas. Presione <2> <0> <1> para buscar un archivo o presione <6> Abra en Studio al crear búsquedas semánticas para abrir en un proyecto de estudio.", + "Navigate": "Navegar", + "Add context file": "Agregar archivo de contexto", + "Select repository": "Seleccionar repositorio", + "Search repository": "Buscar repositorio", + "Select": "Seleccionar", + "Search branch...": "Buscar rama...", + "Search file...": "Buscar archivo...", + "Tip: Select code to create ranges for context use.": "Consejo: seleccione código para crear rangos para uso del contexto.", + "Whole file": "Archivo completo", + "Lines # - #": "Líneas {{start}} - {{end}}", + "# ranges_one": "{{count}} ranges", + "# ranges_other": "{{count}} ranges", + "Only the selected lines (# - #) will be used as context.": "Solo las líneas seleccionadas ({{start}} - {{end}}) se utilizarán como contexto.", + "Clear ranges": "Borrar rangos", + "Only the selected ranges will be used as context.": "Solo los rangos seleccionados se utilizarán como contexto.", + "Remove related files": "Eliminar archivos relacionados", + "Imported files": "Archivos importados", + "Referencing target file": "Referencia al archivo objetivo", + "Clear range": "Borrar rango", + "Use file": "Usar archivo", + "Add related files": "Agregar archivos relacionados", + "Add related file": "Agregar archivo relacionado", + "Select branch": "Seleccionar rama", + "Select file": "Seleccionar archivo", + "Use": "Usar", + "Restore": "Restaurar", + "Hide file": "Ocultar archivo", + "Remove file": "Remover archivo", + "Show file": "Mostrar archivo", + "Search repos or Studio projects...": "Buscar repositorios o proyectos de Studio...", + "All": "Toda", + "StudioProjects": "EstudioProyectos", + "View all": "Ver todo", + "+ # more_one": "+ {{count}} más", + "+ # more_other": "+ {{count}} más", + "No Studio projects": "Sin proyectos de estudio", + "Repositories": "Repositorios", + "As soon as you create a new Studio project it will appear here.": "Tan pronto como cree un nuevo proyecto de Studio, aparecerá aquí.", + "Add to Studio context": "Agregar al contexto de Studio", + "Add context": "Agregar contexto", + "Add context in a new Studio project or add it to an existing one.": "Agregue contexto en un nuevo proyecto de Studio o agréguelo a uno existente.", + "New Studio Project": "Nuevo proyecto de estudio", + "Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}": "Explique el propósito del archivo {{filePath}}, de las líneas {{lineStart}} - {{lineEnd}}", + "Create new Studio Project with context": "Crear un nuevo proyecto de estudio con contexto", + "Retry": "Rever", + "Use template": "Usar plantilla", + "Save to templates": "Guardar a las plantillas", + "Clear input": "Borrar entrada", + "Rename code studio": "Cambiar el nombre de Code Studio", + "Can’t open studio project": "No se puede abrir el proyecto de estudio", + "One or more repositories used in this studio project is being indexed. Try again when this process in complete.": "Se están indexando uno o más repositorios utilizados en este proyecto de estudio. Intente nuevamente cuando este proceso esté completo.", + "<0># of # tokens": "<0>{{count}} de {{total}} fichas", + "<0># of # tokens_one": "<0>{{count}} de {{total}} fichas", + "<0># of # tokens_other": "<0>{{count}} de {{total}} fichas", + "Templates": "Plantillas", + "No related files found": "No se encontraron archivos relacionados", + "Unavailable": "Indisponible", + "Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.": "Límite de token excedido. Reduzca la cantidad de archivos o mensajes de contexto para habilitar la capacidad de generar.", + "Invert": "Invertir", + "uses left_one": "usos restantes", + "uses left_other": "usos restante", + "Upgrade": "Actualizar", + "Your quota resets every 24 hours, upgrade for unlimited uses": "Tu cuota se restablece cada 24 horas, actualiza para solicitudes ilimitadas", + "Manage subscription": "Administrar suscripción", + "Usage resets in": "Restablecimiento de uso en", + "This file is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.": "Este archivo no está disponible actualmente. La capacidad de generar se reanudará tan pronto como se resuelva este problema.", + "Search code studio...": "Código de búsqueda Studio ...", + "Faster answers may impact the quality of results": "Las respuestas más rápidas pueden afectar la calidad de los resultados", + "Normal": "Normal", + "Answer speed": "Velocidad de respuesta", + "Fast": "Rápida", + "Code Studio": "Estudio de código", + "Watch": "Mirar", + "Code Studio helps hobbyists and engineers working on the largest codebases, alike, to collaborate with AI. We recommend watching the guide to maximise your productivity.": "Estudio de código ayuda a los aficionados e ingenieros que trabajan en las bases de código más grandes, por igual, a colaborar con IA. Recomendamos ver la guía para maximizar su productividad.", + "Open in new tab": "Abrir en una pestaña nueva", + "Show link": "Mostrar enlace", + "or go to the following link": "o ir al siguiente enlace", + "Email is required": "Se requiere correo electrónico", + "Last name is required": "Se requiere apellido", + "First name is required": "Se requiere el primer nombre", + "Connect GitHub account to continue": "Conecte la cuenta GitHub para continuar", + "Save context changes before answer generation": "Guardar cambios de contexto antes de la generación de respuesta", + "Index": "Índice", + "File not indexed": "Archivo no indexado", + "Force index": "Índice de fuerza", + "bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.": "Bloop excluye automáticamente ciertos archivos de la indexación. Este archivo puede ser demasiado grande o podría tener un tipo de archivo excluido.", + "Recommended: The classic response type": "Recomendado: El tipo de respuesta clásico", + "Experimental: Faster but less accurate": "Experimental: Más rápido pero menos preciso", + "What would you like to know about <2>#repo?": "¿Qué te gustaría saber sobre <2>{{repoName}}?", + "Hi, I'm bloop.": "Hola, soy bloop!", + "Done": "Hecho", + "Send a message": "Envía un mensaje", + "Token limit exceeded": "Límite de token excedido", + "bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.": "Bloop excluye automáticamente ciertos archivos de la indexación. Este archivo puede ser demasiado grande o podría tener un tipo de archivo excluido.", + "We use analytics to improve your experience. Please refresh the page after changing the value.": "Utilizamos análisis para mejorar su experiencia. Actualice la página después de cambiar el valor.", + "Allow analytics": "Permitir análisis", + "Complete your transaction in Stripe...": "Complete su transacción en Stripe ...", + "Launch manually": "Lanzar manualmente", + "Check payment status": "Verificar el estado de pago", + "No change in payment status identified.": "No hay cambio en el estado de pago identificado.", + "We've redirected you to Stripe to complete your transaction. Didn't work?": "Te hemos redirigido a rayar para completar su transacción. ¿No funcionó?", + "You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage": "Se ha quedado sin uso gratuito para hoy, espere a que su cuota se reinicie o actualice para un uso ilimitado", + "Unlimited usage and premium features are activated.": "Se activan el uso ilimitado y las características premium.", + "You've upgraded your account!": "¡Has actualizado tu cuenta!", + "Let's go": "Vamos", + "Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.": "Límite de token alcanzado, esta respuesta puede estar incompleta. Para generar una respuesta completa, reduzca el número de tokens utilizados y regenerados." +} diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json new file mode 100644 index 0000000000..7e77457e04 --- /dev/null +++ b/client/src/locales/ja.json @@ -0,0 +1,395 @@ +{ + "Documentation": "ドキュメント", + "Showing # result_zero": "検索に一致するものはありませんでした。 別の検索を試してみてください!", + "Showing # result_one": "{{count}} 件の結果を表示します", + "Showing # result_other": "{{count}} 件の結果を表示します", + "bloop crashed unexpectedly": "bloopが予期せずクラッシュしました", + "By submitting this crash report you agree to send it to bloop for investigation.": "このクラッシュレポートを送信すると、調査のためにbloopに送信することに同意したことになります。", + "Close": "閉じる", + "Describe the bug to help us reproduce it...": "再現できるようにバグを説明してください...", + "Discord": "Discord", + "Email address": "メールアドレス", + "Email is not valid": "メールアドレスは無効です", + "Full name": "フルネーム", + "General": "一般", + "Got it!": "理解した!", + "Offline": "オフライン", + "Online": "オンライン", + "Preferences": "設定", + "Problem details and System configuration": "問題の詳細とシステム構成", + "Provide any steps necessary to reproduce the problem...": "問題を再現するために必要な手順を提供してください...", + "Report a bug": "バグを報告する", + "Sign In": "サインイン", + "Sign in with GitHub": "GitHubでサインインする", + "Status": "ステータス", + "Submit bug report": "バグレポートを送信する", + "Submit crash report": "クラッシュレポートを送信する", + "Thank you!": "ありがとうございます!", + "Use GitHub to sign in to your account": "GitHubを使用してアカウントにサインインする", + "We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.": "我々は、皆様にとって最高の体験となるよう努力しています。バグに遭遇した場合は、バグレポートをお送りください。我々のチームはできるだけ早く調査します。", + "We’ll investigate and reach out back soon if necessary.": "調査し、必要に応じてすぐにご連絡いたします。", + "Syncing repository": "リポジトリを同期しています", + "Public repository": "パブリックリポジトリ", + "key": "キー", + "Add a repository from your local machine": "ローカルマシンからリポジトリを追加する", + "Your GitHub repository": "あなたのGitHubリポジトリ", + "Any repository from your private GitHub account": "プライベートGitHubアカウントからのリポジトリ", + "Local repository": "ローカルリポジトリ", + "Any public repository hosted on GitHub": "GitHubでホストされているパブリックリポジトリ", + "Add": "追加", + "All repositories": "すべてのリポジトリ", + "Sign out": "サインアウト", + "Settings": "設定", + "Manage how you will be called in bloop": "bloop内での名前の呼ばれ方を管理します", + "First and last name": "名前", + "First name": "名", + "Your name": "あなたの名前", + "Last name": "姓", + "Your last name": "あなたの名字", + "Used to sign in, syncing and product updates": "サインイン、同期、製品の更新に使用されます", + "Email": "メール", + "Your email address": "あなたのメールアドレス", + "Save changes": "変更内容を保存", + "Theme": "テーマ", + "Select your interface color scheme": "インターフェースの配色を選択してください", + "System Preference": "システム優先", + "Atom One Dark Pro": "Atom One Dark Pro", + "Solarized Light": "Solarized Light", + "Default": "Default", + "VSCode Dark": "VSCode Dark", + "Abyss": "Abyss", + "Darcula": "Darcula", + "Dracula": "Dracula", + "Material": "Material", + "GitHub Dark": "GitHub Dark", + "Gruvbox Dark": "Gruvbox Dark", + "Kimbie": "Kimbie", + "Solarized Dark": "Solarized Dark", + "Tomorrow Night Blue": "Tomorrow Night Blue", + "Default Light": "Default Light", + "Monokai": "Monokai", + "Night Owl": "Night Owl", + "GitHub Light": "GitHub Light", + "Gruvbox Light": "Gruvbox Light", + "VSCode Light": "VSCode Light", + "Quiet Light": "Quiet Light", + "No repositories": "リポジトリはありません", + "As soon as you add a repository it will appear here.": "リポジトリを追加するとこちらに表示されます。", + "Last updated ": "最終更新", + "Re-sync": "再同期", + "Remove": "削除", + "Cloning...": "クローン中...", + "Queued...": "待ち...", + "Cancel": "キャンセル", + "Indexing...": "インデックス中...", + "We are syncing your repository to bloop. This might take a couple of minutes": "あなたのリポジトリをbloopに同期しています。少々を待ちください。", + "complete": "完了", + "Confirm": "確認", + "Cancelling...": "キャンセル中...", + "Cancelled": "キャンセル", + "cancelling": "キャンセル中", + "done": "終わり", + "cancelled": "キャンセル", + "syncing": "同期しています", + "indexing": "インデックス中です", + "Error": "エラー", + "Remote removed ": "リモートで削除されました", + "Not synced": "同期していません", + "Removed": "削除", + "Select any private repository you would like to sync": "同期したいプライベートリポジトリを選択してください", + "Private repository": "プライベートリポジトリ", + "Search repository...": "リポジトリを検索...", + "Alphabetically": "アルファベット順", + "Last updated": "最終更新", + "No results...": "結果が見つかりませんでした...", + "Nothing matched your search. Try a different combination!": "検索条件に一致するものが見つかりませんでした。別の条件を試してみてください!", + "Already synced": "同期済み", + "Sync": "同期", + "Scan a folder to sync it’s repositories.": "フォルダをスキャンしてリポジトリを同期する。.gitフォルダのあるディレクトリのみが同期されます", + "Select folder": "フォルダの選択", + "Sync local repositories": "ローカルリポジトリを同期する", + "Scan a folder": "フォルダをスキャンする", + "Change folder": "フォルダの変更", + "Select the folders you want to add to bloop. You can always sync, unsync or remove unwanted repositories later.": "bloopに追加したいフォルダを選択してください。いつでも同期や同期の解除、不要なリポジトリの削除ができます。", + "Repository url...": "リポジトリのURL ...", + "Sync repository": "リポジトリを同期", + "Paste a link to any public repository you would like to index.": "インデックスを作成したいパブリックリポジトリへのリンクを貼り付けてください", + "This is not a public repository / We couldn't find this repository": "パブリックリポジトリではありません / リポジトリは見つかりませんでした", + "Verifying access...": "アクセスの確認...", + "Synced": "同期済み", + "Files in": "ファイル", + "Files": "ファイル", + "Copy search query to clipboard": "検索クエリをクリップボードにコピーする", + "Search for code using regex": "正規表現を使用してコードを検索する", + "Clear search history": "検索履歴をクリアする", + "Search branches...": "ブランチを検索...", + "Switch branch": "ブランチを切り替える", + "Upgrade now": "今すぐアップグレードする", + "Upgrade plan": "プランをアップグレードする", + "to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.": "GitHubリポジトリ内の全てのブランチでコードをシームレスに探索できるようにし、コード発見能力を最大化してください。", + "GitHub Branches": "GitHubブランチ", + "Fold everything": "すべて折りたたむ", + "Reset filters": "フィルタをリセットする", + "Hide filters": "フィルタを非表示にする", + "Filter lang...": "言語を絞り込む...", + "Select all": "すべてを選択する", + "File Type": "ファイルの種類", + "View all results": "すべての結果を表示する", + "Query suggestions": "クエリの提案", + "Result suggestions": "結果の提案", + "Show fewer results": "より少ない結果を表示する", + "# match_one": "{{count}} 件", + "# match_other": "{{count}} 件", + "Today": "今日", + "Expand everything": "すべてを拡張する", + "Language": "言語", + "Results": "結果", + "No results": "結果がありません", + "Suggested combinations": "提案された条件", + "Show filters": "フィルタを表示する", + "Filters": "フィルタ", + "Apply filters": "フィルタを適用する", + "Show # more match_other": "さらに {{count}} 件を表示する", + "Show # more match_one": "さらに {{count}} 件を表示する", + "Show less": "表示を減らす", + "Previous page": "前のページ", + "Next page": "次のページ", + "Showing page {{page}} of {{totalPages}}": "{{totalPages}} ページ中 {{page}} ページ目を表示しています", + "Deselect all": "すべての選択を解除", + "File": "ファイル", + "Open in modal": "モーダルで開く", + "Open in full view": "フルページで開く", + "Open in sidebar": "サイドバーで開く", + "The line of code where identifier is defined": "識別子が定義されているコード行", + "The line of code where the identifier is referenced": "識別子が参照されているコードの行", + "We weren't able to identify any references at the moment": "現在、参照を識別することができませんでした", + "No references or definitions found": "参照や定義は見つかりません", + "Definitions": "定義", + "References": "参照", + "definition": "定義", + "reference": "参照", + "In this file": "ファイル内", + "All conversations": "すべての会話", + "Sorry, this repository is not ready for search": "申し訳ありませんが、このリポジトリは検索の準備ができていません", + "Wait for the repository to finish syncing and try again": "リポジトリの同期が終了するのを待ち、再試行してください", + "Create new": "新しく作る", + "Show": "表示", + "Hide": "非表示", + "Conversations": "会話", + "Delete": "消去", + "Answer Ready": "回答準備ができています", + "View": "意見", + "Reading ": "読む", + "avatar": "アバター", + "Submit": "送信", + "Save": "保存", + "Generating response...": "回答を生成中...", + "Responding...": "回答中...", + "Reading": "読む", + "Bad": "悪い", + "Good": "良い", + "Show more": "もっと見る", + "How would you rate this response?": "こちらの回答をどのように評価しますか?", + "What was the issue with this response? How could it be improved?": "こちらの回答の問題は何でしたか?どうすれば改善できますか?", + "Failed to get a response from OpenAI. Try again in a few moments.": "OpenAIからの回答を取得できませんでした。しばらくしてからもう一度試してください。", + "Bad response": "誤回答", + "Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}": "{{filePath}} の {{lineStart}}行 - {{lineEnd}}行について説明します", + "Select less code": "コード選択範囲を少なくしてください", + "Ask bloop": "bloopに聞く", + "Explain": "説明", + "Something went wrong": "何かがうまくいかなかった", + "chats in bloop": "bloopでチャット", + "Setup bloop": "bloopをセットアップする", + "Please log into your GitHub account to complete setup": "セットアップを完了するため、GitHubアカウントにログインしてください", + "Select color theme:": "カラーテーマを選択する", + "Connect account": "アカウントを接続する", + "Continue": "次へ", + "Back": "戻る", + "Code expires in {{tokenExpireIn}}": "コードは {{tokenExpireIn}} で期限切れになります", + "After launching the GitHub login window, you’ll need to perform the following steps:": "GitHubログインウィンドウを起動した後、次の手順を実行してください。", + "or visit: ": "または訪問:", + "Authorise bloop": "bloopを承認する", + "Enter the device code": "デバイスコードを入力してください", + "Note: GitHub OAuth login doesn't support granular repo or organisation level access. The token has a wide scope, but only repos you explicitly choose will be synced, and your account credentials are always stored locally.": "注:GitHub OAuthログインは、詳細なリポジトリや組織レベルのアクセスをサポートしていません。トークンには幅広い範囲がありますが、明示的に選択するリポジトリのみが同期され、アカウントの資格情報は常にローカルに保存されます。", + "Launch GitHub Login": "GitHubログインを開始する", + "Copy": "コピー", + "Copied": "コピーしました", + "Waiting for authentication...": "認証完了を待っています...", + "Code has expired": "コードの期限が切れました", + "Generate new code": "新しいコードを生成する", + "Relaunch GitHub auth": "GitHub認証を再開する", + "Disconnect": "切断する", + "Terms & conditions": "利用規約", + "By continuing you accept our": "継続することで、次のことを承認します。", + "and ": "と", + "Privacy policy": "プライバシーポリシー", + "Welcome to bloop": "bloopへようこそ", + "Unlock the value of your existing code, using AI": "AIを使用して既存のコードの価値を引き出します", + "Search code in natural language": "自然言語でコードを検索する", + "Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.": "ChatGPTに話すように、自然言語でコードベースについて質問します。リポジトリを同期して開始し、リポジトリを開いてチャットを開始します。", + "Generate code using AI": "AIを使用してコードを生成する", + "Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.": "Code Studioは、AIを使用してスクリプトを作成したり、ユニットテストを作成したり、問題をデバッグしたり、他に思いつくものを生成するのに役立ちます! リポジトリを同期し、Code Studioプロジェクトを作成します。", + "Got it": "理解しました", + "Precise code navigation": "正確なコードナビゲーション", + "Show search steps": "検索手順を表示する", + "Hide search steps": "検索手順を非表示にする", + "To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for being a valued user of our app.": "アプリを更新するには、GitHubのリリースページにアクセスして、最新バージョンを手動でダウンロードしてください。アプリの大切なユーザーになってくれてありがとう。", + "Update Required": "アップデートしてください", + "Restart the app": "アプリを再起動してください", + "or ": "または", + "visit the downloads page": "ダウンロードページにアクセスしてください", + "Open dropdown": "ドロップダウンを開く", + "Prompt guide": "プロンプトガイド", + "Like ChatGPT, bloop responds best to certain prompts. We’ve compiled a really quick guide on how better to prompt bloop.": "ChatGPTと同様に、bloopは特定のプロンプトに最もよく応答します。 bloopをプロンプトする方が良い方法に関する非常に簡単なガイドをまとめました。", + "Skip (Not recommended)": "スキップ(非推奨)", + "We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.": "ご質問に回答できませんでした。しばらくしてからもう一度質問するか、質問を変えてみてください。", + "Stop generating": "生成を停止する", + "Take a quick look": "ちょっと見る", + "Go back": "戻る", + "Repo home": "リポジトリホーム", + "Go forward": "進む", + "History": "履歴", + "Yesterday": "昨日", + "Regex search...": "正規表現検索...", + "Searching...": "検索中...", + "The folder is empty": "フォルダが空です", + "We haven't found any files to index in this folder": "このフォルダにインデックスを作成するファイルが見つかりませんでした", + "We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.": "私たちは、bloopにいくつかのエキサイティングな機能強化を行いました! 自然言語検索機能を含む完全な機能を継続するには、アプリを最新バージョンに更新してください。", + "To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.": "アプリを更新するには、GitHubのリリースページにアクセスして、最新バージョンを手動でダウンロードしてください。 bloopを使用していただきありがとうございます。", + "View in {{viewer}}": "{{viewer}}で表示", + "Open file": "ファイルを開く", + "Edit": "編集", + "Editing a previously submitted question will discard all answers and questions following it.": "以前に提出された質問を編集すると、それに続くすべての回答と質問が破棄されます。", + "Click to copy": "クリックしてコピーします", + "Copy link": "リンクをコピーする", + "Search using RegExp": "正規表現を使用して検索します", + "We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub": "bloopをより安全にするために認証サービスを更新しました。GitHubでクライアントを再承認してください", + "Loading...": "読み込み中...", + "Give a short descriptive name for your new code studio.": "新しいコードスタジオに短い説明的な名前を付けてください。", + "Last modified": "最終更新日", + "Studio project": "スタジオプロジェクト", + "All studio projects": "すべてのスタジオプロジェクト", + "Use generative AI with a user defined context": "ユーザー定義のコンテキストで生成AIを使用します", + "Name": "名前", + "Context files": "コンテキストファイル", + "Studio Projects": "スタジオプロジェクト", + "Rename": "名前を変更します", + "# of #_one": "{{count}}の{{total}}", + "# of #_other": "{{count}}の{{total}}", + "Add file": "ファイルを追加します", + "Studio conversation": "スタジオの会話", + "My templates": "私のテンプレート", + "Use templates": "テンプレートを使用します", + "User": "ユーザー", + "Assistant": "アシスタント", + "Start typing...": "タイピングを開始します...", + "View history": "履歴を表示します", + "Clear conversation": "明確な会話", + "Generate": "生成する", + "In Studio Projects you can use generative AI with a user defined context to get more accurate responses. Press <2><0><1> to search for a files or press <6>Open in Studio when creating semantic searches to open in a Studio Project.": "Studioプロジェクトでは、ユーザー定義のコンテキストで生成AIを使用して、より正確な応答を取得できます。 <2> <0> <1> を押して、ファイルを検索するか、Studio で開くファイルを検索するか、スタジオプロジェクトで開くセマンティック検索を作成します。", + "Navigate": "ナビゲートします", + "Add context file": "コンテキストファイルを追加します", + "Select repository": "リポジトリを選択します", + "Search repository": "リポジトリを検索します", + "Select": "選択する", + "Search branch...": "ブランチを検索...", + "Select file": "ファイルを選ぶ", + "Tip: Select code to create ranges for context use.": "ヒント:コードを選択して、コンテキスト使用の範囲を作成します。", + "Whole file": "ファイル全体", + "Lines # - #": "行{{start}} - {{end}}", + "# ranges_one": "{{count}}つの範囲", + "# ranges_other": "{{count}}つの範囲", + "Only the selected lines (# - #) will be used as context.": "選択した行({{start}} - {{end}})のみがコンテキストとして使用されます。", + "Clear ranges": "クリア範囲", + "Only the selected ranges will be used as context.": "選択した範囲のみがコンテキストとして使用されます。", + "Remove related files": "関連ファイルを削除します", + "Imported files": "インポートされたファイル", + "Referencing target file": "ターゲットファイルの参照", + "Clear range": "クリア範囲", + "Use file": "ファイルを使用します", + "Add related files": "関連ファイルを追加します", + "Add related file": "関連ファイルを追加します", + "Select branch": "ブランチを選択", + "Search file...": "ファイルを検索...", + "Use": "使用", + "Restore": "復元する", + "Hide file": "ファイルを隠す", + "Remove file": "ファイルを削除する", + "Show file": "ファイルを表示", + "Search repos or Studio projects...": "リポジトリまたは Studio プロジェクトを検索...", + "All": "全て", + "Repositories": "リポジトリ", + "View all": "すべて見る", + "+ # more_one": "+ さらに {{count}} つ", + "+ # more_other": "+ さらに {{count}} つ", + "As soon as you create a new Studio project it will appear here.": "新しい Studio プロジェクトを作成するとすぐに、ここに表示されます。", + "No Studio projects": "Studio プロジェクトはありません", + "Add to Studio context": "Studio コンテキストに追加", + "Add context": "コンテキストを追加する", + "New Studio Project": "新しいスタジオプロジェクト", + "Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}": "ファイル {{filePath}} の目的を {{lineStart}} から {{lineEnd}} 行まで説明します。", + "Add context in a new Studio project or add it to an existing one.": "新しい Studio プロジェクトにコンテキストを追加するか、既存のプロジェクトにコンテキストを追加します。", + "Create new Studio Project with context": "コンテキストを使用して新しい Studio プロジェクトを作成する", + "Retry": "リトライ", + "Use template": "テンプレートを使用します", + "Save to templates": "テンプレートに保存します", + "Clear input": "入力をクリアします", + "Rename code studio": "コードスタジオの名前を変更します", + "Can’t open studio project": "スタジオプロジェクトを開くことができません", + "One or more repositories used in this studio project is being indexed. Try again when this process in complete.": "このスタジオプロジェクトで使用される1つ以上のリポジトリは、インデックス付けされています。 このプロセスが完全になったら再試行してください。", + "<0># of # tokens": "{{total}} トークン中 <0>{{count}} 個", + "<0># of # tokens_one": "{{total}} トークン中 <0>{{count}} 個", + "<0># of # tokens_other": "{{total}} トークン中 <0>{{count}} 個", + "Templates": "テンプレート", + "No related files found": "関連するファイルは見つかりません", + "Unavailable": "利用できません", + "Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.": "トークンの制限が超えました。 コンテキストファイルまたはメッセージの数を減らして、生成する機能を有効にします。", + "Invert": "反転", + "uses left_one": "回数", + "uses left_other": "回数", + "Upgrade": "アップグレード", + "Your quota resets every 24 hours, upgrade for unlimited uses": "あなたのクオータは24時間ごとにリセットされます、無制限のリクエストにアップグレードしてください", + "Manage subscription": "サブスクリプションを管理します", + "You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage": "今日は無料の使用法がなくなっています。クォータがリセットまたはアップグレードされるのを待ってください。", + "This file is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.": "このファイルは現在利用できません。 生成能力は、この問題が解決されるとすぐに再開されます。", + "Search code studio...": "コードスタジオを検索...", + "Code Studio": "コードスタジオ", + "Watch": "時計", + "Code Studio helps hobbyists and engineers working on the largest codebases, alike, to collaborate with AI. We recommend watching the guide to maximise your productivity.": "コードスタジオは、大規模なコードベースに取り組んでいる愛好家やエンジニアが同様に AI と共同作業できるよう支援します。 生産性を最大限に高めるために、ガイドを参照することをお勧めします。", + "Open in new tab": "新しいタブで開きます", + "Faster answers may impact the quality of results": "推奨:クラシックなレスポンスタイプ", + "Recommended: The classic response type": "推奨:クラシックなレスポンスタイプ", + "Experimental: Faster but less accurate": "実験的:速度は速いが精度は低い", + "Normal": "普通", + "Answer speed": "回答速度", + "Fast": "速い", + "Show link": "リンクを表示", + "or go to the following link": "または、次のリンクに移動します", + "Email is required": "メールが必要です", + "First name is required": "名が必要です", + "Last name is required": "姓が必要です", + "Connect GitHub account to continue": "githubアカウントを接続して続行します", + "Save context changes before answer generation": "回答生成の前にコンテキストの変更を保存します", + "Index": "索引", + "File not indexed": "ファイルはインデックス化されていません", + "Force index": "フォースインデックス", + "bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.": "Bloopは、インデックスから特定のファイルを自動的に除外します。 このファイルが大きすぎるか、除外されたファイルタイプがある場合があります。", + "What would you like to know about <2>#repo?": "<2>{{repoName}}について何を知りたいですか?", + "Hi, I'm bloop.": "こんにちは、私の名前はbloopです。", + "Done": "終わり", + "Send a message": "メッセージを送る", + "Token limit exceeded": "トークンの制限が超えました", + "bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.": "Bloopは、インデックスから特定のファイルを自動的に除外します。 このファイルが大きすぎるか、除外されたファイルタイプがある場合があります。", + "Allow analytics": "分析を許可します", + "We use analytics to improve your experience. Please refresh the page after changing the value.": "分析を使用して体験を向上させます。 値を変更した後、ページを更新してください。", + "Complete your transaction in Stripe...": "ストライプでトランザクションを完了してください...", + "We've redirected you to Stripe to complete your transaction. Didn't work?": "トランザクションを完了するために、ストライプにリダイレクトしました。 うまくいかなかった?", + "Check payment status": "支払いステータスを確認してください", + "No change in payment status identified.": "支払いステータスの変更は特定されていません。", + "Launch manually": "手動で起動します", + "Usage resets in": "使用法がリセットされます", + "You've upgraded your account!": "アカウントをアップグレードしました!", + "Unlimited usage and premium features are activated.": "無制限の使用機能とプレミアム機能がアクティブになります。", + "Let's go": "さあ行こう", + "Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.": "到達したトークン制限は、この答えが不完全になる可能性があります。 完全な回答を生成するには、使用して再生するトークンの数を減らしてください。" +} diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json new file mode 100644 index 0000000000..a6478fad27 --- /dev/null +++ b/client/src/locales/zh-CN.json @@ -0,0 +1,403 @@ +{ + "Documentation": "文档", + "Showing # result": "显示 {{count}} 结果", + "Showing # result_zero": "您的搜索没有匹配结果。尝试不同的组合!", + "Showing # result_one": "显示 {{count}} 结果", + "Showing # result_other": "显示 {{count}} 个结果", + "bloop crashed unexpectedly": "bloop意外崩溃", + "By submitting this crash report you agree to send it to bloop for investigation.": "提交此崩溃报告即表示您同意将其发送至bloop以进行调查。", + "Close": "关闭", + "Describe the bug to help us reproduce it...": "描述错误以帮助我们复现它...", + "Discord": "Discord", + "Email address": "电子邮件地址", + "Email is not valid": "电子邮件无效", + "Full name": "全名", + "General": "常规", + "Got it!": "明白了!", + "Offline": "离线", + "Online": "在线", + "Preferences": "偏好设置", + "Problem details and System configuration": "问题详情和系统配置", + "Provide any steps necessary to reproduce the problem...": "提供任何必要的步骤以复现问题...", + "Report a bug": "报告一个错误", + "Sign In": "登录", + "Sign in with GitHub": "使用GitHub登录", + "Status": "状态", + "Submit bug report": "提交错误报告", + "Submit crash report": "提交崩溃报告", + "Thank you!": "谢谢!", + "Use GitHub to sign in to your account": "使用 GitHub 登录您的账户", + "We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.": "我们希望为您提供最好的体验。如果您遇到了错误,请向我们提交错误报告。我们的团队将会尽快调查。", + "We’ll investigate and reach out back soon if necessary.": "如有必要,我们会进行调查并尽快联系您。", + "Syncing repository": "同步仓库", + "Public repository": "公开仓库", + "key": "键", + "Add a repository from your local machine": "从您的本地机器添加一个仓库", + "Your GitHub repository": "您的 GitHub 仓库", + "Any repository from your private GitHub account": "您的私有 GitHub 仓库", + "Local repository": "本地仓库", + "Any public repository hosted on GitHub": "任何托管在 GitHub 上的公开仓库", + "Add": "添加", + "All repositories": "所有仓库", + "Sign out": "退出登录", + "Settings": "设置", + "Manage how you will be called in bloop": "管理 bloop 中如何称呼您", + "First and last name": "姓和名", + "First name": "名", + "Your name": "您的名字", + "Last name": "姓", + "Your last name": "您的姓氏", + "Used to sign in, syncing and product updates": "用于登录、同步和产品更新", + "Email": "电子邮件", + "Your email address": "您的电子邮件地址", + "Save changes": "保存更改", + "Theme": "主题", + "Select your interface color scheme": "选择您的界面颜色方案", + "System Preference": "系统偏好设置", + "Atom One Dark Pro": "Atom One Dark Pro", + "Solarized Light": "Solarized 浅色主题", + "Default": "默认", + "VSCode Dark": "VSCode 暗色", + "Abyss": "深渊", + "Darcula": "Darcula", + "Dracula": "Dracula", + "Material": "Material", + "GitHub Dark": "GitHub 暗色", + "Gruvbox Dark": "Gruvbox 暗色", + "Kimbie": "Kimbie", + "Solarized Dark": "Solarized 深色主题", + "Tomorrow Night Blue": "Tomorrow Night 蓝色主题", + "Default Light": "默认浅色主题", + "Monokai": "Monokai", + "Night Owl": "Night Owl", + "GitHub Light": "GitHub 浅色", + "Gruvbox Light": "Gruvbox 浅色", + "VSCode Light": "VSCode 浅色", + "Quiet Light": "静谧浅色", + "No repositories": "无仓库", + "As soon as you add a repository it will appear here.": "一旦你添加了仓库,它将出现在这里。", + "Last updated ": "最后更新 ", + "Re-sync": "重新同步", + "Remove": "移除", + "Cloning...": "克隆中...", + "Queued...": "已进入队列...", + "Cancel": "取消", + "Indexing...": "索引中...", + "We are syncing your repository to bloop. This might take a couple of minutes": "我们正在将您的仓库同步到 bloop。这可能需要几分钟", + "complete": "完成", + "Confirm": "确认", + "Cancelling...": "正在取消...", + "Cancelled": "已取消", + "cancelling": "正在取消", + "done": "已完成", + "cancelled": "已取消", + "syncing": "同步中", + "indexing": "索引中", + "Error": "错误", + "Remote removed ": "已移除远程仓库", + "Not synced": "未同步", + "Removed": "已移除", + "Select any private repository you would like to sync": "选择您想要同步的任何私有仓库", + "Private repository": "私有仓库", + "Search repository...": "搜索仓库...", + "Alphabetically": "按字母顺序", + "Last updated": "最后更新", + "No results...": "无结果...", + "Nothing matched your search. Try a different combination!": "没有找到匹配您搜索的内容。尝试不同的组合吧!", + "Already synced": "已同步", + "Sync": "同步", + "Scan a folder to sync it’s repositories.": "扫描文件夹以同步其仓库。仅具有 .git 文件夹的目录才会同步", + "Select folder": "选择文件夹", + "Sync local repositories": "同步本地仓库", + "Scan a folder": "扫描文件夹", + "Change folder": "更改文件夹", + "Select the folders you want to add to bloop. You can always sync, unsync or remove unwanted repositories later.": "选择您希望添加至bloop的文件夹。您随时可以同步、解除同步或移除不需要的仓库。", + "Repository url...": "仓库URL...", + "Sync repository": "同步仓库", + "Paste a link to any public repository you would like to index.": "粘贴您希望索引的任何公开仓库的链接。", + "This is not a public repository / We couldn't find this repository": "这不是公开的仓库/我们找不到这个仓库", + "Verifying access...": "正在验证访问权限...", + "Synced": "已同步", + "Files in": "文件在", + "Files": "文件", + "Copy search query to clipboard": "复制搜索查询到剪贴板", + "Search for code using regex": "使用正则表达式搜索代码", + "Clear search history": "清除搜索历史", + "Search branches...": "搜索分支...", + "Switch branch": "切换分支", + "Upgrade now": "现在升级", + "Upgrade plan": "升级计划", + "to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.": "无缝地探索GitHub仓库中所有分支的代码,最大限度地提升您的代码发现能力。", + "GitHub Branches": "GitHub 分支", + "Fold everything": "折叠全部", + "Reset filters": "重置筛选", + "Hide filters": "隐藏筛选", + "Filter lang...": "筛选语言...", + "Select all": "全选", + "File Type": "文件类型", + "View all results": "查看所有结果", + "Query suggestions": "查询建议", + "Result suggestions": "结果建议", + "Show fewer results": "显示较少的结果", + "# match": "找到 {{count}} 个匹配", + "# match_one": "找到 {{count}} 个匹配", + "# match_other": "找到 {{count}} 个匹配", + "Today": "今天", + "Expand everything": "展开全部", + "Language": "语言", + "Results": "结果", + "No results": "无结果", + "Suggested combinations": "推荐的组合", + "Show filters": "显示筛选", + "Filters": "筛选", + "Apply filters": "应用筛选", + "Show # more match": "显示 {{count}} 更多匹配", + "Show # more match_one": "显示 {{count}} 更多匹配", + "Show # more match_other": "显示 {{count}} 更多匹配", + "Show less": "显示较少", + "Previous page": "上一页", + "Next page": "下一页", + "Showing page {{page}} of {{totalPages}}": "正在显示第{{page}}页,总共{{totalPages}}页", + "Deselect all": "取消全选", + "File": "文件", + "Open in sidebar": "在侧边栏打开", + "Open in full view": "在全屏视图中打开", + "Open in modal": "在模态窗口打开", + "The line of code where identifier is defined": "定义标识符的代码行", + "The line of code where the identifier is referenced": "引用标识符的代码行", + "We weren't able to identify any references at the moment": "我们目前无法找到任何引用", + "No references or definitions found": "找不到任何引用或定义", + "Definitions": "定义", + "References": "引用", + "definition": "定义", + "reference": "引用", + "In this file": "在此文件中", + "All conversations": "所有对话", + "Sorry, this repository is not ready for search": "抱歉,这个仓库还未准备好进行搜索", + "Wait for the repository to finish syncing and try again": "等待仓库完成同步后再试", + "Create new": "新会话", + "Show": "显示", + "Hide": "隐藏", + "Conversations": "对话", + "Delete": "删除", + "Answer Ready": "回答就绪", + "View": "查看", + "Reading ": "正在阅读 ", + "avatar": "头像", + "Submit": "提交", + "Save": "保存", + "Generating response...": "正在生成回答...", + "Responding...": "正在回答...", + "Reading": "阅读", + "Bad": "差", + "Good": "好", + "Show more": "显示更多", + "How would you rate this response?": "您如何评价这个回答?", + "What was the issue with this response? How could it be improved?": "这个回答有什么问题?怎样可以改进?", + "Failed to get a response from OpenAI. Try again in a few moments.": "从OpenAI获取回应失败。请稍后再试。", + "Bad response": "回应不好", + "Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}": "解释{{filePath}}中的第{{lineStart}}行到第{{lineEnd}}行", + "Select less code": "选择更少的代码", + "Ask bloop": "询问bloop", + "Explain": "解释", + "Something went wrong": "出错了", + "chats in bloop": "在bloop中对话", + "Setup bloop": "设置bloop", + "Please log into your GitHub account to complete setup": "请登录您的GitHub账户以完成设置", + "Select color theme:": "选择颜色主题:", + "Connect account": "连接账户", + "Continue": "继续", + "Back": "返回", + "Code expires in {{tokenExpireIn}}": "代码在{{tokenExpireIn}}后过期", + "After launching the GitHub login window, you’ll need to perform the following steps:": "启动GitHub登录窗口后,您需要执行以下步骤:", + "or visit: ": "或者访问: ", + "Authorise bloop": "授权bloop", + "Enter the device code": "输入设备代码", + "Note: GitHub OAuth login doesn't support granular repo or organisation level access. The token has a wide scope, but only repos you explicitly choose will be synced, and your account credentials are always stored locally.": "注意:GitHub OAuth登录不支持精细的仓库或组织级别的访问。token的范围很广,但只有您明确选择的仓库会被同步,您的账户凭据始终存储在本地。", + "Launch GitHub Login": "启动GitHub登录", + "Copy": "复制", + "Copied": "已复制", + "Waiting for authentication...": "正在等待身份验证...", + "Code has expired": "代码已过期", + "Generate new code": "生成新的代码", + "Relaunch GitHub auth": "重新启动GitHub授权", + "Disconnect": "断开连接", + "Terms & conditions": "条款和条件", + "By continuing you accept our": "继续即表示你接受我们的", + "and ": "和", + "Privacy policy": "隐私政策", + "Welcome to bloop": "欢迎来到bloop", + "Unlock the value of your existing code, using AI": "利用AI解锁您现有代码的价值", + "Search code in natural language": "用自然语言搜索代码", + "Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.": "用自然语言向您的代码库提问,就像您和ChatGPT对话一样。首先同步一个仓库,然后打开仓库开始聊天。", + "Generate code using AI": "使用AI生成代码", + "Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.": "代码工作室可以帮助您使用AI编写脚本、创建单元测试、调试问题或生成您能想到的任何其他内容!同步一个仓库,然后创建一个代码工作室项目。", + "Got it": "了解了", + "Precise code navigation": "精确的代码导航", + "Show search steps": "显示搜索步骤", + "Hide search steps": "隐藏搜索步骤", + "To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for being a valued user of our app.": "要更新您的应用,请访问我们在GitHub上的发布页面并手动下载最新版本。感谢您成为我们应用的重要用户。", + "Update Required": "需要更新", + "Restart the app": "重新启动应用", + "or ": "或者 ", + "visit the downloads page": "访问下载页面", + "Open dropdown": "打开下拉菜单", + "Prompt guide": "提示指南", + "Like ChatGPT, bloop responds best to certain prompts. We’ve compiled a really quick guide on how better to prompt bloop.": "像ChatGPT一样,bloop最好的响应是对某些提示的反馈。我们已经编制了一个非常快速的指南,以更好地提示bloop。", + "Skip (Not recommended)": "跳过(不建议)", + "We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.": "我们无法回答您的问题。您可以尝试在几分钟后再次提问,或者以不同的方式提问。", + "Stop generating": "停止生成", + "Take a quick look": "快速查看", + "Go back": "返回", + "Repo home": "仓库主页", + "Go forward": "前进", + "History": "历史", + "Yesterday": "昨天", + "Regex search...": "正则表达式搜索...", + "Searching...": "搜索中...", + "The folder is empty": "文件夹是空的", + "We haven't found any files to index in this folder": "我们没有在这个文件夹中找到任何可以索引的文件", + "We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.": "我们对bloop进行了一些激动人心的增强功能!为了继续享受全部功能,包括自然语言搜索功能,请更新您的应用到最新版本。", + "To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.": "要更新您的应用,请访问我们在GitHub上的发布页面并手动下载最新版本。感谢您使用bloop。", + "View in {{viewer}}": "在{{viewer}}中查看", + "Open file": "打开文件", + "Edit": "编辑", + "Editing a previously submitted question will discard all answers and questions following it.": "编辑之前提交的问题将丢弃其后的所有答案和问题。", + "Click to copy": "单击复制", + "Copy link": "复制链接", + "Search using RegExp": "使用RegExp搜索", + "We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub": "我们已经更新了我们的身份服务以使Bloop更加安全,请使用GitHub重新授权您的客户", + "Loading...": "加载...", + "New code studio": "新代码工作室", + "Last modified": "上一次更改", + "All studio projects": "所有工作室项目", + "Use generative AI with a user defined context": "将生成AI与用户定义的上下文一起使用", + "Give a short descriptive name for your new code studio.": "为您的新代码工作室提供一个简短的描述性名称。", + "Name": "姓名", + "Context files": "上下文文件", + "Studio Projects": "工作室项目", + "Studio project": "Studio Project", + "Rename": "改名", + "# of #": "{{total}} 中的 {{count}}", + "# of #_one": "{{total}} 中的 {{count}}", + "# of #_other": "{{total}} 中的 {{count}}", + "Add file": "添加文件", + "Studio conversation": "工作室对话", + "My templates": "我的模板", + "Use templates": "使用模板", + "User": "用户", + "Assistant": "助手", + "Start typing...": "开始打字...", + "View history": "查看历史", + "Clear conversation": "清除对话", + "Generate": "产生", + "In Studio Projects you can use generative AI with a user defined context to get more accurate responses. Press <2><0><1> to search for a files or press <6>Open in Studio when creating semantic searches to open in a Studio Project.": "在录音室项目中,您可以使用用户定义上下文的生成AI来获得更准确的响应。 按<2> <0> <1> > 搜索文件,或在创建语义搜索在Studio Project中打开时在Studio 中按<6>打开。", + "Navigate": "导航", + "Add context file": "添加上下文文件", + "Select repository": "选择存储库", + "Search repository": "搜索存储库", + "Select": "选择", + "Search branch...": "搜索分支...", + "Select file": "选择文件", + "Tip: Select code to create ranges for context use.": "提示:选择代码以创建范围以供上下文使用。", + "Whole file": "整个文件", + "Lines # - #": "行{{start}} - {{end}}", + "# ranges": "{{count}}个范围", + "# ranges_one": "{{count}}个范围", + "# ranges_other": "{{count}}个范围", + "Only the selected lines (# - #) will be used as context.": "仅选择的行({{start}} - {{end}})将被用作上下文。", + "Clear ranges": "清晰的范围", + "Only the selected ranges will be used as context.": "仅选择的范围将用作上下文。", + "Remove related files": "删除相关文件", + "Imported files": "导入文件", + "Referencing target file": "引用目标文件", + "Clear range": "清除范围", + "Use file": "使用文件", + "Add related files": "添加相关文件", + "Add related file": "添加相关文件", + "Select branch": "选择分行", + "Search file...": "搜索文件...", + "Use": "使用", + "Restore": "恢复", + "Hide file": "隐藏文件", + "Remove file": "删除文件", + "Show file": "显示文件", + "Search repos or Studio projects...": "搜索存储库或 Studio 项目...", + "All": "全部", + "StudioProjects": "工作室项目", + "View all": "查看全部", + "+ # more": "+ {{count}} 更多", + "+ # more_other": "+ {{count}} 更多", + "No Studio projects": "没有工作室项目", + "Repositories": "存储库", + "As soon as you create a new Studio project it will appear here.": "一旦您创建了新的 Studio 项目,它就会出现在此处。", + "Add context": "添加上下文", + "Add context in a new Studio project or add it to an existing one.": "在新的 Studio 项目中添加上下文或将其添加到现有项目中。", + "New Studio Project": "新工作室项目", + "Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}": "解释文件 {{filePath}} 的用途,从第 {{lineStart}} - {{lineEnd}} 行开始", + "Add to Studio context": "添加到 Studio 上下文", + "Create new Studio Project with context": "使用上下文创建新的 Studio 项目", + "Retry": "重试", + "Use template": "使用模板", + "Save to templates": "保存到模板", + "Clear input": "清除输入", + "Rename code studio": "重命名代码工作室", + "One or more repositories used in this studio project is being indexed. Try again when this process in complete.": "该工作室项目中使用的一个或多个存储库正在索引。 完成此过程时,请重试。", + "Can’t open studio project": "无法打开录音室项目", + "<0># of # tokens": "{{total}}个代币中的 <0>{{count}} 个", + "<0># of # tokens_one": "{{total}}个代币中的 <0>{{count}} 个", + "<0># of # tokens_other": "{{total}}个代币中的 <0>{{count}} 个", + "Templates": "模板", + "No related files found": "找不到相关文件", + "Unavailable": "不可用", + "Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.": "令牌限制超过了。 减少上下文文件的数量或消息,以实现生成能力。", + "Invert": "倒置", + "uses left": "使用左侧", + "uses left_one": "使用左侧", + "uses left_other": "使用左侧", + "Upgrade": "升级", + "Your quota resets every 24 hours, upgrade for unlimited uses": "您的配额每24小时重置一次,升级以获得无限请求", + "Manage subscription": "管理订阅", + "You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage": "您今天的免费使用时间用完了,请等待您的配额重置或升级无限制", + "This file is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.": "此文件当前不可用。 一旦解决此问题,就会恢复产生的能力。", + "Search code studio...": "搜索代码工作室...", + "Code Studio": "代码工作室", + "Code Studio helps hobbyists and engineers working on the largest codebases, alike, to collaborate with AI. We recommend watching the guide to maximise your productivity.": "代码工作室帮助业余爱好者和从事最大代码库工作的工程师等与人工智能进行协作。 我们建议您观看该指南以最大限度地提高您的工作效率。", + "Watch": "手表", + "Open in new tab": "在新标签中打开", + "Faster answers may impact the quality of results": "更快的答案可能会影响结果的质量", + "Recommended: The classic response type": "推荐:经典的回应类型", + "Experimental: Faster but less accurate": "实验性:更快但准确性较低", + "Answer speed": "答案速度", + "Normal": "普通的", + "Fast": "快速地", + "Show link": "显示链接", + "or go to the following link": "或转到以下链接", + "Email is required": "需要电子邮件", + "First name is required": "需要名字", + "Last name is required": "需要姓氏", + "Connect GitHub account to continue": "连接github帐户继续", + "Save context changes before answer generation": "在回答生成之前保存上下文更改", + "Index": "指数", + "Force index": "力索引", + "bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.": "Bloop自动将某些文件从索引中排除。 该文件可能太大,或者可能具有排除的文件类型。", + "What would you like to know about <2>#repo?": "你想了解<2>#repo的什么信息?", + "Hi, I'm bloop.": "你好,我是bloop。", + "Done": "已完成", + "Send a message": "发送消息", + "Token limit exceeded": "令牌限制超过了", + "bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.": "Bloop自动将某些文件从索引中排除。 该文件可能太大,或者可能具有排除的文件类型。", + "File not indexed": "文件未索引", + "Allow analytics": "允许分析", + "We use analytics to improve your experience. Please refresh the page after changing the value.": "我们使用分析来改善您的经验。 更改值后,请刷新页面。", + "Complete your transaction in Stripe...": "完成您的交易...", + "Launch manually": "手动启动", + "Check payment status": "检查付款状态", + "No change in payment status identified.": "未确定付款状态的变化。", + "We've redirected you to Stripe to complete your transaction. Didn't work?": "我们将您重定向到条纹以完成您的交易。 不起作用吗?", + "Usage resets in": "用法重置", + "Let's go": "我们走吧", + "You've upgraded your account!": "您已经升级了您的帐户!", + "Unlimited usage and premium features are activated.": "无限用法和高级功能被激活。", + "Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.": "达到令牌限制,此答案可能不完整。 要产生完整的答案,请减少使用和再生的代币数量。" +} diff --git a/client/src/pages/StudioTab/RightPanel/Conversation/Input.tsx b/client/src/pages/StudioTab/RightPanel/Conversation/Input.tsx index 8e41492c3b..a0429c3e34 100644 --- a/client/src/pages/StudioTab/RightPanel/Conversation/Input.tsx +++ b/client/src/pages/StudioTab/RightPanel/Conversation/Input.tsx @@ -14,6 +14,7 @@ import { Trans, useTranslation } from 'react-i18next'; import Button from '../../../../components/Button'; import { ArrowRotate, + Info, PenUnderline, Sparkles, Template, @@ -40,6 +41,8 @@ type Props = { scrollToBottom?: () => void; inputRef?: React.MutableRefObject; setLeftPanel: Dispatch>; + isTokenLimitExceeded: boolean; + isLast: boolean; }; const ConversationInput = ({ @@ -51,6 +54,8 @@ const ConversationInput = ({ scrollToBottom, inputRef, setLeftPanel, + isLast, + isTokenLimitExceeded, }: Props) => { const { t } = useTranslation(); const { envConfig } = useContext(DeviceContext); @@ -244,7 +249,25 @@ const ConversationInput = ({ /> ) : ( - + <> + + {author === StudioConversationMessageAuthor.ASSISTANT && + isLast && + isTokenLimitExceeded && ( +
+ + + Token limit reached, this answer may be incomplete. To + generate a full answer, please reduce the number of tokens + used and regenerate. + +
+ )} + )} diff --git a/client/src/pages/StudioTab/RightPanel/Conversation/index.tsx b/client/src/pages/StudioTab/RightPanel/Conversation/index.tsx index 77bf0da771..f3f5fd4c83 100644 --- a/client/src/pages/StudioTab/RightPanel/Conversation/index.tsx +++ b/client/src/pages/StudioTab/RightPanel/Conversation/index.tsx @@ -383,6 +383,8 @@ const Conversation = ({ onMessageRemoved={onMessageRemoved} i={i} setLeftPanel={setLeftPanel} + isTokenLimitExceeded={isTokenLimitExceeded} + isLast={i === conversation.length - 1} /> ))} {!isLoading && @@ -399,6 +401,8 @@ const Conversation = ({ scrollToBottom={scrollToBottom} inputRef={inputRef} setLeftPanel={setLeftPanel} + isTokenLimitExceeded={isTokenLimitExceeded} + isLast /> )} diff --git a/flake.lock b/flake.lock index 218a776f41..d90444edec 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "owner": "numtide", "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1690272529, - "narHash": "sha256-MakzcKXEdv/I4qJUtq/k/eG+rVmyOZLnYNC2w1mB59Y=", + "lastModified": 1696879762, + "narHash": "sha256-Ud6bH4DMcYHUDKavNMxAhcIpDGgHMyL/yaDEAVSImQY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ef99fa5c5ed624460217c31ac4271cfb5cb2502c", + "rev": "f99e5f03cc0aa231ab5950a15ed02afec45ed51a", "type": "github" }, "original": { @@ -34,10 +34,27 @@ "type": "github" } }, + "nixpkgs2305": { + "locked": { + "lastModified": 1696983906, + "narHash": "sha256-L7GyeErguS7Pg4h8nK0wGlcUTbfUMDu+HMf1UcyP72k=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "bd1cde45c77891214131cbbea5b1203e485a9d51", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "nixpkgs2305": "nixpkgs2305" } }, "systems": { diff --git a/flake.nix b/flake.nix index 411c6fa252..c87232e1ef 100644 --- a/flake.nix +++ b/flake.nix @@ -9,13 +9,15 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nixpkgs2305.url = "github:nixos/nixpkgs/nixos-23.05"; flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: + outputs = { self, nixpkgs, nixpkgs2305, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; + pkgsStable = import nixpkgs2305 { inherit system; }; pkgsStatic = pkgs.pkgsStatic; lib = pkgs.lib; @@ -146,8 +148,8 @@ }).overrideAttrs (old: envVars); onnxruntime14 = import ./nix/onnxruntime.nix { - inherit pkgs; inherit (llvm) stdenv; + pkgs = pkgsStable; }; frontend = (pkgs.buildNpmPackage rec { diff --git a/helm/bloop/templates/deployment.yaml b/helm/bloop/templates/deployment.yaml index a656e6fbff..d3dc9497f7 100644 --- a/helm/bloop/templates/deployment.yaml +++ b/helm/bloop/templates/deployment.yaml @@ -70,7 +70,7 @@ spec: - --cognito-mgmt-url={{ .Values.bloop.cognitoMgmtUrl }} - --cognito-auth-url={{ .Values.bloop.cognitoAuthUrl }} - --bloop-instance-secret={{ .Values.bloop.bloopInstanceSecret }} - - --bloop-instance-org={{ .Values.bloop.bloopInstanceOrg }} + - --bloop-instance-org={{ .Values.bloop.githubOrgName }} - --analytics-key={{ .Values.bloop.analyticsKey }} - --analytics-data-plane={{ .Values.bloop.analyticsDataPlane }} - --sentry-dsn={{ .Values.bloop.sentryDsn }} diff --git a/helm/bloop/templates/secret.yaml b/helm/bloop/templates/secret.yaml index 390cfbf971..2744c2958b 100644 --- a/helm/bloop/templates/secret.yaml +++ b/helm/bloop/templates/secret.yaml @@ -4,6 +4,4 @@ metadata: name: {{ include "bloop.fullname" . }} labels: {{- include "bloop.labels" . | nindent 4 }} -type: Opaque -data: - github-app-private-key.pem: {{ .Values.bloop.githubAppPrivateKey | b64enc }} \ No newline at end of file +type: Opaque \ No newline at end of file diff --git a/helm/bloop/values.yaml b/helm/bloop/values.yaml index 46b11f938f..ca6a80de1d 100644 --- a/helm/bloop/values.yaml +++ b/helm/bloop/values.yaml @@ -7,10 +7,10 @@ bloop: githubOrgOwnerLastName: "" loopsAPIKey: "" loopsEventName: "" - githubAppPrivateKey: "" - githubAppInstallId: "" - githubClientId: "" - githubClientSecret: "" + cognitoUserpoolId: "" + cognitoClientId: "" + cognitoMgmtUrl: "" + cognitoAuthUrl: "" instanceDomain: "" answerApiUrl: "" embeddingServerUrl: "" diff --git a/nix/onnxruntime.nix b/nix/onnxruntime.nix index bc72085ee9..018b8bd3f5 100644 --- a/nix/onnxruntime.nix +++ b/nix/onnxruntime.nix @@ -1,13 +1,13 @@ { pkgs, stdenv }: stdenv.mkDerivation rec { pname = "onnxruntime"; - version = "1.14.0"; + version = "1.14.1"; src = pkgs.fetchFromGitHub { owner = "microsoft"; repo = "onnxruntime"; rev = "v${version}"; - sha256 = "sha256-Lm0AfUdr6EclNL/R3rPiA1o9qfsWH+f1Y0CX3JCFovo="; + sha256 = "sha256-cedOy9RIxtRszcpyL6/eX8r2u9nnTkK90/5IWgvZpKg="; fetchSubmodules = true; }; diff --git a/server/bleep/Cargo.toml b/server/bleep/Cargo.toml index 290c36edc6..bcc99a7db9 100644 --- a/server/bleep/Cargo.toml +++ b/server/bleep/Cargo.toml @@ -1,15 +1,16 @@ [package] name = "bleep" -version = "0.5.5" +version = "0.5.6" edition = "2021" default-run = "bleep" build = "build.rs" [features] -default = ["dynamic-ort"] +default = ["dynamic-ort", "ee-pro"] debug = ["console-subscriber", "histogram"] dynamic-ort = ["ort/load-dynamic"] -ee = [] +ee-pro = [] +ee-cloud = ["ee-pro"] [[bin]] name = "bleep" @@ -29,7 +30,8 @@ harness = false [dependencies] # core -tantivy = { version = "0.19.2", features = ["mmap"] } +tantivy = { version = "0.21.0", features = ["mmap"] } +tantivy-columnar = "0.2.0" tokio = { version = "1.32.0", features = ["macros", "process", "rt", "rt-multi-thread", "io-std", "io-util", "sync", "fs"] } futures = "0.3.28" rayon = "1.8.0" diff --git a/server/bleep/benches/queries.rs b/server/bleep/benches/queries.rs index 85c19957da..dcaa6e423e 100644 --- a/server/bleep/benches/queries.rs +++ b/server/bleep/benches/queries.rs @@ -25,7 +25,7 @@ const QUERIES: &[&str] = &[ r#"/.*/"#, r#"/[a-z]{4}/"#, r#"/--[a-zA-Z]/"#, - r#"/#[clap(short, long, default_value_t = \d{4})]/"#, + r"/#[clap(short, long, default_value_t = \d{4})]/", r#"symbol:handle"#, r#"symbol:res case:ignore"#, r#"path:src/comp symbol:handle"#, diff --git a/server/bleep/src/agent/tools/answer.rs b/server/bleep/src/agent/tools/answer.rs index e2d3789901..f45e5e5371 100644 --- a/server/bleep/src/agent/tools/answer.rs +++ b/server/bleep/src/agent/tools/answer.rs @@ -228,10 +228,7 @@ impl Agent { } }); - query - .into_iter() - .chain(conclusion.into_iter()) - .collect::>() + query.into_iter().chain(conclusion).collect::>() }) } diff --git a/server/bleep/src/analytics.rs b/server/bleep/src/analytics.rs index 68b2c24058..6763841a60 100644 --- a/server/bleep/src/analytics.rs +++ b/server/bleep/src/analytics.rs @@ -137,6 +137,7 @@ pub struct UserState { } impl RudderHub { + #[tracing::instrument(skip_all)] pub fn new_with_options( state: &StateSource, device_id: impl Into>, @@ -145,6 +146,8 @@ impl RudderHub { options: impl Into>, ) -> anyhow::Result> { let client = RudderAnalytics::load(key, data_plane); + tracing::debug!("client initialized"); + Ok(Self { client, options: options.into(), diff --git a/server/bleep/src/background.rs b/server/bleep/src/background.rs index 6c57512871..a277a041bd 100644 --- a/server/bleep/src/background.rs +++ b/server/bleep/src/background.rs @@ -87,7 +87,7 @@ impl BackgroundExecutor { .spawn_handler(move |thread| { let tokio_ref = tokio_ref.clone(); - let thread_priority = if cfg!(feature = "ee") { + let thread_priority = if cfg!(feature = "ee-cloud") { // 0-100 low-high // pick mid-range for worker threads so we don't starve other threads thread_priority::ThreadPriority::Crossplatform(49u8.try_into().unwrap()) diff --git a/server/bleep/src/background/sync.rs b/server/bleep/src/background/sync.rs index 58d880cdf6..eeab8a2534 100644 --- a/server/bleep/src/background/sync.rs +++ b/server/bleep/src/background/sync.rs @@ -324,8 +324,8 @@ impl SyncHandle { Some(creds) => creds, None => { let Some(path) = repo.local_path() else { - return Err(SyncError::NoKeysForBackend(backend)); - }; + return Err(SyncError::NoKeysForBackend(backend)); + }; if !self.app.allow_path(&path) { return Err(SyncError::PathNotAllowed(path)); diff --git a/server/bleep/src/collector/bytes_filter.rs b/server/bleep/src/collector/bytes_filter.rs index 89dde6fcbc..6cf34e0f84 100644 --- a/server/bleep/src/collector/bytes_filter.rs +++ b/server/bleep/src/collector/bytes_filter.rs @@ -1,7 +1,6 @@ // a version of tantivy::collector::FilterCollector that works on byte fast fields use tantivy::collector::{Collector, SegmentCollector}; -use tantivy::fastfield::BytesFastFieldReader; use tantivy::schema::Field; use tantivy::{Score, SegmentReader, TantivyError}; @@ -58,7 +57,8 @@ where ))); } - let fast_field_reader = segment_reader.fast_fields().bytes(self.field)?; + let field_name = schema.get_field_name(self.field); + let fast_field_reader = segment_reader.fast_fields().bytes(field_name)?.unwrap(); let segment_collector = self .collector @@ -87,7 +87,7 @@ pub struct BytesFilterSegmentCollector where TPredicate: 'static, { - fast_field_reader: BytesFastFieldReader, + fast_field_reader: tantivy_columnar::BytesColumn, segment_collector: TSegmentCollector, predicate: TPredicate, } @@ -101,8 +101,16 @@ where type Fruit = TSegmentCollector::Fruit; fn collect(&mut self, doc: u32, score: Score) { - let value = self.fast_field_reader.get_bytes(doc); - if (self.predicate)(value) { + let mut value = Vec::new(); + self.fast_field_reader + .ords() + .values_for_doc(doc) + .for_each(|ord| { + self.fast_field_reader + .ord_to_bytes(ord, &mut value) + .unwrap(); + }); + if (self.predicate)(&value) { self.segment_collector.collect(doc, score) } } diff --git a/server/bleep/src/collector/frequency.rs b/server/bleep/src/collector/frequency.rs index 40a9aa58bd..6b28d56758 100644 --- a/server/bleep/src/collector/frequency.rs +++ b/server/bleep/src/collector/frequency.rs @@ -2,10 +2,10 @@ use std::collections::HashMap; use tantivy::{ collector::{Collector, SegmentCollector}, - fastfield::BytesFastFieldReader, schema::Field, Score, SegmentReader, }; +use tantivy_columnar::BytesColumn; pub struct FrequencyCollector(pub Field); @@ -19,7 +19,8 @@ impl Collector for FrequencyCollector { _segment_local_id: u32, segment_reader: &SegmentReader, ) -> tantivy::Result { - let reader = segment_reader.fast_fields().bytes(self.0)?; + let field_name = segment_reader.schema().get_field_name(self.0); + let reader = segment_reader.fast_fields().bytes(field_name)?.unwrap(); Ok(FrequencySegmentCollector { reader, freqs: HashMap::new(), @@ -43,7 +44,7 @@ impl Collector for FrequencyCollector { } pub struct FrequencySegmentCollector { - reader: BytesFastFieldReader, + reader: BytesColumn, freqs: HashMap, usize>, } @@ -51,11 +52,11 @@ impl SegmentCollector for FrequencySegmentCollector { type Fruit = HashMap, usize>; fn collect(&mut self, doc: u32, _score: Score) { - let k = self.reader.get_bytes(doc); - self.freqs - .entry(k.to_owned()) - .and_modify(|v| *v += 1) - .or_insert(1); + let mut k = Vec::new(); + self.reader.ords().values_for_doc(doc).for_each(|ord| { + self.reader.ord_to_bytes(ord, &mut k).unwrap(); + }); + self.freqs.entry(k).and_modify(|v| *v += 1).or_insert(1); } fn harvest(self) -> ::Fruit { diff --git a/server/bleep/src/commits.rs b/server/bleep/src/commits.rs index 39e419fb62..809b9f7228 100644 --- a/server/bleep/src/commits.rs +++ b/server/bleep/src/commits.rs @@ -62,10 +62,9 @@ impl<'a> Iterator for CommitIterator<'a> { type Item = DiffStat; fn next(&mut self) -> Option { - let Some(parent_id) = self.parent - else { - return None; - }; + let Some(parent_id) = self.parent else { + return None; + }; let parent_commit = parent_id.object().unwrap().into_commit(); let mut stats = DiffStat { @@ -456,9 +455,8 @@ pub async fn generate_tutorial_questions( } debug!(%reporef, "generating tutorial questions"); - let Ok(llm_gateway) = llm_gateway - else { - bail!("badly configured llm gw"); + let Ok(llm_gateway) = llm_gateway else { + bail!("badly configured llm gw"); }; // Due to `Send` issues on the gix side, we need to split this off quite brutally. diff --git a/server/bleep/src/config.rs b/server/bleep/src/config.rs index 8ca3488647..cd9204d27c 100644 --- a/server/bleep/src/config.rs +++ b/server/bleep/src/config.rs @@ -218,8 +218,8 @@ impl Configuration { .config_file .as_ref() .context("no config file specified") - .and_then(Self::read) else - { + .and_then(Self::read) + else { return Ok(cli); }; diff --git a/server/bleep/src/db.rs b/server/bleep/src/db.rs index b1f4a44807..e7efacccc6 100644 --- a/server/bleep/src/db.rs +++ b/server/bleep/src/db.rs @@ -11,17 +11,22 @@ pub use query_log::QueryLog; pub type SqlDb = Arc; -pub async fn init(config: &Configuration) -> Result { +#[tracing::instrument(skip_all)] +pub async fn initialize(config: &Configuration) -> Result { let data_dir = config.index_dir.to_string_lossy(); + let url = format!("sqlite://{data_dir}/bleep.db?mode=rwc"); - match connect(&data_dir).await { - Ok(pool) => Ok(pool), + match connect(&url).await { + Ok(pool) => { + debug!("connected"); + Ok(pool) + } Err(e) => { - warn!( - ?e, - "encountered DB error while migrating, recreating database..." - ); + warn!(?e, "error while migrating, recreating database..."); + reset(&data_dir)?; + debug!("reset complete"); + Ok(connect(&data_dir) .await .context("failed to recreate database")?) @@ -29,10 +34,9 @@ pub async fn init(config: &Configuration) -> Result { } } -async fn connect(data_dir: &str) -> Result { - let url = format!("sqlite://{data_dir}/bleep.db?mode=rwc"); - debug!("loading db from {url}"); - let pool = SqlitePool::connect(&url).await?; +#[tracing::instrument()] +async fn connect(url: &str) -> Result { + let pool = SqlitePool::connect(url).await?; if let Err(e) = sqlx::migrate!().run(&pool).await { // We manually close the pool here to ensure file handles are properly cleaned up on @@ -44,6 +48,7 @@ async fn connect(data_dir: &str) -> Result { } } +#[tracing::instrument()] fn reset(data_dir: &str) -> Result<()> { let db_path = Path::new(data_dir).join("bleep.db"); let bk_path = db_path.with_extension("db.bk"); diff --git a/server/bleep/src/ee.rs b/server/bleep/src/ee.rs index ff02576537..4d09640470 100644 --- a/server/bleep/src/ee.rs +++ b/server/bleep/src/ee.rs @@ -1,5 +1,9 @@ //! Modules for Bloop's Enterprise Edition. +//! Please see `LICENSE` for details. +#[cfg(feature = "ee-pro")] pub(crate) mod background; +#[cfg(feature = "ee-cloud")] pub(crate) mod embedder; +#[cfg(feature = "ee-pro")] pub(crate) mod webserver; diff --git a/server/bleep/src/ee/webserver.rs b/server/bleep/src/ee/webserver.rs index cc21edebb2..96d5c4e536 100644 --- a/server/bleep/src/ee/webserver.rs +++ b/server/bleep/src/ee/webserver.rs @@ -6,6 +6,7 @@ use axum::{ use crate::{ repo::FilterUpdate, webserver::{ + middleware::User, prelude::*, repos::{RepoParams, ReposResponse}, }, @@ -17,8 +18,9 @@ use crate::{ // pub(crate) async fn patch_repository( Query(RepoParams { repo }): Query, + user: Extension, State(app): State, - Json(patch): Json, + Json(mut patch): Json, ) -> impl IntoResponse { if let Some(ref file_filter) = patch.file_filter { _ = crate::repo::iterator::FileFilter::from(file_filter); @@ -28,6 +30,10 @@ pub(crate) async fn patch_repository( _ = crate::repo::iterator::BranchFilter::from(branch_filter); } + if !user.paid_features(&app).await { + patch.branch_filter = None; + } + if patch.file_filter.is_some() || patch.branch_filter.is_some() { app.write_index().add_branches_for_repo(repo, patch).await; json(ReposResponse::SyncQueued) diff --git a/server/bleep/src/indexes.rs b/server/bleep/src/indexes.rs index 7076603bf5..03a3558845 100644 --- a/server/bleep/src/indexes.rs +++ b/server/bleep/src/indexes.rs @@ -238,7 +238,7 @@ impl Indexer { index.set_multithread_executor(threads)?; index .tokenizers() - .register("default", NgramTokenizer::new(1, 3, false)); + .register("default", NgramTokenizer::new(1, 3, false).unwrap()); Ok(index) } diff --git a/server/bleep/src/indexes/file.rs b/server/bleep/src/indexes/file.rs index eb50e12c7c..2cc8984454 100644 --- a/server/bleep/src/indexes/file.rs +++ b/server/bleep/src/indexes/file.rs @@ -16,7 +16,7 @@ use tantivy::{ }; use tokenizers as _; use tokio::runtime::Handle; -use tracing::{info, trace, warn}; +use tracing::{error, info, trace, warn}; pub use super::schema::File; @@ -658,7 +658,7 @@ impl RepoDir { impl RepoFile { #[allow(clippy::too_many_arguments)] fn build_document( - mut self, + self, schema: &File, workload: &Workload<'_>, cache_keys: &CacheKeys, @@ -681,19 +681,19 @@ impl RepoFile { let relative_path_str = relative_path_str.replace('\\', "/"); let branches = self.branches.join("\n"); - let lang_str = repo_metadata - .langs - .get(normalized_path, self.buffer.as_ref()) - .unwrap_or_else(|| { - warn!(?normalized_path, "Path not found in language map"); - "" - }); - let indexed = file_filter .is_allowed(relative_path) - .unwrap_or_else(|| should_index(relative_path)); + .unwrap_or_else(|| self.should_index()); if !indexed { + let lang_str = repo_metadata + .langs + .get(normalized_path, b"") + .unwrap_or_else(|| { + warn!(?normalized_path, "Path not found in language map"); + "" + }); + return Some(doc!( schema.raw_content => vec![], schema.content => "", @@ -716,9 +716,24 @@ impl RepoFile { )); } + let mut buffer = match self.buffer() { + Ok(b) => b, + Err(err) => { + error!(?err, "failed to open file buffer; skipping file"); + return None; + } + }; + let lang_str = repo_metadata + .langs + .get(normalized_path, buffer.as_ref()) + .unwrap_or_else(|| { + warn!(?normalized_path, "Path not found in language map"); + "" + }); + let symbol_locations = { // build a syntax aware representation of the file - let scope_graph = TreeSitterFile::try_build(self.buffer.as_bytes(), lang_str) + let scope_graph = TreeSitterFile::try_build(buffer.as_bytes(), lang_str) .and_then(TreeSitterFile::scope_graph); match scope_graph { @@ -733,19 +748,18 @@ impl RepoFile { let symbols = symbol_locations .list() .iter() - .map(|sym| self.buffer[sym.range.start.byte..sym.range.end.byte].to_owned()) + .map(|sym| buffer[sym.range.start.byte..sym.range.end.byte].to_owned()) .collect::>() .into_iter() .collect::>() .join("\n"); // add an NL if this file is not NL-terminated - if !self.buffer.ends_with('\n') { - self.buffer += "\n"; + if !buffer.ends_with('\n') { + buffer += "\n"; } - let line_end_indices = self - .buffer + let line_end_indices = buffer .match_indices('\n') .flat_map(|(i, _)| u32::to_le_bytes(i as u32)) .collect::>(); @@ -756,7 +770,7 @@ impl RepoFile { return None; } - let lines_avg = self.buffer.len() as f64 / self.buffer.lines().count() as f64; + let lines_avg = buffer.len() as f64 / buffer.lines().count() as f64; tokio::task::block_in_place(|| { Handle::current().block_on(async { @@ -766,7 +780,7 @@ impl RepoFile { repo_name, repo_ref, &relative_path_str, - &self.buffer, + &buffer, lang_str, &self.branches, ) @@ -775,7 +789,7 @@ impl RepoFile { }); Some(doc!( - schema.raw_content => self.buffer.as_bytes(), + schema.raw_content => buffer.as_bytes(), schema.raw_repo_name => repo_name.as_bytes(), schema.raw_relative_path => relative_path_str.as_bytes(), schema.unique_hash => cache_keys.tantivy(), @@ -783,7 +797,7 @@ impl RepoFile { schema.relative_path => relative_path_str, schema.repo_ref => repo_ref.to_string(), schema.repo_name => *repo_name, - schema.content => self.buffer, + schema.content => buffer, schema.line_end_indices => line_end_indices, schema.lang => lang_str.to_ascii_lowercase().as_bytes(), schema.avg_line_length => lines_avg, diff --git a/server/bleep/src/indexes/reader.rs b/server/bleep/src/indexes/reader.rs index 01fc943c39..90562deff3 100644 --- a/server/bleep/src/indexes/reader.rs +++ b/server/bleep/src/indexes/reader.rs @@ -373,9 +373,8 @@ fn read_bool_field(doc: &tantivy::Document, field: Field) -> bool { } fn read_text_field(doc: &tantivy::Document, field: Field) -> String { - let Some(field) = doc.get_first(field) - else { - return Default::default(); + let Some(field) = doc.get_first(field) else { + return Default::default(); }; field.as_text().unwrap().into() diff --git a/server/bleep/src/indexes/schema.rs b/server/bleep/src/indexes/schema.rs index ba35d91b6a..f556fe6b35 100644 --- a/server/bleep/src/indexes/schema.rs +++ b/server/bleep/src/indexes/schema.rs @@ -132,6 +132,10 @@ impl File { histogram: Arc::new(Histogram::builder().build().unwrap().into()), } } + + pub fn schema(&self) -> Schema { + self.schema.clone() + } } impl Default for File { diff --git a/server/bleep/src/intelligence/code_navigation.rs b/server/bleep/src/intelligence/code_navigation.rs index b2e4d433d4..b3e8a878b5 100644 --- a/server/bleep/src/intelligence/code_navigation.rs +++ b/server/bleep/src/intelligence/code_navigation.rs @@ -153,8 +153,8 @@ impl<'a, 'b> CodeNavigationContext<'a, 'b> { .filter(|doc| doc.relative_path != source_doc.relative_path) .filter(|doc| { let Some(scope_graph) = doc.symbol_locations.scope_graph() else { - return false; - }; + return false; + }; let content = doc.content.as_bytes(); scope_graph .graph @@ -217,7 +217,7 @@ impl<'a, 'b> CodeNavigationContext<'a, 'b> { .or(imports) .into_iter() .chain(repo_wide_definitions) - .chain(local_references.into_iter()) + .chain(local_references) .chain(repo_wide_references) .collect() } else if self.is_import() { diff --git a/server/bleep/src/intelligence/scope_resolution.rs b/server/bleep/src/intelligence/scope_resolution.rs index 7028c7bccd..339122b500 100644 --- a/server/bleep/src/intelligence/scope_resolution.rs +++ b/server/bleep/src/intelligence/scope_resolution.rs @@ -861,7 +861,7 @@ mod tests { let foo = definition(0, 3); let foo_ref = reference(5, 8); - let src = r#"foo\nfoo"#.as_bytes(); + let src = r"foo\nfoo".as_bytes(); s.insert_local_def(foo); s.insert_ref(foo_ref, src); diff --git a/server/bleep/src/lib.rs b/server/bleep/src/lib.rs index f2a4738c32..a572555820 100644 --- a/server/bleep/src/lib.rs +++ b/server/bleep/src/lib.rs @@ -57,7 +57,6 @@ mod remotes; mod repo; mod webserver; -#[cfg(feature = "ee")] mod ee; pub mod analytics; @@ -141,7 +140,7 @@ impl Application { let repo_pool = config.source.initialize_pool()?; // Databases & indexes - let sql = Arc::new(db::init(&config).await?); + let sql = Arc::new(db::initialize(&config).await?); let semantic = Semantic::initialize(&config.model_dir, &config.qdrant_url, Arc::clone(&config)) .await @@ -149,15 +148,26 @@ impl Application { // Wipe existing dbs & caches if the schema has changed if config.source.index_version_mismatch() { + debug!("schema version mismatch, resetting state"); + Indexes::reset_databases(&config).await?; + debug!("tantivy indexes deleted"); + cache::FileCache::new(sql.clone(), semantic.clone()) .reset(&repo_pool) .await?; + debug!("caches deleted"); semantic.reset_collection_blocking().await?; + debug!("semantic indexes deleted"); + + debug!("state reset complete"); } + + debug!("saving index version"); config.source.save_index_version()?; + debug!("initializing indexes"); let indexes = Indexes::new(&config).await?.into(); // Enforce capabilies and features depending on environment @@ -216,8 +226,7 @@ impl Application { sentry::configure_scope(|scope| { scope.add_event_processor(|event| { - let Some(ref logger) = event.logger - else { + let Some(ref logger) = event.logger else { return Some(event); }; @@ -464,11 +473,14 @@ where }) } +#[tracing::instrument(skip_all)] fn initialize_analytics( config: &Configuration, tracking_seed: impl Into>, options: impl Into>, ) -> Result> { + debug!("creating configuration"); + let Some(key) = &config.analytics_key else { bail!("analytics key missing; skipping initialization"); }; @@ -486,7 +498,6 @@ fn initialize_analytics( }), }); - info!("configuring analytics ..."); tokio::task::block_in_place(|| { analytics::RudderHub::new_with_options( &config.source, diff --git a/server/bleep/src/periodic/logrotate.rs b/server/bleep/src/periodic/logrotate.rs index ad356b3274..d1dac8ce64 100644 --- a/server/bleep/src/periodic/logrotate.rs +++ b/server/bleep/src/periodic/logrotate.rs @@ -74,8 +74,7 @@ fn update_branch_filters( ) -> Vec { let mut to_sync = vec![]; map.for_each(|repo, branches| { - let Ok(reporef) = repo.parse() - else { + let Ok(reporef) = repo.parse() else { return; }; diff --git a/server/bleep/src/periodic/remotes.rs b/server/bleep/src/periodic/remotes.rs index 890c64e2e0..a201cace60 100644 --- a/server/bleep/src/periodic/remotes.rs +++ b/server/bleep/src/periodic/remotes.rs @@ -5,7 +5,7 @@ use std::{ }; use anyhow::Context; -use chrono::Utc; +use chrono::{DateTime, Utc}; use notify_debouncer_mini::{ new_debouncer_opt, notify::{Config, RecommendedWatcher, RecursiveMode}, @@ -110,7 +110,11 @@ pub(crate) async fn sync_github_status(app: Application) { }; debug!("repo list updated"); - let updated = app.credentials.github_updated().unwrap(); + let updated = match app.credentials.github_updated() { + Some(receiver) => receiver, + // This is a race condition, let's need to start from scratch. + None => continue, + }; let new = github.update_repositories(repos); // store the updated credentials here @@ -160,13 +164,7 @@ async fn update_credentials(app: &Application) { let verifier = crate::webserver::aaa::get_authorizer(app).await; let rotate_access_key = match verifier.check_auth(&creds.access_token).await { Ok(jsonwebtoken::TokenData { claims, .. }) => { - let Some(exp) = claims.exp - else { - return; - }; - - let expiry_time = UNIX_EPOCH + Duration::from_secs(i64::from(exp) as u64); - expiry_time - Duration::from_secs(600) < SystemTime::now() + DateTime::::from(claims.exp) - Duration::from_secs(600) < Utc::now() } Err(err) => { warn!(?err, "failed to validate access token; rotating"); diff --git a/server/bleep/src/query/compiler.rs b/server/bleep/src/query/compiler.rs index 170c54e173..68414c4a64 100644 --- a/server/bleep/src/query/compiler.rs +++ b/server/bleep/src/query/compiler.rs @@ -94,12 +94,12 @@ impl Compiler { for (field, extractor) in &mut self.extractors { let Some(extraction) = extractor(query) else { - continue + continue; }; let field_query = match extraction { Extraction::Literal(Literal::Plain(text)) => { - let tokenizer = index + let mut tokenizer = index .tokenizer_for_field(*field) .context("field is missing tokenizer")?; @@ -209,7 +209,7 @@ pub fn trigrams(s: &str) -> impl Iterator { std::iter::from_fn(move || match chars.len() { 0 => None, - 1 | 2 | 3 => Some(mem::take(&mut chars).into_iter().collect()), + 1..=3 => Some(mem::take(&mut chars).into_iter().collect()), _ => { let out = chars.iter().take(3).collect(); chars.remove(0); @@ -376,7 +376,7 @@ mod tests { let (occur, term) = &subquery.clauses()[0]; let term = term.downcast_ref::().unwrap(); assert_eq!(*occur, Occur::Should); - assert_eq!(term.term().as_str().unwrap(), expected); + assert_eq!(term.term().value().as_str().unwrap(), expected); } } } diff --git a/server/bleep/src/query/execute.rs b/server/bleep/src/query/execute.rs index 68a7877ba7..f8a187c6bb 100644 --- a/server/bleep/src/query/execute.rs +++ b/server/bleep/src/query/execute.rs @@ -809,7 +809,7 @@ mod tests { "end": 56, }], "symbols": [], - "data": r#" mut writer: IndexWriter,\n _threads: usize,\n ) -> Result<()> {"#, + "data": r" mut writer: IndexWriter,\n _threads: usize,\n ) -> Result<()> {", "line_range": { "start": 49, "end": 51 @@ -845,7 +845,7 @@ mod tests { repo_ref: "/User/bloop/bleep".into(), lang: Some("Rust".into()), snippets: vec![Snippet { - data: r#" mut writer: IndexWriter,\n _threads: usize,\n ) -> Result<()> {"#.to_owned(), + data: r" mut writer: IndexWriter,\n _threads: usize,\n ) -> Result<()> {".to_owned(), line_range: 49..51, highlights: vec![51..56], symbols: vec![], diff --git a/server/bleep/src/query/ranking.rs b/server/bleep/src/query/ranking.rs index b10d287fd0..891f8104f6 100644 --- a/server/bleep/src/query/ranking.rs +++ b/server/bleep/src/query/ranking.rs @@ -1,32 +1,37 @@ -use std::{sync::Arc, time::SystemTime}; +use std::time::SystemTime; use tantivy::{ collector::{ScoreSegmentTweaker, ScoreTweaker}, - fastfield::{BytesFastFieldReader, Column}, + fastfield::Column, DocId, Score, }; +use tantivy_columnar::{column_values::ColumnValues, BytesColumn}; use crate::indexes::file::File; pub struct DocumentTweaker(pub File); pub struct SegmentScorer { - line_length: Arc>, - lang: BytesFastFieldReader, - last_commit: Arc>, + line_length: Column, + lang: BytesColumn, + last_commit: Column, } impl ScoreSegmentTweaker for SegmentScorer { fn score(&mut self, doc: DocId, mut score: Score) -> Score { // * 1000 if it's a language we understand - score *= 1.0 + self.lang.num_bytes(doc).min(1) as f32 * 999.0; + let mut bytes = Vec::new(); + self.lang.ords().values_for_doc(doc).for_each(|ord| { + self.lang.ord_to_bytes(ord, &mut bytes).unwrap(); + }); + score *= 1.0 + bytes.len().min(1) as f32 * 999.0; // Penalty for lines that are too long - score /= self.line_length.get_val(doc).clamp(20.0, 1000.0) as f32; + score /= self.line_length.values.get_val(doc).clamp(20.0, 1000.0) as f32; score /= SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() - .saturating_sub(self.last_commit.get_val(doc)) + .saturating_sub(self.last_commit.values.get_val(doc)) .min(5_000_000) as f32; score @@ -40,13 +45,17 @@ impl ScoreTweaker for DocumentTweaker { &self, segment_reader: &tantivy::SegmentReader, ) -> tantivy::Result { - let Self(schema) = self; + let Self(file) = self; + let schema = file.schema(); + let avg_line_length_field = schema.get_field_name(file.avg_line_length); + let lang_field = schema.get_field_name(file.lang); + let last_commit_unix_seconds_field = schema.get_field_name(file.last_commit_unix_seconds); Ok(SegmentScorer { - line_length: segment_reader.fast_fields().f64(schema.avg_line_length)?, - lang: segment_reader.fast_fields().bytes(schema.lang)?, + line_length: segment_reader.fast_fields().f64(avg_line_length_field)?, + lang: segment_reader.fast_fields().bytes(lang_field)?.unwrap(), last_commit: segment_reader .fast_fields() - .u64(schema.last_commit_unix_seconds)?, + .u64(last_commit_unix_seconds_field)?, }) } } diff --git a/server/bleep/src/remotes.rs b/server/bleep/src/remotes.rs index a3ce4f39f4..51b62a47fe 100644 --- a/server/bleep/src/remotes.rs +++ b/server/bleep/src/remotes.rs @@ -197,12 +197,12 @@ pub(crate) fn gather_repo_roots( use ignore::WalkState::*; let Ok(de) = entry else { - return Continue; - }; + return Continue; + }; let Some(ft) = de.file_type() else { - return Continue; - }; + return Continue; + }; if ft.is_dir() && RECOGNIZED_VCS_DIRS.contains(&de.file_name().to_string_lossy().as_ref()) diff --git a/server/bleep/src/remotes/github.rs b/server/bleep/src/remotes/github.rs index 90477fc9cf..a3d4c13f02 100644 --- a/server/bleep/src/remotes/github.rs +++ b/server/bleep/src/remotes/github.rs @@ -106,9 +106,7 @@ impl Auth { } pub async fn check_repo(&self, repo: &Repository) -> Result<()> { - let RepoRemote::Git(GitRemote { - ref address, .. - }) = repo.remote else { + let RepoRemote::Git(GitRemote { ref address, .. }) = repo.remote else { return Err(RemoteError::NotSupported("github without git backend")); }; diff --git a/server/bleep/src/repo/iterator.rs b/server/bleep/src/repo/iterator.rs index cc1421f8ab..32a9b47601 100644 --- a/server/bleep/src/repo/iterator.rs +++ b/server/bleep/src/repo/iterator.rs @@ -65,9 +65,9 @@ impl RepoDirEntry { } } - pub fn buffer(&self) -> Option<&str> { + pub fn buffer(&self) -> Option { match self { - Self::File(file) => Some(file.buffer.as_str()), + Self::File(file) => (file.buffer)().ok(), _ => None, } } @@ -87,9 +87,24 @@ pub struct RepoDir { } pub struct RepoFile { + /// Path to file pub path: String, - pub buffer: String, + /// Branches which include the file pub branches: Vec, + /// Length of the buffer + pub len: u64, + /// Lazily loaded buffer that contains the file contents + buffer: Box std::io::Result + Send + Sync>, +} + +impl RepoFile { + pub fn should_index(&self) -> bool { + should_index_path(&self.path) && self.len < MAX_FILE_LEN + } + + pub fn buffer(&self) -> std::io::Result { + (self.buffer)() + } } #[derive(Hash, Eq, PartialEq)] @@ -99,7 +114,7 @@ pub enum FileType { Other, } -pub(crate) fn should_index + ?Sized>(p: &P) -> bool { +fn should_index_path + ?Sized>(p: &P) -> bool { let path = p.as_ref(); // TODO: Make this more robust @@ -193,7 +208,7 @@ mod test { ]; for (path, index) in tests { - assert_eq!(should_index(&Path::new(path)), index); + assert_eq!(should_index_path(&Path::new(path)), index); } } } diff --git a/server/bleep/src/repo/iterator/filters.rs b/server/bleep/src/repo/iterator/filters.rs index be978161df..0c7ddb0f0c 100644 --- a/server/bleep/src/repo/iterator/filters.rs +++ b/server/bleep/src/repo/iterator/filters.rs @@ -28,15 +28,13 @@ impl BranchFilterConfig { &self, old: Option<&BranchFilterConfig>, ) -> Option { - let Some(BranchFilterConfig::Select(ref old_list)) = old - else { - return Some(self.clone()); - }; - - let BranchFilterConfig::Select(new_list) = self - else { - return Some(self.clone()); - }; + let Some(BranchFilterConfig::Select(ref old_list)) = old else { + return Some(self.clone()); + }; + + let BranchFilterConfig::Select(new_list) = self else { + return Some(self.clone()); + }; let mut updated = old_list.iter().collect::>(); updated.extend(new_list); diff --git a/server/bleep/src/repo/iterator/fs.rs b/server/bleep/src/repo/iterator/fs.rs index 945a356819..ab6158cb93 100644 --- a/server/bleep/src/repo/iterator/fs.rs +++ b/server/bleep/src/repo/iterator/fs.rs @@ -29,8 +29,6 @@ impl FileWalker { None } }) - // Preliminarily ignore files that are very large, without reading the contents. - .filter(|de| matches!(de.metadata(), Ok(meta) if meta.len() < MAX_FILE_LEN)) .filter(|de| !de.path().strip_prefix(&dir).unwrap().starts_with(".git")) .filter_map(|de| crate::canonicalize(de.into_path()).ok()) .collect(); @@ -51,15 +49,17 @@ impl FileSource for FileWalker { .into_par_iter() .filter_map(|entry_disk_path| { if entry_disk_path.is_file() { - let buffer = match std::fs::read_to_string(&entry_disk_path) { + let path = entry_disk_path.clone(); + let buffer = Box::new(move || match std::fs::read_to_string(&path) { Err(err) => { - warn!(%err, ?entry_disk_path, "read failed; skipping"); - return None; + warn!(%err, entry_disk_path=?path, "read failed; skipping"); + Err(err) } - Ok(buffer) => buffer, - }; + Ok(buffer) => Ok(buffer), + }); Some(RepoDirEntry::File(RepoFile { buffer, + len: entry_disk_path.metadata().ok()?.len(), path: entry_disk_path.to_string_lossy().to_string(), branches: vec![self.branch.clone()], })) diff --git a/server/bleep/src/repo/iterator/git.rs b/server/bleep/src/repo/iterator/git.rs index eea379c917..91c3cfc96a 100644 --- a/server/bleep/src/repo/iterator/git.rs +++ b/server/bleep/src/repo/iterator/git.rs @@ -167,17 +167,14 @@ impl FileSource for GitWalker { return None; }; - if object.data.len() as u64 > MAX_FILE_LEN { - return None; - } - let entry = match kind { FileType::File => { let buffer = String::from_utf8_lossy(&object.data).to_string(); RepoDirEntry::File(RepoFile { path, + len: buffer.len() as u64, branches: branches.into_iter().collect(), - buffer, + buffer: Box::new(move || Ok(buffer.clone())), }) } FileType::Dir => RepoDirEntry::Dir(RepoDir { diff --git a/server/bleep/src/semantic.rs b/server/bleep/src/semantic.rs index 50778b0afd..c14768bed6 100644 --- a/server/bleep/src/semantic.rs +++ b/server/bleep/src/semantic.rs @@ -109,11 +109,13 @@ fn parse_payload( payload: HashMap, score: f32, ) -> Payload { - let Some(PointId { point_id_options: Some(PointIdOptions::Uuid(id)) }) = id + let Some(PointId { + point_id_options: Some(PointIdOptions::Uuid(id)), + }) = id else { - // unless the db was corrupted/written by someone else, - // this shouldn't happen - unreachable!("corrupted db"); + // unless the db was corrupted/written by someone else, + // this shouldn't happen + unreachable!("corrupted db"); }; let embedding = match vectors { @@ -184,12 +186,14 @@ async fn create_indexes(collection_name: &str, qdrant: &QdrantClient) -> anyhow: } impl Semantic { + #[tracing::instrument(fields(collection=%config.collection_name, %qdrant_url), skip_all)] pub async fn initialize( model_dir: &Path, qdrant_url: &str, config: Arc, ) -> Result { let qdrant = QdrantClient::new(Some(QdrantClientConfig::from_url(qdrant_url))).unwrap(); + debug!("initialized client"); match qdrant.has_collection(&config.collection_name).await { Ok(false) => { @@ -198,34 +202,40 @@ impl Semantic { .await .unwrap(); - debug!( - time, - created = result, - name = config.collection_name, - "created qdrant collection" - ); - + debug!(time, created = result, "collection created"); assert!(result); } - Ok(true) => {} + Ok(true) => { + debug!("collection already exists"); + } Err(_) => return Err(SemanticError::QdrantInitializationError), } create_indexes(&config.collection_name, &qdrant).await?; + debug!("indexes created"); if let Some(dylib_dir) = config.dylib_dir.as_ref() { init_ort_dylib(dylib_dir); + debug!( + dylib_dir = dylib_dir.to_string_lossy().as_ref(), + "initialized ORT dylib" + ); } - #[cfg(feature = "ee")] + #[cfg(feature = "ee-cloud")] let embedder: Arc = if let Some(ref url) = config.embedding_server_url { - Arc::new(embedder::RemoteEmbedder::new(url.clone(), model_dir)?) + let embedder = Arc::new(embedder::RemoteEmbedder::new(url.clone(), model_dir)?); + debug!("using remote embedder"); + embedder } else { - Arc::new(LocalEmbedder::new(model_dir)?) + let embedder = Arc::new(LocalEmbedder::new(model_dir)?); + debug!("using local embedder"); + embedder }; - #[cfg(not(feature = "ee"))] + #[cfg(not(feature = "ee-cloud"))] let embedder: Arc = Arc::new(LocalEmbedder::new(model_dir)?); + debug!("using local embedder"); Ok(Self { qdrant: qdrant.into(), diff --git a/server/bleep/src/semantic/chunk.rs b/server/bleep/src/semantic/chunk.rs index 4fbc0a5710..d9952f22a2 100644 --- a/server/bleep/src/semantic/chunk.rs +++ b/server/bleep/src/semantic/chunk.rs @@ -207,8 +207,7 @@ pub fn by_tokens<'s>( if src.len() < min_tokens { return Vec::new(); } - let Ok(encoding) = tokenizer.encode(src, true) - else { + let Ok(encoding) = tokenizer.encode(src, true) else { warn!("Could not encode \"{}\"", src); return by_lines(src, 15); }; @@ -383,8 +382,8 @@ mod tests { .standard_filters(true) .filter_entry(|de| { let Some(ft) = de.file_type() else { - return false; - }; + return false; + }; // pretty crude, but do ignore generated files if ft.is_dir() && de.file_name() == "target" { @@ -403,7 +402,9 @@ mod tests { if file.metadata().unwrap().is_dir() { continue; } - let Ok(src) = std::fs::read_to_string(file.path()) else { continue }; + let Ok(src) = std::fs::read_to_string(file.path()) else { + continue; + }; let chunks = super::by_tokens( "bloop", &file.path().to_string_lossy(), diff --git a/server/bleep/src/semantic/embedder.rs b/server/bleep/src/semantic/embedder.rs index cac709e368..343d786780 100644 --- a/server/bleep/src/semantic/embedder.rs +++ b/server/bleep/src/semantic/embedder.rs @@ -18,7 +18,7 @@ use tracing::trace; use super::Embedding; -#[cfg(feature = "ee")] +#[cfg(feature = "ee-cloud")] pub use crate::ee::embedder::*; #[derive(Default)] @@ -29,10 +29,9 @@ pub struct EmbedQueue { impl EmbedQueue { pub fn pop(&self) -> Option { - let Some(val) = self.log.pop() - else { - return None; - }; + let Some(val) = self.log.pop() else { + return None; + }; // wrapping shouldn't happen, because only decrements when // `log` is non-empty. diff --git a/server/bleep/src/snippet.rs b/server/bleep/src/snippet.rs index 59267621ca..31dec71ec1 100644 --- a/server/bleep/src/snippet.rs +++ b/server/bleep/src/snippet.rs @@ -531,16 +531,14 @@ mod tests { assert_eq!(observed.len(), 2); assert_eq!( observed[0].data, - vec![ - r#"pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];"#, + [r#"pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];"#, r#"pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];"#, - r#"pub const GET: [&str; 4] = ["core", "slice", "", "get"];"#, - ].join("\n") + r#"pub const GET: [&str; 4] = ["core", "slice", "", "get"];"#].join("\n") ); assert_eq!(observed[0].line_count(), 2); assert_eq!( observed[1].data, - vec![ + [ r#"pub const INTO: [&str; 4] = ["core", "slice", "", "iter"];"#, r#"pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];"#, "" diff --git a/server/bleep/src/webserver/aaa.rs b/server/bleep/src/webserver/aaa.rs index 548e7b125a..ecd9cd890e 100644 --- a/server/bleep/src/webserver/aaa.rs +++ b/server/bleep/src/webserver/aaa.rs @@ -8,7 +8,7 @@ use axum_extra::extract::{ CookieJar, }; use chrono::{DateTime, Utc}; -use jwt_authorizer::{layer::JwtSource, Authorizer, IntoLayer, JwtAuthorizer}; +use jwt_authorizer::{layer::JwtSource, Authorizer, IntoLayer, JwtAuthorizer, NumericDate}; use secrecy::{ExposeSecret, SecretString}; use serde_json::json; @@ -16,7 +16,7 @@ use crate::{webserver::middleware, Application}; use super::prelude::*; -const COOKIE_NAME: &str = "X-Bleep-Cognito"; +pub(super) const COOKIE_NAME: &str = "X-Bleep-Cognito"; #[derive(Serialize)] #[serde(rename_all = "snake_case")] @@ -48,7 +48,9 @@ pub(super) async fn login( let timestamp = chrono::Utc::now(); let payload = serde_json::json!({ "timestamp": timestamp.to_rfc2822(), - "redirect_url": redirect_to.as_ref() + "redirect_to": format!("{}/{}", + app.config.instance_domain.as_ref().unwrap(), + redirect_to.unwrap_or_default()) }); app.seal_auth_state(payload) }; @@ -90,7 +92,15 @@ pub(super) async fn router(router: Router, app: Application) -> Router { .route("/auth/refresh_token", get(refresh_token)) } -pub async fn get_authorizer(app: &Application) -> Authorizer { +#[derive(Deserialize, Clone, Debug)] +pub struct TokenClaims { + pub exp: NumericDate, + pub sub: String, + #[serde(rename = "cognito:groups")] + pub groups: Vec, +} + +pub async fn get_authorizer(app: &Application) -> Authorizer { let userpool_id = app.config.cognito_userpool_id.as_ref().expect("bad config"); let (region, _) = userpool_id.split_once('_').unwrap(); let url = @@ -147,24 +157,13 @@ pub(super) async fn refresh_token( .map_err(|_| Error::new(ErrorKind::UpstreamService, "invalid token issued"))? .claims; - let sub = claims.sub.ok_or(Error::new( - ErrorKind::UpstreamService, - "invalid token issued", - ))?; - - app.user_profiles.entry(sub).or_default().get_mut().username = Some(response.username.clone()); - - let now = Utc::now(); - let exp: DateTime = claims - .exp - .ok_or(Error::new( - ErrorKind::UpstreamService, - "invalid token issued", - ))? - .into(); - - let max_age = (exp - now).num_seconds(); + app.user_profiles + .entry(claims.sub) + .or_default() + .get_mut() + .username = Some(response.username.clone()); + let max_age = (DateTime::::from(claims.exp) - Utc::now()).num_seconds(); Ok(( jar.add( Cookie::build( diff --git a/server/bleep/src/webserver/config.rs b/server/bleep/src/webserver/config.rs index 54bee84baf..ce3433c7c9 100644 --- a/server/bleep/src/webserver/config.rs +++ b/server/bleep/src/webserver/config.rs @@ -18,6 +18,7 @@ pub(super) struct ConfigResponse { bloop_version: String, bloop_commit: String, credentials_upgrade: bool, + paid: bool, } impl super::ApiResponse for ConfigResponse {} @@ -51,8 +52,7 @@ pub(super) async fn get( }); let github_user = 'user: { - let (Some(login), Some(crab)) = (&user_login, user.github()) - else { + let (Some(login), Some(crab)) = (&user_login, user.github()) else { break 'user None; }; @@ -68,6 +68,7 @@ pub(super) async fn get( bloop_commit: git_version::git_version!(fallback = "unknown").into(), bloop_user_profile: user_profile, credentials_upgrade: app.config.source.exists("credentials.json"), + paid: user.paid_features(&app).await, user_login, github_user, device_id, diff --git a/server/bleep/src/webserver/middleware.rs b/server/bleep/src/webserver/middleware.rs index 9dec417b3b..6993dee259 100644 --- a/server/bleep/src/webserver/middleware.rs +++ b/server/bleep/src/webserver/middleware.rs @@ -1,5 +1,5 @@ -use super::prelude::*; -use crate::Application; +use super::{aaa, prelude::*}; +use crate::{remotes::CognitoGithubTokenBundle, Application}; use anyhow::Context; use axum::{ @@ -8,14 +8,17 @@ use axum::{ middleware::{from_fn, from_fn_with_state, Next}, response::Response, }; -use jwt_authorizer::{JwtClaims, RegisteredClaims}; +use axum_extra::extract::CookieJar; +use jwt_authorizer::JwtClaims; use sentry::{Hub, SentryFutureExt}; +use tracing::error; #[derive(Serialize, Clone)] pub enum User { Unknown, Authenticated { login: String, + api_token: String, #[serde(skip)] crab: Arc anyhow::Result + Send + Sync>, }, @@ -23,22 +26,61 @@ pub enum User { impl User { pub(crate) fn login(&self) -> Option<&str> { - let User::Authenticated { login, .. } = self - else { - return None; - }; + let User::Authenticated { login, .. } = self else { + return None; + }; Some(login) } pub(crate) fn github(&self) -> Option { - let User::Authenticated { crab, .. } = self - else { - return None; - }; + let User::Authenticated { crab, .. } = self else { + return None; + }; crab().ok() } + + pub(crate) async fn paid_features(&self, app: &Application) -> bool { + let User::Authenticated { api_token, .. } = self else { + return false; + }; + + let Ok(response) = reqwest::Client::new() + .get(format!("{}/v2/get-usage-quota", app.config.answer_api_url)) + .bearer_auth(api_token) + .send() + .await + else { + error!("failed to get quota for user"); + return false; + }; + + if response.status().is_success() { + let response: serde_json::Value = + response.json().await.expect("answer_api proto bad or down"); + + response + .get("upgraded") + .and_then(serde_json::Value::as_bool) + .unwrap_or_default() + } else { + let status = response.status(); + match response.text().await { + Ok(body) if !body.is_empty() => { + error!(?status, ?body, "request failed with status code") + } + Ok(_) => error!(?status, "request failed; response had no body"), + Err(err) => error!( + ?status, + ?err, + "request failed; failed to retrieve response body", + ), + } + + false + } + } } pub fn sentry_layer(router: Router) -> Router { @@ -73,15 +115,30 @@ async fn local_user_mw( next: Next, ) -> Response { request.extensions_mut().insert( - app.clone() - .credentials + app.credentials .user() - .map(|user| User::Authenticated { - login: user, - crab: Arc::new(move || { - let gh = app.credentials.github().context("no github")?; - Ok(gh.client()?) - }), + .zip(app.credentials.github()) + .map(|(user, gh)| { + use crate::remotes::github::{Auth, State}; + let api_token = match gh { + State { + auth: + Auth::OAuth(CognitoGithubTokenBundle { + access_token: ref token, + .. + }), + .. + } => token.clone(), + _ => { + panic!("invalid configuration"); + } + }; + + User::Authenticated { + api_token, + login: user, + crab: Arc::new(move || Ok(gh.client()?)), + } }) .unwrap_or_else(|| User::Unknown), ); @@ -90,34 +147,28 @@ async fn local_user_mw( } pub async fn cloud_user_layer_mw( - JwtClaims(claims): JwtClaims, + JwtClaims(claims): JwtClaims, State(app): State, + jar: CookieJar, mut request: Request, next: Next, ) -> Response { - request.extensions_mut().insert( - claims - .sub - .map(|login| { - let login = app - .user_profiles - .read(&login, |_, v| v.username.clone()) - .flatten() - .unwrap_or_default(); - - // login will be an empty string if we can auth the user, but they haven't called `/auth/refresh_token` yet. - // this is to avoid inadvertently leaking our cognito user ids all over the place in remote calls - - User::Authenticated { - login, - crab: Arc::new(move || { - let gh = app.credentials.github().context("no github")?; - Ok(gh.client()?) - }), - } - }) - .unwrap_or_else(|| User::Unknown), - ); + request.extensions_mut().insert({ + let login = app + .user_profiles + .read(&claims.sub, |_, v| v.username.clone()) + .flatten() + .unwrap_or_default(); + + User::Authenticated { + login, + api_token: jar.get(super::aaa::COOKIE_NAME).unwrap().to_string(), + crab: Arc::new(move || { + let gh = app.credentials.github().context("no github")?; + Ok(gh.client()?) + }), + } + }); next.run(request).await } diff --git a/server/bleep/src/webserver/quota.rs b/server/bleep/src/webserver/quota.rs index 9b6d4dbc3d..192aa1443f 100644 --- a/server/bleep/src/webserver/quota.rs +++ b/server/bleep/src/webserver/quota.rs @@ -1,12 +1,10 @@ use axum::{Extension, Json}; use chrono::{DateTime, Utc}; -use reqwest::StatusCode; -use secrecy::ExposeSecret; use serde::Deserialize; use crate::Application; -use super::Error; +use super::{middleware::User, Error}; #[derive(serde::Deserialize, serde::Serialize)] pub struct QuotaResponse { @@ -21,29 +19,32 @@ pub struct SubscriptionResponse { url: String, } -pub async fn get(app: Extension) -> super::Result> { - get_request(app, "/v2/get-usage-quota").await +pub async fn get( + app: Extension, + user: Extension, +) -> super::Result> { + get_request(app, user, "/v2/get-usage-quota").await } pub async fn create_checkout_session( app: Extension, + user: Extension, ) -> super::Result> { - get_request(app, "/v2/create-checkout-session").await + get_request(app, user, "/v2/create-checkout-session").await } async fn get_request Deserialize<'a>>( app: Extension, + Extension(user): Extension, endpoint: &str, ) -> super::Result> { - let answer_api_token = app - .answer_api_token() - .map_err(|e| Error::user(e).with_status(StatusCode::UNAUTHORIZED))? - .ok_or_else(|| Error::unauthorized("answer API token was not present")) - .map(|s| s.expose_secret().to_owned())?; + let User::Authenticated { api_token, .. } = user else { + return Err(Error::unauthorized("answer API token was not present")); + }; let response = reqwest::Client::new() .get(format!("{}{}", app.config.answer_api_url, endpoint)) - .bearer_auth(answer_api_token) + .bearer_auth(api_token) .send() .await .map_err(Error::internal)?; diff --git a/server/bleep/src/webserver/repos.rs b/server/bleep/src/webserver/repos.rs index 546caa8f7d..084d83b4d1 100644 --- a/server/bleep/src/webserver/repos.rs +++ b/server/bleep/src/webserver/repos.rs @@ -43,8 +43,7 @@ impl From<(&RepoRef, &Repository)> for Repo { fn from((key, repo): (&RepoRef, &Repository)) -> Self { let (head, branches) = 'branch_list: { let default = ("HEAD".to_string(), vec![]); - let Ok(git) = gix::open(&repo.disk_path) - else { + let Ok(git) = gix::open(&repo.disk_path) else { break 'branch_list default; }; @@ -61,47 +60,51 @@ impl From<(&RepoRef, &Repository)> for Repo { }) .unwrap_or_else(|| default.0.clone()); - let Ok(refs) = git.references() - else { + let Ok(refs) = git.references() else { break 'branch_list default; }; - let Ok(refs) = refs.all() - else { + let Ok(refs) = refs.all() else { break 'branch_list default; }; - use gix::bstr::ByteSlice; - let mut branches = refs - .filter_map(Result::ok) - .filter_map(|mut r| { - let name = r.name().shorten().to_str_lossy().to_string(); - let last_commit_unix_secs = r - .peel_to_id_in_place() - .ok()? - .object() - .ok()? - .try_into_commit() - .ok()? - .time() - .ok()? - .seconds; - - Some(Branch { - name, - last_commit_unix_secs, + let branches = if key.is_local() { + vec![] + } else { + use gix::bstr::ByteSlice; + let mut branches = refs + .filter_map(Result::ok) + .filter_map(|mut r| { + let name = r.name().shorten().to_str_lossy().to_string(); + let last_commit_unix_secs = r + .peel_to_id_in_place() + .ok()? + .object() + .ok()? + .try_into_commit() + .ok()? + .time() + .ok()? + .seconds; + + Some(Branch { + name, + last_commit_unix_secs, + }) }) - }) - .filter(|b| { - if key.is_remote() { - b.name != "origin/HEAD" && b.name.starts_with("origin/") - } else { - b.name != "HEAD" && !b.name.starts_with("origin/") - } - }) - .collect::>(); + .filter(|b| { + if key.is_remote() { + b.name != "origin/HEAD" && b.name.starts_with("origin/") + } else { + b.name != "HEAD" && !b.name.starts_with("origin/") + } + }) + .collect::>(); + + branches.sort_by_key(|b| b.last_commit_unix_secs); + branches + }; - branches.sort_by_key(|b| b.last_commit_unix_secs); (head, branches) }; @@ -194,7 +197,7 @@ pub(crate) enum ReposResponse { Item(Repo), SyncQueue(Vec), SyncQueued, - #[cfg(feature = "ee")] + #[cfg(feature = "ee-pro")] Unchanged, Deleted, } @@ -207,7 +210,7 @@ pub(super) fn router() -> Router { let mut indexed = get(indexed).put(set_indexed).delete(delete_by_id); - #[cfg(feature = "ee")] + #[cfg(feature = "ee-pro")] { indexed = indexed.patch(crate::ee::webserver::patch_repository); } diff --git a/server/bleep/src/webserver/search.rs b/server/bleep/src/webserver/search.rs index 4bdefca6ca..ce143e15dd 100644 --- a/server/bleep/src/webserver/search.rs +++ b/server/bleep/src/webserver/search.rs @@ -12,15 +12,8 @@ use tracing::error; pub(super) async fn semantic_code( Query(args): Query, - Extension(semantic): Extension>, + Extension(semantic): Extension, ) -> impl IntoResponse { - let Some(semantic) = semantic else { - return Err(Error::new( - ErrorKind::Configuration, - "Qdrant not configured", - )); - }; - match parser::parse_nl(&args.q.clone()) { Ok(q) => semantic::execute::execute(semantic, q, args) .await diff --git a/server/bleep/src/webserver/studio.rs b/server/bleep/src/webserver/studio.rs index 91560346df..c1ffb90fce 100644 --- a/server/bleep/src/webserver/studio.rs +++ b/server/bleep/src/webserver/studio.rs @@ -391,6 +391,7 @@ pub struct TokenCounts { total: usize, messages: usize, per_file: Vec>, + baseline: usize, } async fn token_counts( @@ -429,16 +430,21 @@ async fn token_counts( None => return Some(0), }; - body.map(|b| count_tokens_in_file(&b, &file.ranges)) + body.map(|b| count_tokens_for_file(&file.path, &b, &file.ranges)) }) .collect::>(); + let empty_context = generate_llm_context(app.clone(), &[]).await?; let empty_system_message = tiktoken_rs::ChatCompletionRequestMessage { role: "system".to_owned(), - content: prompts::studio_article_prompt(""), + content: prompts::studio_article_prompt(&empty_context), name: None, }; + let baseline = + tiktoken_rs::num_tokens_from_messages(LLM_GATEWAY_MODEL, &[empty_system_message.clone()]) + .unwrap(); + let tiktoken_messages = messages.iter().cloned().map(|message| match message { Message::User(content) => tiktoken_rs::ChatCompletionRequestMessage { role: "user".to_owned(), @@ -460,10 +466,20 @@ async fn token_counts( ) .unwrap(); + // We calculate `total` here as a summation of other calculated values here, because OpenAI's + // tokenization in general is contextual. Summing token counts from subsections of a string + // will often result in a different (and slightly larger) token count compared to counting + // tokens in the same string as a whole. + // + // We accept that here, and opt to always use the slightly less accurate (but larger) number + // for consistency. + let total = (messages + per_file.iter().flatten().sum::()).saturating_sub(baseline); + Ok(TokenCounts { - total: per_file.iter().flatten().sum::() + messages, + total, messages, per_file, + baseline, }) } @@ -499,32 +515,53 @@ pub async fn get_file_token_count( ) })?; - let token_count = count_tokens_in_file(&doc.content, &file.ranges); + let token_count = count_tokens_for_file(&file.path, &doc.content, &file.ranges); Ok(Json(token_count)) } -fn count_tokens_in_file(body: &str, ranges: &[Range]) -> usize { - let mut token_count = 0; +fn count_tokens_for_file(path: &str, body: &str, ranges: &[Range]) -> usize { let core_bpe = tiktoken_rs::get_bpe_from_model("gpt-4-0613").unwrap(); + let mut chunks = Vec::new(); + if ranges.is_empty() { - token_count = core_bpe.encode_ordinary(body).len(); + let numbered_body = body + .lines() + .enumerate() + .map(|(i, line)| format!("{} {line}\n", i + 1)) + .collect::>() + .join("\n"); + + chunks.push(numbered_body); } else { let lines = body.lines().collect::>(); for range in ranges { let chunk = lines .iter() .copied() + .enumerate() .skip(range.start) .take(range.end - range.start) + .map(|(i, line)| format!("{} {line}\n", range.start + i + 1)) .collect::>() .join("\n"); - token_count += core_bpe.encode_ordinary(&chunk).len(); + + chunks.push(chunk); } } - token_count + // Here, we build up a pseudo context in order to count tokens more accurately. This includes + // the path twice; once for the full path list under the `##### PATHS #####` section, and + // another time for the path when it is re-printed above the code chunk. + + let mut pseudo_context = format!("{path}\n"); + + for chunk in chunks { + pseudo_context += &format!("### {path} ###\n{chunk}\n"); + } + + core_bpe.encode_ordinary(&pseudo_context).len() } pub async fn generate( @@ -565,7 +602,7 @@ pub async fn generate( .with_payload("messages", &messages), ); - let llm_context = generate_llm_context((*app).clone(), context).await?; + let llm_context = generate_llm_context((*app).clone(), &context).await?; let system_prompt = prompts::studio_article_prompt(&llm_context); let llm_messages = iter::once(llm_gateway::api::Message::system(&system_prompt)) .chain(messages.iter().map(llm_gateway::api::Message::from)) @@ -629,7 +666,8 @@ pub async fn generate( Ok(Sse::new(Box::pin(stream))) } -async fn generate_llm_context(app: Application, context: Vec) -> Result { +#[allow(clippy::single_range_in_vec_init)] +async fn generate_llm_context(app: Application, context: &[ContextFile]) -> Result { let mut s = String::new(); s += "##### PATHS #####\n"; @@ -749,6 +787,7 @@ pub struct Import { } /// Returns a new studio UUID, or the `?studio_id=...` query param if present. +#[allow(clippy::single_range_in_vec_init)] pub async fn import( app: Extension, user: Extension,