mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
095474a901 | ||
|
|
d63f3cf9d9 | ||
|
|
3d85a0595c | ||
|
|
84cf524d84 | ||
|
|
7c72578765 | ||
|
|
483b433aab | ||
|
|
bcca1d6d7d | ||
|
|
0e88eecc30 | ||
|
|
a4685e34ab | ||
|
|
76dc2f5a12 | ||
|
|
7fc068f8b2 | ||
|
|
91caf129ed | ||
|
|
92578881df | ||
|
|
28986f4355 | ||
|
|
41a6eb48a0 | ||
|
|
92ae76168c | ||
|
|
f1dd16be82 | ||
|
|
cc2837b740 | ||
|
|
0633d1f23c | ||
|
|
8bc4f1e520 | ||
|
|
c962fdd636 | ||
|
|
8e036a340f |
86 changed files with 1556 additions and 1223 deletions
|
|
@ -21,7 +21,7 @@ AUTH_URL="http://localhost:3000"
|
||||||
|
|
||||||
DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot)
|
DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot)
|
||||||
SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
|
SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
|
||||||
# CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exists)
|
CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exists)
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
# EMAIL_FROM_ADDRESS="" # The from address for transactional emails.
|
# EMAIL_FROM_ADDRESS="" # The from address for transactional emails.
|
||||||
|
|
@ -29,7 +29,6 @@ SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
|
||||||
|
|
||||||
# PostHog
|
# PostHog
|
||||||
# POSTHOG_PAPIK=""
|
# POSTHOG_PAPIK=""
|
||||||
# NEXT_PUBLIC_POSTHOG_PAPIK=""
|
|
||||||
|
|
||||||
# Sentry
|
# Sentry
|
||||||
# SENTRY_BACKEND_DSN=""
|
# SENTRY_BACKEND_DSN=""
|
||||||
|
|
|
||||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 👾 Discord
|
- name: 👾 Discord
|
||||||
url: https://discord.gg/GbXMEM5H
|
url: https://discord.gg/HDScTs3ptP
|
||||||
about: Something else? Join the Discord!
|
about: Something else? Join the Discord!
|
||||||
|
|
|
||||||
1
.github/workflows/_gcp-deploy.yml
vendored
1
.github/workflows/_gcp-deploy.yml
vendored
|
|
@ -55,7 +55,6 @@ jobs:
|
||||||
${{ env.IMAGE_PATH }}:latest
|
${{ env.IMAGE_PATH }}:latest
|
||||||
build-args: |
|
build-args: |
|
||||||
NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }}
|
NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }}
|
||||||
NEXT_PUBLIC_POSTHOG_PAPIK=${{ vars.NEXT_PUBLIC_POSTHOG_PAPIK }}
|
|
||||||
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT }}
|
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT }}
|
||||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
|
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
|
||||||
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
|
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
|
||||||
|
|
|
||||||
1
.github/workflows/ghcr-publish.yml
vendored
1
.github/workflows/ghcr-publish.yml
vendored
|
|
@ -77,7 +77,6 @@ jobs:
|
||||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true,annotation.org.opencontainers.image.description=Blazingly fast code search
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true,annotation.org.opencontainers.image.description=Blazingly fast code search
|
||||||
build-args: |
|
build-args: |
|
||||||
NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }}
|
NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }}
|
||||||
NEXT_PUBLIC_POSTHOG_PAPIK=${{ vars.NEXT_PUBLIC_POSTHOG_PAPIK }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
31
CHANGELOG.md
31
CHANGELOG.md
|
|
@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed review agent so that it works with GHES instances [#611](https://github.com/sourcebot-dev/sourcebot/pull/611)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added support for arbitrary user IDs required for OpenShift. [#658](https://github.com/sourcebot-dev/sourcebot/pull/658)
|
||||||
|
|
||||||
|
### Updated
|
||||||
|
- Improved error messages in file source api. [#665](https://github.com/sourcebot-dev/sourcebot/pull/665)
|
||||||
|
|
||||||
|
## [4.10.2] - 2025-12-04
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue where the disable telemetry flag was not being respected for web server telemetry. [#657](https://github.com/sourcebot-dev/sourcebot/pull/657)
|
||||||
|
|
||||||
|
## [4.10.1] - 2025-12-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added `ALWAYS_INDEX_FILE_PATTERNS` environment variable to allow specifying a comma seperated list of glob patterns matching file paths that should always be indexed, regardless of size or # of trigrams. [#631](https://github.com/sourcebot-dev/sourcebot/pull/631)
|
||||||
|
- Added button to explore menu to toggle cross-repository search. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647)
|
||||||
|
- Added server side telemetry for search metrics. [#652](https://github.com/sourcebot-dev/sourcebot/pull/652)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue where single quotes could not be used in search queries. [#629](https://github.com/sourcebot-dev/sourcebot/pull/629)
|
||||||
|
- Fixed issue where files with special characters would fail to load. [#636](https://github.com/sourcebot-dev/sourcebot/issues/636)
|
||||||
|
- Fixed Ask performance issues. [#632](https://github.com/sourcebot-dev/sourcebot/pull/632)
|
||||||
|
- Fixed regression where creating a new Ask thread when unauthenticated would result in a 404. [#641](https://github.com/sourcebot-dev/sourcebot/pull/641)
|
||||||
|
- Updated react and next package versions to fix CVE 2025-55182. [#654](https://github.com/sourcebot-dev/sourcebot/pull/654)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changed the default behaviour for code nav to scope references & definitions search to the current repository. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647)
|
||||||
|
|
||||||
## [4.10.0] - 2025-11-24
|
## [4.10.0] - 2025-11-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
48
Dockerfile
48
Dockerfile
|
|
@ -1,3 +1,4 @@
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
# ------ Global scope variables ------
|
# ------ Global scope variables ------
|
||||||
|
|
||||||
# Set of global build arguments.
|
# Set of global build arguments.
|
||||||
|
|
@ -8,11 +9,6 @@
|
||||||
# @see: https://docs.docker.com/build/building/variables/#scoping
|
# @see: https://docs.docker.com/build/building/variables/#scoping
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_SOURCEBOT_VERSION
|
ARG NEXT_PUBLIC_SOURCEBOT_VERSION
|
||||||
# PAPIK = Project API Key
|
|
||||||
# Note that this key does not need to be kept secret, so it's not
|
|
||||||
# necessary to use Docker build secrets here.
|
|
||||||
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
|
|
||||||
ARG NEXT_PUBLIC_POSTHOG_PAPIK
|
|
||||||
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||||
ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT
|
ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT
|
||||||
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||||
|
|
@ -57,8 +53,6 @@ ENV SKIP_ENV_VALIDATION=1
|
||||||
# -----------
|
# -----------
|
||||||
ARG NEXT_PUBLIC_SOURCEBOT_VERSION
|
ARG NEXT_PUBLIC_SOURCEBOT_VERSION
|
||||||
ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION
|
ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION
|
||||||
ARG NEXT_PUBLIC_POSTHOG_PAPIK
|
|
||||||
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$NEXT_PUBLIC_POSTHOG_PAPIK
|
|
||||||
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||||
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||||
ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT
|
ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT
|
||||||
|
|
@ -148,14 +142,12 @@ fi
|
||||||
|
|
||||||
ENV SKIP_ENV_VALIDATION=0
|
ENV SKIP_ENV_VALIDATION=0
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
|
|
||||||
# ------ Runner ------
|
# ------ Runner ------
|
||||||
FROM node-alpine AS runner
|
FROM node-alpine AS runner
|
||||||
# -----------
|
# -----------
|
||||||
ARG NEXT_PUBLIC_SOURCEBOT_VERSION
|
ARG NEXT_PUBLIC_SOURCEBOT_VERSION
|
||||||
ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION
|
ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION
|
||||||
ARG NEXT_PUBLIC_POSTHOG_PAPIK
|
|
||||||
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$NEXT_PUBLIC_POSTHOG_PAPIK
|
|
||||||
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||||
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||||
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||||
|
|
@ -178,6 +170,12 @@ ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot
|
||||||
ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db
|
ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db
|
||||||
ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis
|
ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis
|
||||||
ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem
|
ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem
|
||||||
|
# PAPIK = Project API Key
|
||||||
|
# Note that this key does not need to be kept secret, so it's not
|
||||||
|
# necessary to use Docker build secrets here.
|
||||||
|
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
|
||||||
|
# @note: this is also declared in the shared env.server.ts file.
|
||||||
|
ENV POSTHOG_PAPIK=phc_lLPuFFi5LH6c94eFJcqvYVFwiJffVcV6HD8U4a1OnRW
|
||||||
|
|
||||||
# Valid values are: debug, info, warn, error
|
# Valid values are: debug, info, warn, error
|
||||||
ENV SOURCEBOT_LOG_LEVEL=info
|
ENV SOURCEBOT_LOG_LEVEL=info
|
||||||
|
|
@ -197,6 +195,7 @@ RUN addgroup -g $GID sourcebot && \
|
||||||
adduser -D -u $UID -h /app -S sourcebot && \
|
adduser -D -u $UID -h /app -S sourcebot && \
|
||||||
adduser sourcebot postgres && \
|
adduser sourcebot postgres && \
|
||||||
adduser sourcebot redis && \
|
adduser sourcebot redis && \
|
||||||
|
chown -R sourcebot /app && \
|
||||||
adduser sourcebot node && \
|
adduser sourcebot node && \
|
||||||
mkdir /var/log/sourcebot && \
|
mkdir /var/log/sourcebot && \
|
||||||
chown sourcebot /var/log/sourcebot
|
chown sourcebot /var/log/sourcebot
|
||||||
|
|
@ -220,22 +219,23 @@ COPY --from=zoekt-builder \
|
||||||
/cmd/zoekt-index \
|
/cmd/zoekt-index \
|
||||||
/usr/local/bin/
|
/usr/local/bin/
|
||||||
|
|
||||||
|
RUN chown -R sourcebot:sourcebot /app
|
||||||
|
|
||||||
# Copy zoekt proto files (needed for gRPC client at runtime)
|
# Copy zoekt proto files (needed for gRPC client at runtime)
|
||||||
COPY vendor/zoekt/grpc/protos /app/vendor/zoekt/grpc/protos
|
COPY --chown=sourcebot:sourcebot vendor/zoekt/grpc/protos /app/vendor/zoekt/grpc/protos
|
||||||
|
|
||||||
# Copy all of the things
|
# Copy all of the things
|
||||||
COPY --from=web-builder /app/packages/web/public ./packages/web/public
|
COPY --chown=sourcebot:sourcebot --from=web-builder /app/packages/web/public ./packages/web/public
|
||||||
COPY --from=web-builder /app/packages/web/.next/standalone ./
|
COPY --chown=sourcebot:sourcebot --from=web-builder /app/packages/web/.next/standalone ./
|
||||||
COPY --from=web-builder /app/packages/web/.next/static ./packages/web/.next/static
|
COPY --chown=sourcebot:sourcebot --from=web-builder /app/packages/web/.next/static ./packages/web/.next/static
|
||||||
|
|
||||||
COPY --from=backend-builder /app/node_modules ./node_modules
|
COPY --chown=sourcebot:sourcebot --from=backend-builder /app/node_modules ./node_modules
|
||||||
COPY --from=backend-builder /app/packages/backend ./packages/backend
|
COPY --chown=sourcebot:sourcebot --from=backend-builder /app/packages/backend ./packages/backend
|
||||||
|
|
||||||
COPY --from=shared-libs-builder /app/node_modules ./node_modules
|
COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/db ./packages/db
|
||||||
COPY --from=shared-libs-builder /app/packages/db ./packages/db
|
COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/schemas ./packages/schemas
|
||||||
COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas
|
COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/shared ./packages/shared
|
||||||
COPY --from=shared-libs-builder /app/packages/shared ./packages/shared
|
COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLanguage
|
||||||
COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLanguage
|
|
||||||
|
|
||||||
# Fixes git "dubious ownership" issues when the volume is mounted with different permissions to the container.
|
# Fixes git "dubious ownership" issues when the volume is mounted with different permissions to the container.
|
||||||
RUN git config --global safe.directory "*"
|
RUN git config --global safe.directory "*"
|
||||||
|
|
@ -246,9 +246,11 @@ RUN mkdir -p /run/postgresql && \
|
||||||
chmod 775 /run/postgresql
|
chmod 775 /run/postgresql
|
||||||
|
|
||||||
# Make app directory accessible to both root and sourcebot user
|
# Make app directory accessible to both root and sourcebot user
|
||||||
RUN chown -R sourcebot:sourcebot /app
|
RUN chown -R sourcebot /app \
|
||||||
|
&& chgrp -R 0 /app \
|
||||||
|
&& chmod -R g=u /app
|
||||||
# Make data directory accessible to both root and sourcebot user
|
# Make data directory accessible to both root and sourcebot user
|
||||||
RUN chown -R sourcebot:sourcebot /data
|
RUN chown -R sourcebot /data
|
||||||
|
|
||||||
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
COPY prefix-output.sh ./prefix-output.sh
|
COPY prefix-output.sh ./prefix-output.sh
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@
|
||||||
"socials": {
|
"socials": {
|
||||||
"github": "https://github.com/sourcebot-dev/sourcebot",
|
"github": "https://github.com/sourcebot-dev/sourcebot",
|
||||||
"twitter": "https://x.com/sourcebot_dev",
|
"twitter": "https://x.com/sourcebot_dev",
|
||||||
"discord": "https://discord.gg/GbXMEM5H",
|
"discord": "https://discord.gg/HDScTs3ptP",
|
||||||
"linkedin": "https://www.linkedin.com/company/sourcebot"
|
"linkedin": "https://www.linkedin.com/company/sourcebot"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,4 @@ Sourcebot's built-in authentication system gates your deployment, and allows adm
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
- If you experience issues logging in, logging out, or accessing an organization you should have access to, try clearing your cookies & performing a full page refresh (`Cmd/Ctrl + Shift + R` on most browsers).
|
- If you experience issues logging in, logging out, or accessing an organization you should have access to, try clearing your cookies & performing a full page refresh (`Cmd/Ctrl + Shift + R` on most browsers).
|
||||||
- Still not working? Reach out to us on our [discord](https://discord.gg/GbXMEM5H) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose)
|
- Still not working? Reach out to us on our [discord](https://discord.gg/HDScTs3ptP) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose)
|
||||||
|
|
@ -35,6 +35,7 @@ The following environment variables allow you to configure your Sourcebot deploy
|
||||||
| `SOURCEBOT_STRUCTURED_LOGGING_FILE` | - | <p>Optional file to log to if structured logging is enabled</p> |
|
| `SOURCEBOT_STRUCTURED_LOGGING_FILE` | - | <p>Optional file to log to if structured logging is enabled</p> |
|
||||||
| `SOURCEBOT_TELEMETRY_DISABLED` | `false` | <p>Enables/disables telemetry collection in Sourcebot. See [this doc](/docs/overview.mdx#telemetry) for more info.</p> |
|
| `SOURCEBOT_TELEMETRY_DISABLED` | `false` | <p>Enables/disables telemetry collection in Sourcebot. See [this doc](/docs/overview.mdx#telemetry) for more info.</p> |
|
||||||
| `DEFAULT_MAX_MATCH_COUNT` | `10000` | <p>The default maximum number of search results to return when using search in the web app.</p> |
|
| `DEFAULT_MAX_MATCH_COUNT` | `10000` | <p>The default maximum number of search results to return when using search in the web app.</p> |
|
||||||
|
| `ALWAYS_INDEX_FILE_PATTERNS` | - | <p>A comma separated list of glob patterns matching file paths that should always be indexed, regardless of size or number of trigrams.</p> |
|
||||||
|
|
||||||
### Enterprise Environment Variables
|
### Enterprise Environment Variables
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,26 @@ To learn more about how to create a connection for a specific code host, check o
|
||||||
|
|
||||||
<Note>Missing your code host? [Submit a feature request on GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new?template=feature_request.md).</Note>
|
<Note>Missing your code host? [Submit a feature request on GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new?template=feature_request.md).</Note>
|
||||||
|
|
||||||
|
## Indexing Large Files
|
||||||
|
|
||||||
|
By default, Sourcebot will skip indexing files that are larger than 2MB or have more than 20,000 trigrams. You can configure this by setting the `maxFileSize` and `maxTrigramCount` [settings](/docs/configuration/config-file#settings).
|
||||||
|
|
||||||
|
These limits can be ignored for specific files by passing in a comma separated list of glob patterns matching file paths to the `ALWAYS_INDEX_FILE_PATTERNS` environment variable. For example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Always index all .sum and .lock files
|
||||||
|
ALWAYS_INDEX_FILE_PATTERNS=**/*.sum,**/*.lock
|
||||||
|
```
|
||||||
|
|
||||||
|
Files that have been skipped are assigned the `skipped` language. You can view a list of all skipped files by using the following query:
|
||||||
|
```
|
||||||
|
lang:skipped
|
||||||
|
```
|
||||||
|
|
||||||
|
## Indexing Binary Files
|
||||||
|
|
||||||
|
Binary files cannot be indexed by Sourcebot. See [#575](https://github.com/sourcebot-dev/sourcebot/issues/575) for more information.
|
||||||
|
|
||||||
|
|
||||||
## Schema reference
|
## Schema reference
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import LicenseKeyRequired from '/snippets/license-key-required.mdx'
|
||||||
| **Go to definition** | Clicking the "go to definition" button in the popover or clicking the symbol name navigates to the symbol's definition. |
|
| **Go to definition** | Clicking the "go to definition" button in the popover or clicking the symbol name navigates to the symbol's definition. |
|
||||||
| **Find references** | Clicking the "find all references" button in the popover lists all references in the explore panel. |
|
| **Find references** | Clicking the "find all references" button in the popover lists all references in the explore panel. |
|
||||||
| **Explore panel** | Lists all references and definitions for the symbol selected in the popover. |
|
| **Explore panel** | Lists all references and definitions for the symbol selected in the popover. |
|
||||||
|
| **Cross-repository navigation** | You can search across all repositories by clicking the globe icon in the explore panel. By default, references and definitions are scoped to the repository where the symbol is being resolved. |
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
---
|
---
|
||||||
title: "Permission syncing"
|
title: "Permission syncing"
|
||||||
sidebarTitle: "Permission syncing"
|
sidebarTitle: "Permission syncing"
|
||||||
tag: "experimental"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
import LicenseKeyRequired from '/snippets/license-key-required.mdx'
|
import LicenseKeyRequired from '/snippets/license-key-required.mdx'
|
||||||
import ExperimentalFeatureWarning from '/snippets/experimental-feature-warning.mdx'
|
|
||||||
|
|
||||||
<LicenseKeyRequired />
|
<LicenseKeyRequired />
|
||||||
<ExperimentalFeatureWarning />
|
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ If your deployment is dependent on these features, please [reach out](https://gi
|
||||||
After updating your configuration file, restart your Sourcebot deployment to pick up the new changes.
|
After updating your configuration file, restart your Sourcebot deployment to pick up the new changes.
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="You're done!">
|
<Step title="You're done!">
|
||||||
Congrats, you've successfully migrated to v3! Please let us know what you think of the new features by reaching out on our [discord](https://discord.gg/GbXMEM5H) or on [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose).
|
Congrats, you've successfully migrated to v3! Please let us know what you think of the new features by reaching out on our [discord](https://discord.gg/HDScTs3ptP) or on [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose).
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
|
@ -90,4 +90,4 @@ Some things to check:
|
||||||
- Make sure you have a name for each `connection`, and that the name only contains letters, digits, hyphens, or underscores
|
- Make sure you have a name for each `connection`, and that the name only contains letters, digits, hyphens, or underscores
|
||||||
- Make sure each `connection` has a `type` field with a valid value (`gitlab`, `github`, `gitea`, `gerrit`)
|
- Make sure each `connection` has a `type` field with a valid value (`gitlab`, `github`, `gitea`, `gerrit`)
|
||||||
|
|
||||||
Having troubles migrating from v2 to v3? Reach out to us on [discord](https://discord.gg/GbXMEM5H) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose) and we'll try our best to help
|
Having troubles migrating from v2 to v3? Reach out to us on [discord](https://discord.gg/HDScTs3ptP) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose) and we'll try our best to help
|
||||||
|
|
@ -40,7 +40,7 @@ Please note that the following features are no longer supported in v4:
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="You're done!">
|
<Step title="You're done!">
|
||||||
Congrats, you've successfully migrated to v4! Please let us know what you think of the new features by reaching out on our [discord](https://discord.gg/GbXMEM5H) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose)
|
Congrats, you've successfully migrated to v4! Please let us know what you think of the new features by reaching out on our [discord](https://discord.gg/HDScTs3ptP) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose)
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
|
@ -58,4 +58,4 @@ to finish upgrading to v4 in single-tenant mode.
|
||||||
- If you're hitting issues with signing into your Sourcebot instance, make sure you're setting `AUTH_URL` correctly to your deployment domain (ex. `https://sourcebot.yourcompany.com`)
|
- If you're hitting issues with signing into your Sourcebot instance, make sure you're setting `AUTH_URL` correctly to your deployment domain (ex. `https://sourcebot.yourcompany.com`)
|
||||||
|
|
||||||
|
|
||||||
Having troubles migrating from v3 to v4? Reach out to us on [discord](https://discord.gg/GbXMEM5H) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose) and we'll try our best to help
|
Having troubles migrating from v3 to v4? Reach out to us on [discord](https://discord.gg/HDScTs3ptP) or [GitHub](https://github.com/sourcebot-dev/sourcebot/issues/new/choose) and we'll try our best to help
|
||||||
|
|
@ -66,12 +66,6 @@ fi
|
||||||
|
|
||||||
echo -e "\e[34m[Info] Sourcebot version: $NEXT_PUBLIC_SOURCEBOT_VERSION\e[0m"
|
echo -e "\e[34m[Info] Sourcebot version: $NEXT_PUBLIC_SOURCEBOT_VERSION\e[0m"
|
||||||
|
|
||||||
# If we don't have a PostHog key, then we need to disable telemetry.
|
|
||||||
if [ -z "$NEXT_PUBLIC_POSTHOG_PAPIK" ]; then
|
|
||||||
echo -e "\e[33m[Warning] NEXT_PUBLIC_POSTHOG_PAPIK was not set. Setting SOURCEBOT_TELEMETRY_DISABLED.\e[0m"
|
|
||||||
export SOURCEBOT_TELEMETRY_DISABLED=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
|
if [ -n "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
|
||||||
# Validate that SOURCEBOT_TELEMETRY_DISABLED is either "true" or "false"
|
# Validate that SOURCEBOT_TELEMETRY_DISABLED is either "true" or "false"
|
||||||
if [ "$SOURCEBOT_TELEMETRY_DISABLED" != "true" ] && [ "$SOURCEBOT_TELEMETRY_DISABLED" != "false" ]; then
|
if [ "$SOURCEBOT_TELEMETRY_DISABLED" != "true" ] && [ "$SOURCEBOT_TELEMETRY_DISABLED" != "false" ]; then
|
||||||
|
|
@ -159,7 +153,7 @@ if [ ! -f "$FIRST_RUN_FILE" ]; then
|
||||||
# (if telemetry is enabled)
|
# (if telemetry is enabled)
|
||||||
if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then
|
if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then
|
||||||
if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{
|
if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{
|
||||||
"api_key": "'"$NEXT_PUBLIC_POSTHOG_PAPIK"'",
|
"api_key": "'"$POSTHOG_PAPIK"'",
|
||||||
"event": "install",
|
"event": "install",
|
||||||
"distinct_id": "'"$SOURCEBOT_INSTALL_ID"'",
|
"distinct_id": "'"$SOURCEBOT_INSTALL_ID"'",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -179,7 +173,7 @@ else
|
||||||
|
|
||||||
if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then
|
if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then
|
||||||
if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{
|
if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{
|
||||||
"api_key": "'"$NEXT_PUBLIC_POSTHOG_PAPIK"'",
|
"api_key": "'"$POSTHOG_PAPIK"'",
|
||||||
"event": "upgrade",
|
"event": "upgrade",
|
||||||
"distinct_id": "'"$SOURCEBOT_INSTALL_ID"'",
|
"distinct_id": "'"$SOURCEBOT_INSTALL_ID"'",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import { PosthogEvent, PosthogEventMap } from './posthogEvents.js';
|
||||||
|
|
||||||
let posthog: PostHog | undefined = undefined;
|
let posthog: PostHog | undefined = undefined;
|
||||||
|
|
||||||
if (clientEnv.NEXT_PUBLIC_POSTHOG_PAPIK) {
|
if (env.POSTHOG_PAPIK) {
|
||||||
posthog = new PostHog(
|
posthog = new PostHog(
|
||||||
clientEnv.NEXT_PUBLIC_POSTHOG_PAPIK,
|
env.POSTHOG_PAPIK,
|
||||||
{
|
{
|
||||||
host: "https://us.i.posthog.com",
|
host: "https://us.i.posthog.com",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Repo } from "@sourcebot/db";
|
import { Repo } from "@sourcebot/db";
|
||||||
import { createLogger } from "@sourcebot/shared";
|
import { createLogger, env } from "@sourcebot/shared";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import { INDEX_CACHE_DIR } from "./constants.js";
|
import { INDEX_CACHE_DIR } from "./constants.js";
|
||||||
import { Settings } from "./types.js";
|
import { Settings } from "./types.js";
|
||||||
|
|
@ -11,6 +11,8 @@ export const indexGitRepository = async (repo: Repo, settings: Settings, revisio
|
||||||
const { path: repoPath } = getRepoPath(repo);
|
const { path: repoPath } = getRepoPath(repo);
|
||||||
const shardPrefix = getShardPrefix(repo.orgId, repo.id);
|
const shardPrefix = getShardPrefix(repo.orgId, repo.id);
|
||||||
|
|
||||||
|
const largeFileGlobPatterns = env.ALWAYS_INDEX_FILE_PATTERNS?.split(',').map(pattern => pattern.trim()) ?? [];
|
||||||
|
|
||||||
const command = [
|
const command = [
|
||||||
'zoekt-git-index',
|
'zoekt-git-index',
|
||||||
'-allow_missing_branches',
|
'-allow_missing_branches',
|
||||||
|
|
@ -21,6 +23,7 @@ export const indexGitRepository = async (repo: Repo, settings: Settings, revisio
|
||||||
`-tenant_id ${repo.orgId}`,
|
`-tenant_id ${repo.orgId}`,
|
||||||
`-repo_id ${repo.id}`,
|
`-repo_id ${repo.id}`,
|
||||||
`-shard_prefix ${shardPrefix}`,
|
`-shard_prefix ${shardPrefix}`,
|
||||||
|
...largeFileGlobPatterns.map((pattern) => `-large_file ${pattern}`),
|
||||||
repoPath
|
repoPath
|
||||||
].join(' ');
|
].join(' ');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- First, remove the NOT NULL constraint on the createdById column.
|
||||||
|
ALTER TABLE "Chat" ALTER COLUMN "createdById" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- Then, set all chats created by the guest user (id: 1) to have a NULL createdById.
|
||||||
|
UPDATE "Chat" SET "createdById" = NULL WHERE "createdById" = '1';
|
||||||
|
|
@ -437,8 +437,8 @@ model Chat {
|
||||||
|
|
||||||
name String?
|
name String?
|
||||||
|
|
||||||
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
|
createdBy User? @relation(fields: [createdById], references: [id], onDelete: Cascade)
|
||||||
createdById String
|
createdById String?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.0.11] - 2025-12-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated API client to match the latest Sourcebot release. [#652](https://github.com/sourcebot-dev/sourcebot/pull/652)
|
||||||
|
|
||||||
|
## [1.0.10] - 2025-11-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Updated API client to match the latest Sourcebot release. [#555](https://github.com/sourcebot-dev/sourcebot/pull/555)
|
- Updated API client to match the latest Sourcebot release. [#555](https://github.com/sourcebot-dev/sourcebot/pull/555)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sourcebot/mcp",
|
"name": "@sourcebot/mcp",
|
||||||
"version": "1.0.9",
|
"version": "1.0.11",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ export const search = async (request: SearchRequest): Promise<SearchResponse | S
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Org-Domain': '~',
|
|
||||||
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
||||||
},
|
},
|
||||||
body: JSON.stringify(request)
|
body: JSON.stringify(request)
|
||||||
|
|
@ -26,7 +25,6 @@ export const listRepos = async (): Promise<ListRepositoriesResponse | ServiceErr
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Org-Domain': '~',
|
|
||||||
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
||||||
},
|
},
|
||||||
}).then(response => response.json());
|
}).then(response => response.json());
|
||||||
|
|
@ -43,7 +41,6 @@ export const getFileSource = async (request: FileSourceRequest): Promise<FileSou
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Org-Domain': '~',
|
|
||||||
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
||||||
},
|
},
|
||||||
body: JSON.stringify(request)
|
body: JSON.stringify(request)
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ server.tool(
|
||||||
contextLines: env.DEFAULT_CONTEXT_LINES,
|
contextLines: env.DEFAULT_CONTEXT_LINES,
|
||||||
isRegexEnabled: true,
|
isRegexEnabled: true,
|
||||||
isCaseSensitivityEnabled: caseSensitive,
|
isCaseSensitivityEnabled: caseSensitive,
|
||||||
|
source: 'mcp'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export const searchOptionsSchema = z.object({
|
||||||
|
|
||||||
export const searchRequestSchema = z.object({
|
export const searchRequestSchema = z.object({
|
||||||
query: z.string(), // The zoekt query to execute.
|
query: z.string(), // The zoekt query to execute.
|
||||||
|
source: z.string().optional(), // The source of the search request.
|
||||||
...searchOptionsSchema.shape,
|
...searchOptionsSchema.shape,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
export const
|
export const
|
||||||
negate = 22,
|
negate = 23,
|
||||||
Program = 1,
|
Program = 1,
|
||||||
OrExpr = 2,
|
OrExpr = 2,
|
||||||
AndExpr = 3,
|
AndExpr = 3,
|
||||||
|
|
@ -18,4 +18,5 @@ export const
|
||||||
SymExpr = 15,
|
SymExpr = 15,
|
||||||
RepoSetExpr = 16,
|
RepoSetExpr = 16,
|
||||||
ParenExpr = 17,
|
ParenExpr = 17,
|
||||||
Term = 18
|
QuotedTerm = 18,
|
||||||
|
Term = 19
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -24,12 +24,13 @@ expr {
|
||||||
NegateExpr |
|
NegateExpr |
|
||||||
ParenExpr |
|
ParenExpr |
|
||||||
PrefixExpr |
|
PrefixExpr |
|
||||||
|
QuotedTerm |
|
||||||
Term
|
Term
|
||||||
}
|
}
|
||||||
|
|
||||||
NegateExpr { !negate negate (PrefixExpr | ParenExpr) }
|
NegateExpr { !negate negate (PrefixExpr | ParenExpr) }
|
||||||
|
|
||||||
ParenExpr { "(" query ")" }
|
ParenExpr { "(" query? ")" }
|
||||||
|
|
||||||
PrefixExpr {
|
PrefixExpr {
|
||||||
ArchivedExpr |
|
ArchivedExpr |
|
||||||
|
|
@ -63,7 +64,8 @@ archivedValue { "yes" | "no" | "only" }
|
||||||
forkValue { "yes" | "no" | "only" }
|
forkValue { "yes" | "no" | "only" }
|
||||||
visibilityValue { "public" | "private" | "any" }
|
visibilityValue { "public" | "private" | "any" }
|
||||||
|
|
||||||
Term { quotedString | word }
|
QuotedTerm { quotedString }
|
||||||
|
Term { word }
|
||||||
|
|
||||||
value { quotedString | word }
|
value { quotedString | word }
|
||||||
|
|
||||||
|
|
@ -86,9 +88,7 @@ value { quotedString | word }
|
||||||
|
|
||||||
quotedString { '"' (!["\\\n] | "\\" _)* '"' }
|
quotedString { '"' (!["\\\n] | "\\" _)* '"' }
|
||||||
|
|
||||||
// Allow almost anything in a word except spaces, parens, quotes
|
word { (![ \t\n()]) (![ \t\n():] | ":" | "-")* }
|
||||||
// Colons and dashes are allowed anywhere in words (including at the start)
|
|
||||||
word { (![ \t\n()"]) (![ \t\n()":] | ":" | "-")* }
|
|
||||||
|
|
||||||
space { $[ \t\n]+ }
|
space { $[ \t\n]+ }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(ParenExpr(Term(⚠)))
|
Program(ParenExpr)
|
||||||
|
|
||||||
# Simple grouping
|
# Simple grouping
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Dash in middle
|
# Dash in middle
|
||||||
|
|
||||||
|
|
@ -244,7 +244,7 @@ Program(NegateExpr(ParenExpr(AndExpr(PrefixExpr(FileExpr),PrefixExpr(LangExpr)))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(NegateExpr(ParenExpr(Term(⚠))))
|
Program(NegateExpr(ParenExpr))
|
||||||
|
|
||||||
# Negate with space after dash
|
# Negate with space after dash
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ Program(OrExpr(Term,NegateExpr(PrefixExpr(FileExpr))))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(OrExpr(Term,Term))
|
Program(OrExpr(QuotedTerm,QuotedTerm))
|
||||||
|
|
||||||
# OR with different prefixes
|
# OR with different prefixes
|
||||||
|
|
||||||
|
|
@ -260,7 +260,7 @@ Program(OrExpr(PrefixExpr(FileExpr),PrefixExpr(FileExpr)))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(OrExpr(ParenExpr(Term(⚠)),ParenExpr(Term(⚠))))
|
Program(OrExpr(ParenExpr,ParenExpr))
|
||||||
|
|
||||||
# OR with negated groups
|
# OR with negated groups
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ Program(AndExpr(ParenExpr(OrExpr(Term,Term)),ParenExpr(OrExpr(Term,Term))))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Prefix with OR value doesn't split
|
# Prefix with OR value doesn't split
|
||||||
|
|
||||||
|
|
@ -188,7 +188,7 @@ Program(OrExpr(Term,ParenExpr(AndExpr(Term,Term))))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(OrExpr(ParenExpr(Term(⚠)),AndExpr(Term,Term)))
|
Program(OrExpr(ParenExpr,AndExpr(Term,Term)))
|
||||||
|
|
||||||
# Negation of empty group
|
# Negation of empty group
|
||||||
|
|
||||||
|
|
@ -196,5 +196,5 @@ Program(OrExpr(ParenExpr(Term(⚠)),AndExpr(Term,Term)))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(AndExpr(NegateExpr(ParenExpr(Term(⚠))),Term))
|
Program(AndExpr(NegateExpr(ParenExpr),Term))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,22 @@
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
|
Program(QuotedTerm)
|
||||||
|
|
||||||
|
# Leading unclosed quote
|
||||||
|
|
||||||
|
"hello
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
|
Program(Term)
|
||||||
|
|
||||||
|
# Trailing unclosed quote
|
||||||
|
|
||||||
|
hello"
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(Term)
|
||||||
|
|
||||||
# Quoted string with spaces
|
# Quoted string with spaces
|
||||||
|
|
@ -12,7 +28,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Multiple words in quotes
|
# Multiple words in quotes
|
||||||
|
|
||||||
|
|
@ -20,7 +36,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with escaped quote
|
# Quoted string with escaped quote
|
||||||
|
|
||||||
|
|
@ -28,7 +44,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with escaped backslash
|
# Quoted string with escaped backslash
|
||||||
|
|
||||||
|
|
@ -36,7 +52,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Double backslash
|
# Double backslash
|
||||||
|
|
||||||
|
|
@ -44,7 +60,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Multiple escaped quotes
|
# Multiple escaped quotes
|
||||||
|
|
||||||
|
|
@ -52,7 +68,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Mixed escaped characters
|
# Mixed escaped characters
|
||||||
|
|
||||||
|
|
@ -60,7 +76,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Empty quoted string
|
# Empty quoted string
|
||||||
|
|
||||||
|
|
@ -68,7 +84,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with only spaces
|
# Quoted string with only spaces
|
||||||
|
|
||||||
|
|
@ -76,7 +92,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string in file prefix
|
# Quoted string in file prefix
|
||||||
|
|
||||||
|
|
@ -116,7 +132,7 @@ Program(PrefixExpr(RevisionExpr))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(AndExpr(Term,Term))
|
Program(AndExpr(QuotedTerm,QuotedTerm))
|
||||||
|
|
||||||
# Quoted and unquoted mixed
|
# Quoted and unquoted mixed
|
||||||
|
|
||||||
|
|
@ -124,7 +140,7 @@ unquoted "quoted string" another
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(AndExpr(Term,Term,Term))
|
Program(AndExpr(Term,QuotedTerm,Term))
|
||||||
|
|
||||||
# Quoted string with parentheses inside
|
# Quoted string with parentheses inside
|
||||||
|
|
||||||
|
|
@ -132,7 +148,7 @@ Program(AndExpr(Term,Term,Term))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with brackets
|
# Quoted string with brackets
|
||||||
|
|
||||||
|
|
@ -140,7 +156,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with special chars
|
# Quoted string with special chars
|
||||||
|
|
||||||
|
|
@ -148,7 +164,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with colons
|
# Quoted string with colons
|
||||||
|
|
||||||
|
|
@ -156,7 +172,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with dashes
|
# Quoted string with dashes
|
||||||
|
|
||||||
|
|
@ -164,7 +180,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with dots
|
# Quoted string with dots
|
||||||
|
|
||||||
|
|
@ -172,7 +188,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with regex pattern
|
# Quoted string with regex pattern
|
||||||
|
|
||||||
|
|
@ -180,7 +196,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with forward slashes
|
# Quoted string with forward slashes
|
||||||
|
|
||||||
|
|
@ -188,7 +204,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with underscores
|
# Quoted string with underscores
|
||||||
|
|
||||||
|
|
@ -196,7 +212,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with numbers
|
# Quoted string with numbers
|
||||||
|
|
||||||
|
|
@ -204,7 +220,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with mixed case
|
# Quoted string with mixed case
|
||||||
|
|
||||||
|
|
@ -212,7 +228,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted prefix value with spaces
|
# Quoted prefix value with spaces
|
||||||
|
|
||||||
|
|
@ -236,7 +252,7 @@ Program(AndExpr(PrefixExpr(FileExpr),PrefixExpr(RepoExpr)))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(ParenExpr(Term))
|
Program(ParenExpr(QuotedTerm))
|
||||||
|
|
||||||
# Multiple quoted in parentheses
|
# Multiple quoted in parentheses
|
||||||
|
|
||||||
|
|
@ -244,7 +260,7 @@ Program(ParenExpr(Term))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(ParenExpr(AndExpr(Term,Term)))
|
Program(ParenExpr(AndExpr(QuotedTerm,QuotedTerm)))
|
||||||
|
|
||||||
# Quoted with escaped newline
|
# Quoted with escaped newline
|
||||||
|
|
||||||
|
|
@ -252,7 +268,7 @@ Program(ParenExpr(AndExpr(Term,Term)))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted with tab character
|
# Quoted with tab character
|
||||||
|
|
||||||
|
|
@ -260,7 +276,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Lang prefix with quoted value
|
# Lang prefix with quoted value
|
||||||
|
|
||||||
|
|
@ -292,7 +308,7 @@ Program(PrefixExpr(ContentExpr))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with hash
|
# Quoted string with hash
|
||||||
|
|
||||||
|
|
@ -300,7 +316,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with dollar sign
|
# Quoted string with dollar sign
|
||||||
|
|
||||||
|
|
@ -308,7 +324,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with percent
|
# Quoted string with percent
|
||||||
|
|
||||||
|
|
@ -316,7 +332,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with ampersand
|
# Quoted string with ampersand
|
||||||
|
|
||||||
|
|
@ -324,7 +340,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with asterisk
|
# Quoted string with asterisk
|
||||||
|
|
||||||
|
|
@ -332,7 +348,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with plus
|
# Quoted string with plus
|
||||||
|
|
||||||
|
|
@ -340,7 +356,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with equals
|
# Quoted string with equals
|
||||||
|
|
||||||
|
|
@ -348,7 +364,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with angle brackets
|
# Quoted string with angle brackets
|
||||||
|
|
||||||
|
|
@ -356,7 +372,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with pipe
|
# Quoted string with pipe
|
||||||
|
|
||||||
|
|
@ -364,7 +380,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with tilde
|
# Quoted string with tilde
|
||||||
|
|
||||||
|
|
@ -372,7 +388,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with backtick
|
# Quoted string with backtick
|
||||||
|
|
||||||
|
|
@ -380,7 +396,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with question mark
|
# Quoted string with question mark
|
||||||
|
|
||||||
|
|
@ -388,7 +404,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with exclamation
|
# Quoted string with exclamation
|
||||||
|
|
||||||
|
|
@ -396,7 +412,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with semicolon
|
# Quoted string with semicolon
|
||||||
|
|
||||||
|
|
@ -404,7 +420,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted string with comma
|
# Quoted string with comma
|
||||||
|
|
||||||
|
|
@ -412,7 +428,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Multiple quotes in content
|
# Multiple quotes in content
|
||||||
|
|
||||||
|
|
@ -428,7 +444,7 @@ Program(PrefixExpr(ContentExpr))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted file prefix as literal
|
# Quoted file prefix as literal
|
||||||
|
|
||||||
|
|
@ -436,7 +452,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted lang prefix as literal
|
# Quoted lang prefix as literal
|
||||||
|
|
||||||
|
|
@ -444,7 +460,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted partial prefix
|
# Quoted partial prefix
|
||||||
|
|
||||||
|
|
@ -452,7 +468,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Mix of quoted prefix and real prefix
|
# Mix of quoted prefix and real prefix
|
||||||
|
|
||||||
|
|
@ -460,7 +476,7 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(AndExpr(Term,PrefixExpr(FileExpr)))
|
Program(AndExpr(QuotedTerm,PrefixExpr(FileExpr)))
|
||||||
|
|
||||||
# Quoted short form prefix
|
# Quoted short form prefix
|
||||||
|
|
||||||
|
|
@ -468,7 +484,7 @@ Program(AndExpr(Term,PrefixExpr(FileExpr)))
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
# Quoted revision prefix
|
# Quoted revision prefix
|
||||||
|
|
||||||
|
|
@ -476,4 +492,12 @@ Program(Term)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(Term)
|
Program(QuotedTerm)
|
||||||
|
|
||||||
|
# Quotes can be used within words
|
||||||
|
|
||||||
|
name\s*=\s*"projectmanagementlugapi lang:HCL
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
|
Program(AndExpr(Term, PrefixExpr(LangExpr)))
|
||||||
|
|
@ -7,7 +7,6 @@ export const env = createEnv({
|
||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(SOURCEBOT_CLOUD_ENVIRONMENT).optional(),
|
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(SOURCEBOT_CLOUD_ENVIRONMENT).optional(),
|
||||||
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default("unknown"),
|
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default("unknown"),
|
||||||
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),
|
|
||||||
NEXT_PUBLIC_SENTRY_BACKEND_DSN: z.string().optional(),
|
NEXT_PUBLIC_SENTRY_BACKEND_DSN: z.string().optional(),
|
||||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().optional(),
|
NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().optional(),
|
||||||
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: z.string().optional(),
|
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: z.string().optional(),
|
||||||
|
|
@ -16,7 +15,6 @@ export const env = createEnv({
|
||||||
runtimeEnvStrict: {
|
runtimeEnvStrict: {
|
||||||
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
|
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
|
||||||
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||||
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
|
|
||||||
NEXT_PUBLIC_SENTRY_BACKEND_DSN: process.env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
|
NEXT_PUBLIC_SENTRY_BACKEND_DSN: process.env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
|
||||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
|
NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
|
||||||
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
|
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,8 @@ export const env = createEnv({
|
||||||
CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(Number.MAX_SAFE_INTEGER),
|
CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(Number.MAX_SAFE_INTEGER),
|
||||||
NODE_ENV: z.enum(["development", "test", "production"]),
|
NODE_ENV: z.enum(["development", "test", "production"]),
|
||||||
SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'),
|
SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'),
|
||||||
|
// @note: this is also declared in the Dockerfile.
|
||||||
|
POSTHOG_PAPIK: z.string().default("phc_lLPuFFi5LH6c94eFJcqvYVFwiJffVcV6HD8U4a1OnRW"),
|
||||||
|
|
||||||
// Database variables
|
// Database variables
|
||||||
// Either DATABASE_URL or DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, and DATABASE_NAME must be set.
|
// Either DATABASE_URL or DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, and DATABASE_NAME must be set.
|
||||||
|
|
@ -219,6 +221,9 @@ export const env = createEnv({
|
||||||
|
|
||||||
// Configure the default maximum number of search results to return by default.
|
// Configure the default maximum number of search results to return by default.
|
||||||
DEFAULT_MAX_MATCH_COUNT: numberSchema.default(10_000),
|
DEFAULT_MAX_MATCH_COUNT: numberSchema.default(10_000),
|
||||||
|
|
||||||
|
// A comma separated list of glob patterns that shwould always be indexed regardless of their size.
|
||||||
|
ALWAYS_INDEX_FILE_PATTERNS: z.string().optional(),
|
||||||
},
|
},
|
||||||
runtimeEnv,
|
runtimeEnv,
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@
|
||||||
"embla-carousel-auto-scroll": "^8.3.0",
|
"embla-carousel-auto-scroll": "^8.3.0",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.0",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"google-auth-library": "^10.1.0",
|
"google-auth-library": "^10.1.0",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
|
|
@ -146,7 +147,7 @@
|
||||||
"langfuse-vercel": "^3.38.4",
|
"langfuse-vercel": "^3.38.4",
|
||||||
"lucide-react": "^0.517.0",
|
"lucide-react": "^0.517.0",
|
||||||
"micromatch": "^4.0.8",
|
"micromatch": "^4.0.8",
|
||||||
"next": "15.5.0",
|
"next": "^15.5.7",
|
||||||
"next-auth": "^5.0.0-beta.30",
|
"next-auth": "^5.0.0-beta.30",
|
||||||
"next-navigation-guard": "^0.2.0",
|
"next-navigation-guard": "^0.2.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
|
|
@ -155,11 +156,12 @@
|
||||||
"openai": "^4.98.0",
|
"openai": "^4.98.0",
|
||||||
"parse-diff": "^0.11.1",
|
"parse-diff": "^0.11.1",
|
||||||
"posthog-js": "^1.161.5",
|
"posthog-js": "^1.161.5",
|
||||||
|
"posthog-node": "^5.15.0",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"psl": "^1.15.0",
|
"psl": "^1.15.0",
|
||||||
"react": "19.1.1",
|
"react": "^19.2.1",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "^19.2.1",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-hotkeys-hook": "^4.5.1",
|
"react-hotkeys-hook": "^4.5.1",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
|
|
@ -195,8 +197,8 @@
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/psl": "^1.1.3",
|
"@types/psl": "^1.1.3",
|
||||||
"@types/react": "19.1.10",
|
"@types/react": "19.2.1",
|
||||||
"@types/react-dom": "19.1.7",
|
"@types/react-dom": "19.2.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
||||||
"@typescript-eslint/parser": "^8.40.0",
|
"@typescript-eslint/parser": "^8.40.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
|
@ -216,7 +218,7 @@
|
||||||
"vitest-mock-extended": "^3.1.0"
|
"vitest-mock-extended": "^3.1.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "19.1.10",
|
"@types/react": "19.2.1",
|
||||||
"@types/react-dom": "19.1.7"
|
"@types/react-dom": "19.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ const auditService = getAuditService();
|
||||||
/**
|
/**
|
||||||
* "Service Error Wrapper".
|
* "Service Error Wrapper".
|
||||||
*
|
*
|
||||||
* Captures any thrown exceptions and converts them to a unexpected
|
* Captures any thrown exceptions, logs them to the console and Sentry,
|
||||||
* service error. Also logs them with Sentry.
|
* and returns a generic unexpected service error.
|
||||||
*/
|
*/
|
||||||
export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> => {
|
export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -52,10 +52,6 @@ export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> =>
|
||||||
return e.serviceError;
|
return e.serviceError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e instanceof Error) {
|
|
||||||
return unexpectedError(e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return unexpectedError(`An unexpected error occurred. Please try again later.`);
|
return unexpectedError(`An unexpected error occurred. Please try again later.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,12 @@ export const CodePreviewPanel = async ({ path, repoName, revisionName }: CodePre
|
||||||
getRepoInfoByName(repoName),
|
getRepoInfoByName(repoName),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isServiceError(fileSourceResponse) || isServiceError(repoInfoResponse)) {
|
if (isServiceError(fileSourceResponse)) {
|
||||||
return <div>Error loading file source</div>
|
return <div>Error loading file source: {fileSourceResponse.message}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isServiceError(repoInfoResponse)) {
|
||||||
|
return <div>Error loading repo info: {repoInfoResponse.message}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const codeHostInfo = getCodeHostInfoForRepo({
|
const codeHostInfo = getCodeHostInfoForRepo({
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
|
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
|
||||||
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
|
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
|
||||||
import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo";
|
|
||||||
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
|
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
|
||||||
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
|
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
|
||||||
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
|
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
|
||||||
|
|
@ -11,15 +10,10 @@ import { useKeymapExtension } from "@/hooks/useKeymapExtension";
|
||||||
import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam";
|
import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam";
|
||||||
import { search } from "@codemirror/search";
|
import { search } from "@codemirror/search";
|
||||||
import CodeMirror, { EditorSelection, EditorView, ReactCodeMirrorRef, SelectionRange, ViewUpdate } from "@uiw/react-codemirror";
|
import CodeMirror, { EditorSelection, EditorView, ReactCodeMirrorRef, SelectionRange, ViewUpdate } from "@uiw/react-codemirror";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { EditorContextMenu } from "../../../components/editorContextMenu";
|
import { EditorContextMenu } from "../../../components/editorContextMenu";
|
||||||
import { useBrowseNavigation } from "../../hooks/useBrowseNavigation";
|
|
||||||
import { BrowseHighlightRange, HIGHLIGHT_RANGE_QUERY_PARAM } from "../../hooks/utils";
|
import { BrowseHighlightRange, HIGHLIGHT_RANGE_QUERY_PARAM } from "../../hooks/utils";
|
||||||
import { useBrowseState } from "../../hooks/useBrowseState";
|
|
||||||
import { rangeHighlightingExtension } from "./rangeHighlightingExtension";
|
import { rangeHighlightingExtension } from "./rangeHighlightingExtension";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
|
||||||
import { createAuditAction } from "@/ee/features/audit/actions";
|
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
|
|
||||||
interface PureCodePreviewPanelProps {
|
interface PureCodePreviewPanelProps {
|
||||||
path: string;
|
path: string;
|
||||||
|
|
@ -41,10 +35,6 @@ export const PureCodePreviewPanel = ({
|
||||||
const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
|
const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
|
||||||
const keymapExtension = useKeymapExtension(editorRef?.view);
|
const keymapExtension = useKeymapExtension(editorRef?.view);
|
||||||
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
|
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
|
||||||
const { updateBrowseState } = useBrowseState();
|
|
||||||
const { navigateToPath } = useBrowseNavigation();
|
|
||||||
const domain = useDomain();
|
|
||||||
const captureEvent = useCaptureEvent();
|
|
||||||
|
|
||||||
const highlightRangeQuery = useNonEmptyQueryParam(HIGHLIGHT_RANGE_QUERY_PARAM);
|
const highlightRangeQuery = useNonEmptyQueryParam(HIGHLIGHT_RANGE_QUERY_PARAM);
|
||||||
const highlightRange = useMemo((): BrowseHighlightRange | undefined => {
|
const highlightRange = useMemo((): BrowseHighlightRange | undefined => {
|
||||||
|
|
@ -90,7 +80,6 @@ export const PureCodePreviewPanel = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [highlightRangeQuery]);
|
}, [highlightRangeQuery]);
|
||||||
|
|
||||||
const extensions = useMemo(() => {
|
const extensions = useMemo(() => {
|
||||||
|
|
@ -118,90 +107,31 @@ export const PureCodePreviewPanel = ({
|
||||||
|
|
||||||
// Scroll the highlighted range into view.
|
// Scroll the highlighted range into view.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!highlightRange || !editorRef || !editorRef.state) {
|
if (!highlightRange || !editorRef || !editorRef.state || !editorRef.view) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = editorRef.state.doc;
|
const doc = editorRef.state.doc;
|
||||||
const { start, end } = highlightRange;
|
const { start, end } = highlightRange;
|
||||||
const selection = EditorSelection.range(
|
|
||||||
doc.line(start.lineNumber).from,
|
|
||||||
doc.line(end.lineNumber).from,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const from = doc.line(start.lineNumber).from;
|
||||||
|
const to = doc.line(end.lineNumber).to;
|
||||||
|
const selection = EditorSelection.range(from, to);
|
||||||
|
|
||||||
|
// When the selection is in view, we don't want to perform any scrolling
|
||||||
|
// as it could be jarring for the user. If it is not in view, scroll to the
|
||||||
|
// center of the viewport.
|
||||||
|
const viewport = editorRef.view.viewport;
|
||||||
|
const isInView = from >= viewport.from && to <= viewport.to;
|
||||||
|
const scrollStrategy = isInView ? "nearest" : "center";
|
||||||
|
|
||||||
editorRef.view?.dispatch({
|
editorRef.view?.dispatch({
|
||||||
effects: [
|
effects: [
|
||||||
EditorView.scrollIntoView(selection, { y: "center" }),
|
EditorView.scrollIntoView(selection, { y: scrollStrategy }),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}, [editorRef, highlightRange]);
|
}, [editorRef, highlightRange]);
|
||||||
|
|
||||||
const onFindReferences = useCallback((symbolName: string) => {
|
|
||||||
captureEvent('wa_find_references_pressed', {
|
|
||||||
source: 'browse',
|
|
||||||
});
|
|
||||||
createAuditAction({
|
|
||||||
action: "user.performed_find_references",
|
|
||||||
metadata: {
|
|
||||||
message: symbolName,
|
|
||||||
},
|
|
||||||
}, domain)
|
|
||||||
|
|
||||||
updateBrowseState({
|
|
||||||
selectedSymbolInfo: {
|
|
||||||
repoName,
|
|
||||||
symbolName,
|
|
||||||
revisionName,
|
|
||||||
language,
|
|
||||||
},
|
|
||||||
isBottomPanelCollapsed: false,
|
|
||||||
activeExploreMenuTab: "references",
|
|
||||||
})
|
|
||||||
}, [captureEvent, updateBrowseState, repoName, revisionName, language, domain]);
|
|
||||||
|
|
||||||
|
|
||||||
// If we resolve multiple matches, instead of navigating to the first match, we should
|
|
||||||
// instead popup the bottom sheet with the list of matches.
|
|
||||||
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
|
|
||||||
captureEvent('wa_goto_definition_pressed', {
|
|
||||||
source: 'browse',
|
|
||||||
});
|
|
||||||
createAuditAction({
|
|
||||||
action: "user.performed_goto_definition",
|
|
||||||
metadata: {
|
|
||||||
message: symbolName,
|
|
||||||
},
|
|
||||||
}, domain)
|
|
||||||
|
|
||||||
if (symbolDefinitions.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (symbolDefinitions.length === 1) {
|
|
||||||
const symbolDefinition = symbolDefinitions[0];
|
|
||||||
const { fileName, repoName } = symbolDefinition;
|
|
||||||
|
|
||||||
navigateToPath({
|
|
||||||
repoName,
|
|
||||||
revisionName,
|
|
||||||
path: fileName,
|
|
||||||
pathType: 'blob',
|
|
||||||
highlightRange: symbolDefinition.range,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
updateBrowseState({
|
|
||||||
selectedSymbolInfo: {
|
|
||||||
symbolName,
|
|
||||||
repoName,
|
|
||||||
revisionName,
|
|
||||||
language,
|
|
||||||
},
|
|
||||||
activeExploreMenuTab: "definitions",
|
|
||||||
isBottomPanelCollapsed: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [captureEvent, navigateToPath, revisionName, updateBrowseState, repoName, language, domain]);
|
|
||||||
|
|
||||||
const theme = useCodeMirrorTheme();
|
const theme = useCodeMirrorTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -225,11 +155,12 @@ export const PureCodePreviewPanel = ({
|
||||||
)}
|
)}
|
||||||
{editorRef && hasCodeNavEntitlement && (
|
{editorRef && hasCodeNavEntitlement && (
|
||||||
<SymbolHoverPopup
|
<SymbolHoverPopup
|
||||||
|
source="preview"
|
||||||
editorRef={editorRef}
|
editorRef={editorRef}
|
||||||
revisionName={revisionName}
|
revisionName={revisionName}
|
||||||
language={language}
|
language={language}
|
||||||
onFindReferences={onFindReferences}
|
fileName={path}
|
||||||
onGotoDefinition={onGotoDefinition}
|
repoName={repoName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CodeMirror>
|
</CodeMirror>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { useBrowseParams } from "./hooks/useBrowseParams";
|
||||||
import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog";
|
import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
import { useDomain } from "@/hooks/useDomain";
|
||||||
import { SearchBar } from "../components/searchBar";
|
import { SearchBar } from "../components/searchBar";
|
||||||
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|
@ -30,7 +31,7 @@ export default function Layout({
|
||||||
<SearchBar
|
<SearchBar
|
||||||
size="sm"
|
size="sm"
|
||||||
defaults={{
|
defaults={{
|
||||||
query: `repo:${repoName}${revisionName ? ` rev:${revisionName}` : ''} `,
|
query: `repo:^${escapeStringRegexp(repoName)}$${revisionName ? ` rev:${revisionName}` : ''} `,
|
||||||
}}
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ export default async function Page(props: PageProps) {
|
||||||
const languageModels = await getConfiguredLanguageModelsInfo();
|
const languageModels = await getConfiguredLanguageModelsInfo();
|
||||||
const repos = await getRepos();
|
const repos = await getRepos();
|
||||||
const searchContexts = await getSearchContexts(params.domain);
|
const searchContexts = await getSearchContexts(params.domain);
|
||||||
const chatInfo = await getChatInfo({ chatId: params.id }, params.domain);
|
const chatInfo = await getChatInfo({ chatId: params.id });
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const chatHistory = session ? await getUserChatHistory(params.domain) : [];
|
const chatHistory = session ? await getUserChatHistory() : [];
|
||||||
|
|
||||||
if (isServiceError(chatHistory)) {
|
if (isServiceError(chatHistory)) {
|
||||||
throw new ServiceErrorException(chatHistory);
|
throw new ServiceErrorException(chatHistory);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { useToast } from "@/components/hooks/use-toast";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { updateChatName } from "@/features/chat/actions";
|
import { updateChatName } from "@/features/chat/actions";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import { isServiceError } from "@/lib/utils";
|
import { isServiceError } from "@/lib/utils";
|
||||||
import { GlobeIcon } from "@radix-ui/react-icons";
|
import { GlobeIcon } from "@radix-ui/react-icons";
|
||||||
import { ChatVisibility } from "@sourcebot/db";
|
import { ChatVisibility } from "@sourcebot/db";
|
||||||
|
|
@ -23,7 +22,6 @@ interface ChatNameProps {
|
||||||
export const ChatName = ({ name, visibility, id, isReadonly }: ChatNameProps) => {
|
export const ChatName = ({ name, visibility, id, isReadonly }: ChatNameProps) => {
|
||||||
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
|
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const domain = useDomain();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const onRenameChat = useCallback(async (name: string) => {
|
const onRenameChat = useCallback(async (name: string) => {
|
||||||
|
|
@ -31,7 +29,7 @@ export const ChatName = ({ name, visibility, id, isReadonly }: ChatNameProps) =>
|
||||||
const response = await updateChatName({
|
const response = await updateChatName({
|
||||||
chatId: id,
|
chatId: id,
|
||||||
name: name,
|
name: name,
|
||||||
}, domain);
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
toast({
|
toast({
|
||||||
|
|
@ -43,7 +41,7 @@ export const ChatName = ({ name, visibility, id, isReadonly }: ChatNameProps) =>
|
||||||
});
|
});
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}
|
}
|
||||||
}, [id, domain, toast, router]);
|
}, [id, toast, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { deleteChat, updateChatName } from "@/features/chat/actions";
|
import { deleteChat, updateChatName } from "@/features/chat/actions";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import { cn, isServiceError } from "@/lib/utils";
|
import { cn, isServiceError } from "@/lib/utils";
|
||||||
import { CirclePlusIcon, EllipsisIcon, PencilIcon, TrashIcon } from "lucide-react";
|
import { CirclePlusIcon, EllipsisIcon, PencilIcon, TrashIcon } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
@ -23,6 +22,7 @@ import { useChatId } from "../useChatId";
|
||||||
import { RenameChatDialog } from "./renameChatDialog";
|
import { RenameChatDialog } from "./renameChatDialog";
|
||||||
import { DeleteChatDialog } from "./deleteChatDialog";
|
import { DeleteChatDialog } from "./deleteChatDialog";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants";
|
||||||
|
|
||||||
interface ChatSidePanelProps {
|
interface ChatSidePanelProps {
|
||||||
order: number;
|
order: number;
|
||||||
|
|
@ -41,7 +41,6 @@ export const ChatSidePanel = ({
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isCollapsedInitially,
|
isCollapsedInitially,
|
||||||
}: ChatSidePanelProps) => {
|
}: ChatSidePanelProps) => {
|
||||||
const domain = useDomain();
|
|
||||||
const [isCollapsed, setIsCollapsed] = useState(isCollapsedInitially);
|
const [isCollapsed, setIsCollapsed] = useState(isCollapsedInitially);
|
||||||
const sidePanelRef = useRef<ImperativePanelHandle>(null);
|
const sidePanelRef = useRef<ImperativePanelHandle>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -72,7 +71,7 @@ export const ChatSidePanel = ({
|
||||||
const response = await updateChatName({
|
const response = await updateChatName({
|
||||||
chatId,
|
chatId,
|
||||||
name: name,
|
name: name,
|
||||||
}, domain);
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
toast({
|
toast({
|
||||||
|
|
@ -84,14 +83,14 @@ export const ChatSidePanel = ({
|
||||||
});
|
});
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}
|
}
|
||||||
}, [router, toast, domain]);
|
}, [router, toast]);
|
||||||
|
|
||||||
const onDeleteChat = useCallback(async (chatIdToDelete: string) => {
|
const onDeleteChat = useCallback(async (chatIdToDelete: string) => {
|
||||||
if (!chatIdToDelete) {
|
if (!chatIdToDelete) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await deleteChat({ chatId: chatIdToDelete }, domain);
|
const response = await deleteChat({ chatId: chatIdToDelete });
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
toast({
|
toast({
|
||||||
|
|
@ -104,12 +103,12 @@ export const ChatSidePanel = ({
|
||||||
|
|
||||||
// If we just deleted the current chat, navigate to new chat
|
// If we just deleted the current chat, navigate to new chat
|
||||||
if (chatIdToDelete === chatId) {
|
if (chatIdToDelete === chatId) {
|
||||||
router.push(`/${domain}/chat`);
|
router.push(`/${SINGLE_TENANT_ORG_DOMAIN}/chat`);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}
|
}
|
||||||
}, [chatId, router, toast, domain]);
|
}, [chatId, router, toast]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -131,7 +130,7 @@ export const ChatSidePanel = ({
|
||||||
size="sm"
|
size="sm"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(`/${domain}/chat`);
|
router.push(`/${SINGLE_TENANT_ORG_DOMAIN}/chat`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CirclePlusIcon className="w-4 h-4 mr-1" />
|
<CirclePlusIcon className="w-4 h-4 mr-1" />
|
||||||
|
|
@ -145,7 +144,7 @@ export const ChatSidePanel = ({
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
<Link
|
<Link
|
||||||
href={`/login?callbackUrl=${encodeURIComponent(`/${domain}/chat`)}`}
|
href={`/login?callbackUrl=${encodeURIComponent(`/${SINGLE_TENANT_ORG_DOMAIN}/chat`)}`}
|
||||||
className="text-sm text-link hover:underline cursor-pointer"
|
className="text-sm text-link hover:underline cursor-pointer"
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
|
|
@ -163,7 +162,7 @@ export const ChatSidePanel = ({
|
||||||
chat.id === chatId && "bg-muted"
|
chat.id === chatId && "bg-muted"
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(`/${domain}/chat/${chat.id}`);
|
router.push(`/${SINGLE_TENANT_ORG_DOMAIN}/chat/${chat.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="text-sm truncate">{chat.name ?? 'Untitled chat'}</span>
|
<span className="text-sm truncate">{chat.name ?? 'Untitled chat'}</span>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { NavigationItems } from "./navigationItems";
|
||||||
import { ProgressIndicator } from "./progressIndicator";
|
import { ProgressIndicator } from "./progressIndicator";
|
||||||
import { TrialIndicator } from "./trialIndicator";
|
import { TrialIndicator } from "./trialIndicator";
|
||||||
|
|
||||||
const SOURCEBOT_DISCORD_URL = "https://discord.gg/GbXMEM5H";
|
const SOURCEBOT_DISCORD_URL = "https://discord.gg/HDScTs3ptP";
|
||||||
const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot";
|
const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot";
|
||||||
|
|
||||||
interface NavigationMenuProps {
|
interface NavigationMenuProps {
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ export const SearchBar = ({
|
||||||
metadata: {
|
metadata: {
|
||||||
message: query,
|
message: query,
|
||||||
},
|
},
|
||||||
}, domain)
|
})
|
||||||
|
|
||||||
const url = createPathWithQueryParams(`/${domain}/search`,
|
const url = createPathWithQueryParams(`/${domain}/search`,
|
||||||
[SearchQueryParams.query, query],
|
[SearchQueryParams.query, query],
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ export const useSuggestionsData = ({
|
||||||
query: `file:${suggestionQuery}`,
|
query: `file:${suggestionQuery}`,
|
||||||
matches: 15,
|
matches: 15,
|
||||||
contextLines: 1,
|
contextLines: 1,
|
||||||
|
source: 'search-bar-file-suggestions'
|
||||||
}),
|
}),
|
||||||
select: (data): Suggestion[] => {
|
select: (data): Suggestion[] => {
|
||||||
if (isServiceError(data)) {
|
if (isServiceError(data)) {
|
||||||
|
|
@ -75,6 +76,7 @@ export const useSuggestionsData = ({
|
||||||
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
|
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
|
||||||
matches: 15,
|
matches: 15,
|
||||||
contextLines: 1,
|
contextLines: 1,
|
||||||
|
source: 'search-bar-symbol-suggestions'
|
||||||
}),
|
}),
|
||||||
select: (data): Suggestion[] => {
|
select: (data): Suggestion[] => {
|
||||||
if (isServiceError(data)) {
|
if (isServiceError(data)) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
||||||
import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu";
|
import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
|
||||||
|
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
|
||||||
|
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
|
||||||
import { SearchResultChunk } from "@/features/search";
|
import { SearchResultChunk } from "@/features/search";
|
||||||
|
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
|
||||||
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
|
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
|
||||||
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
|
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
|
||||||
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
|
|
||||||
import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension";
|
import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension";
|
||||||
import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension";
|
import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension";
|
||||||
import { search } from "@codemirror/search";
|
import { search } from "@codemirror/search";
|
||||||
|
|
@ -16,15 +20,6 @@ import { Scrollbar } from "@radix-ui/react-scroll-area";
|
||||||
import CodeMirror, { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror';
|
import CodeMirror, { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror';
|
||||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||||
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
|
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
|
||||||
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
|
|
||||||
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
|
|
||||||
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
|
|
||||||
import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo";
|
|
||||||
import { createAuditAction } from "@/ee/features/audit/actions";
|
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
|
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
|
||||||
|
|
||||||
export interface CodePreviewFile {
|
export interface CodePreviewFile {
|
||||||
content: string;
|
content: string;
|
||||||
|
|
@ -53,7 +48,6 @@ export const CodePreview = ({
|
||||||
const [editorRef, setEditorRef] = useState<ReactCodeMirrorRef | null>(null);
|
const [editorRef, setEditorRef] = useState<ReactCodeMirrorRef | null>(null);
|
||||||
const { navigateToPath } = useBrowseNavigation();
|
const { navigateToPath } = useBrowseNavigation();
|
||||||
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
|
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
|
||||||
const domain = useDomain();
|
|
||||||
|
|
||||||
const [gutterWidth, setGutterWidth] = useState(0);
|
const [gutterWidth, setGutterWidth] = useState(0);
|
||||||
const theme = useCodeMirrorTheme();
|
const theme = useCodeMirrorTheme();
|
||||||
|
|
@ -62,8 +56,6 @@ export const CodePreview = ({
|
||||||
const languageExtension = useCodeMirrorLanguageExtension(file?.language ?? '', editorRef?.view);
|
const languageExtension = useCodeMirrorLanguageExtension(file?.language ?? '', editorRef?.view);
|
||||||
const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
|
const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
|
||||||
|
|
||||||
const captureEvent = useCaptureEvent();
|
|
||||||
|
|
||||||
const extensions = useMemo(() => {
|
const extensions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
keymapExtension,
|
keymapExtension,
|
||||||
|
|
@ -118,81 +110,6 @@ export const CodePreview = ({
|
||||||
onSelectedMatchIndexChange((prev) => prev + 1);
|
onSelectedMatchIndexChange((prev) => prev + 1);
|
||||||
}, [onSelectedMatchIndexChange]);
|
}, [onSelectedMatchIndexChange]);
|
||||||
|
|
||||||
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
|
|
||||||
captureEvent('wa_goto_definition_pressed', {
|
|
||||||
source: 'preview',
|
|
||||||
});
|
|
||||||
createAuditAction({
|
|
||||||
action: "user.performed_goto_definition",
|
|
||||||
metadata: {
|
|
||||||
message: symbolName,
|
|
||||||
},
|
|
||||||
}, domain)
|
|
||||||
|
|
||||||
if (symbolDefinitions.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (symbolDefinitions.length === 1) {
|
|
||||||
const symbolDefinition = symbolDefinitions[0];
|
|
||||||
const { fileName, repoName } = symbolDefinition;
|
|
||||||
|
|
||||||
navigateToPath({
|
|
||||||
repoName,
|
|
||||||
revisionName: file.revision,
|
|
||||||
path: fileName,
|
|
||||||
pathType: 'blob',
|
|
||||||
highlightRange: symbolDefinition.range,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
navigateToPath({
|
|
||||||
repoName,
|
|
||||||
revisionName: file.revision,
|
|
||||||
path: file.filepath,
|
|
||||||
pathType: 'blob',
|
|
||||||
setBrowseState: {
|
|
||||||
selectedSymbolInfo: {
|
|
||||||
symbolName,
|
|
||||||
repoName,
|
|
||||||
revisionName: file.revision,
|
|
||||||
language: file.language,
|
|
||||||
},
|
|
||||||
activeExploreMenuTab: "definitions",
|
|
||||||
isBottomPanelCollapsed: false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName, domain]);
|
|
||||||
|
|
||||||
const onFindReferences = useCallback((symbolName: string) => {
|
|
||||||
captureEvent('wa_find_references_pressed', {
|
|
||||||
source: 'preview',
|
|
||||||
});
|
|
||||||
createAuditAction({
|
|
||||||
action: "user.performed_find_references",
|
|
||||||
metadata: {
|
|
||||||
message: symbolName,
|
|
||||||
},
|
|
||||||
}, domain)
|
|
||||||
|
|
||||||
navigateToPath({
|
|
||||||
repoName,
|
|
||||||
revisionName: file.revision,
|
|
||||||
path: file.filepath,
|
|
||||||
pathType: 'blob',
|
|
||||||
setBrowseState: {
|
|
||||||
selectedSymbolInfo: {
|
|
||||||
repoName,
|
|
||||||
symbolName,
|
|
||||||
revisionName: file.revision,
|
|
||||||
language: file.language,
|
|
||||||
},
|
|
||||||
activeExploreMenuTab: "references",
|
|
||||||
isBottomPanelCollapsed: false,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName, domain]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="flex flex-row bg-accent items-center justify-between pr-3 py-0.5 mt-7">
|
<div className="flex flex-row bg-accent items-center justify-between pr-3 py-0.5 mt-7">
|
||||||
|
|
@ -289,11 +206,12 @@ export const CodePreview = ({
|
||||||
|
|
||||||
{editorRef && hasCodeNavEntitlement && (
|
{editorRef && hasCodeNavEntitlement && (
|
||||||
<SymbolHoverPopup
|
<SymbolHoverPopup
|
||||||
|
source="preview"
|
||||||
editorRef={editorRef}
|
editorRef={editorRef}
|
||||||
language={file.language}
|
language={file.language}
|
||||||
revisionName={file.revision}
|
revisionName={file.revision}
|
||||||
onFindReferences={onFindReferences}
|
fileName={file.filepath}
|
||||||
onGotoDefinition={onGotoDefinition}
|
repoName={repoName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CodeMirror>
|
</CodeMirror>
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,8 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
|
||||||
whole,
|
whole,
|
||||||
isRegexEnabled,
|
isRegexEnabled,
|
||||||
isCaseSensitivityEnabled,
|
isCaseSensitivityEnabled,
|
||||||
}),
|
source: 'sourcebot-web-client'
|
||||||
|
} satisfies SearchRequest),
|
||||||
signal: abortControllerRef.current.signal,
|
signal: abortControllerRef.current.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { sew, withAuth, withOrgMembership } from "@/actions";
|
import { sew } from "@/actions";
|
||||||
import { _getConfiguredLanguageModelsFull, _getAISDKLanguageModelAndOptions, updateChatMessages } from "@/features/chat/actions";
|
import { _getConfiguredLanguageModelsFull, _getAISDKLanguageModelAndOptions, updateChatMessages } from "@/features/chat/actions";
|
||||||
import { createAgentStream } from "@/features/chat/agent";
|
import { createAgentStream } from "@/features/chat/agent";
|
||||||
import { additionalChatRequestParamsSchema, LanguageModelInfo, SBChatMessage, SearchScope } from "@/features/chat/types";
|
import { additionalChatRequestParamsSchema, LanguageModelInfo, SBChatMessage, SearchScope } from "@/features/chat/types";
|
||||||
|
|
@ -6,10 +6,10 @@ import { getAnswerPartFromAssistantMessage, getLanguageModelKey } from "@/featur
|
||||||
import { ErrorCode } from "@/lib/errorCodes";
|
import { ErrorCode } from "@/lib/errorCodes";
|
||||||
import { notFound, schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
import { notFound, schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
||||||
import { isServiceError } from "@/lib/utils";
|
import { isServiceError } from "@/lib/utils";
|
||||||
import { prisma } from "@/prisma";
|
import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
import { LanguageModelV2 as AISDKLanguageModelV2 } from "@ai-sdk/provider";
|
import { LanguageModelV2 as AISDKLanguageModelV2 } from "@ai-sdk/provider";
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
import { OrgRole } from "@sourcebot/db";
|
import { PrismaClient } from "@sourcebot/db";
|
||||||
import { createLogger } from "@sourcebot/shared";
|
import { createLogger } from "@sourcebot/shared";
|
||||||
import {
|
import {
|
||||||
createUIMessageStream,
|
createUIMessageStream,
|
||||||
|
|
@ -34,15 +34,6 @@ const chatRequestSchema = z.object({
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const domain = req.headers.get("X-Org-Domain");
|
|
||||||
if (!domain) {
|
|
||||||
return serviceErrorResponse({
|
|
||||||
statusCode: StatusCodes.BAD_REQUEST,
|
|
||||||
errorCode: ErrorCode.MISSING_ORG_DOMAIN_HEADER,
|
|
||||||
message: "Missing X-Org-Domain header",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestBody = await req.json();
|
const requestBody = await req.json();
|
||||||
const parsed = await chatRequestSchema.safeParseAsync(requestBody);
|
const parsed = await chatRequestSchema.safeParseAsync(requestBody);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
|
|
@ -56,56 +47,54 @@ export async function POST(req: Request) {
|
||||||
const languageModel = _languageModel as LanguageModelInfo;
|
const languageModel = _languageModel as LanguageModelInfo;
|
||||||
|
|
||||||
const response = await sew(() =>
|
const response = await sew(() =>
|
||||||
withAuth((userId) =>
|
withOptionalAuthV2(async ({ org, prisma }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
// Validate that the chat exists and is not readonly.
|
||||||
// Validate that the chat exists and is not readonly.
|
const chat = await prisma.chat.findUnique({
|
||||||
const chat = await prisma.chat.findUnique({
|
where: {
|
||||||
where: {
|
|
||||||
orgId: org.id,
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!chat) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chat.isReadonly) {
|
|
||||||
return serviceErrorResponse({
|
|
||||||
statusCode: StatusCodes.BAD_REQUEST,
|
|
||||||
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
|
||||||
message: "Chat is readonly and cannot be edited.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// From the language model ID, attempt to find the
|
|
||||||
// corresponding config in `config.json`.
|
|
||||||
const languageModelConfig =
|
|
||||||
(await _getConfiguredLanguageModelsFull())
|
|
||||||
.find((model) => getLanguageModelKey(model) === getLanguageModelKey(languageModel));
|
|
||||||
|
|
||||||
if (!languageModelConfig) {
|
|
||||||
return serviceErrorResponse({
|
|
||||||
statusCode: StatusCodes.BAD_REQUEST,
|
|
||||||
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
|
||||||
message: `Language model ${languageModel.model} is not configured.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { model, providerOptions } = await _getAISDKLanguageModelAndOptions(languageModelConfig);
|
|
||||||
|
|
||||||
return createMessageStreamResponse({
|
|
||||||
messages,
|
|
||||||
id,
|
|
||||||
selectedSearchScopes,
|
|
||||||
model,
|
|
||||||
modelName: languageModelConfig.displayName ?? languageModelConfig.model,
|
|
||||||
modelProviderOptions: providerOptions,
|
|
||||||
domain,
|
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chat.isReadonly) {
|
||||||
|
return serviceErrorResponse({
|
||||||
|
statusCode: StatusCodes.BAD_REQUEST,
|
||||||
|
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
||||||
|
message: "Chat is readonly and cannot be edited.",
|
||||||
});
|
});
|
||||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true
|
}
|
||||||
)
|
|
||||||
|
// From the language model ID, attempt to find the
|
||||||
|
// corresponding config in `config.json`.
|
||||||
|
const languageModelConfig =
|
||||||
|
(await _getConfiguredLanguageModelsFull())
|
||||||
|
.find((model) => getLanguageModelKey(model) === getLanguageModelKey(languageModel));
|
||||||
|
|
||||||
|
if (!languageModelConfig) {
|
||||||
|
return serviceErrorResponse({
|
||||||
|
statusCode: StatusCodes.BAD_REQUEST,
|
||||||
|
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
||||||
|
message: `Language model ${languageModel.model} is not configured.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { model, providerOptions } = await _getAISDKLanguageModelAndOptions(languageModelConfig);
|
||||||
|
|
||||||
|
return createMessageStreamResponse({
|
||||||
|
messages,
|
||||||
|
id,
|
||||||
|
selectedSearchScopes,
|
||||||
|
model,
|
||||||
|
modelName: languageModelConfig.displayName ?? languageModelConfig.model,
|
||||||
|
modelProviderOptions: providerOptions,
|
||||||
|
orgId: org.id,
|
||||||
|
prisma,
|
||||||
|
});
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
|
|
@ -132,8 +121,8 @@ interface CreateMessageStreamResponseProps {
|
||||||
model: AISDKLanguageModelV2;
|
model: AISDKLanguageModelV2;
|
||||||
modelName: string;
|
modelName: string;
|
||||||
modelProviderOptions?: Record<string, Record<string, JSONValue>>;
|
modelProviderOptions?: Record<string, Record<string, JSONValue>>;
|
||||||
domain: string;
|
|
||||||
orgId: number;
|
orgId: number;
|
||||||
|
prisma: PrismaClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createMessageStreamResponse = async ({
|
const createMessageStreamResponse = async ({
|
||||||
|
|
@ -143,8 +132,8 @@ const createMessageStreamResponse = async ({
|
||||||
model,
|
model,
|
||||||
modelName,
|
modelName,
|
||||||
modelProviderOptions,
|
modelProviderOptions,
|
||||||
domain,
|
|
||||||
orgId,
|
orgId,
|
||||||
|
prisma,
|
||||||
}: CreateMessageStreamResponseProps) => {
|
}: CreateMessageStreamResponseProps) => {
|
||||||
const latestMessage = messages[messages.length - 1];
|
const latestMessage = messages[messages.length - 1];
|
||||||
const sources = latestMessage.parts
|
const sources = latestMessage.parts
|
||||||
|
|
@ -254,7 +243,7 @@ const createMessageStreamResponse = async ({
|
||||||
await updateChatMessages({
|
await updateChatMessages({
|
||||||
chatId: id,
|
chatId: id,
|
||||||
messages
|
messages
|
||||||
}, domain);
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,25 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { NextRequest } from "next/server";
|
|
||||||
import { fetchAuditRecords } from "@/ee/features/audit/actions";
|
import { fetchAuditRecords } from "@/ee/features/audit/actions";
|
||||||
import { isServiceError } from "@/lib/utils";
|
|
||||||
import { serviceErrorResponse } from "@/lib/serviceError";
|
|
||||||
import { StatusCodes } from "http-status-codes";
|
|
||||||
import { ErrorCode } from "@/lib/errorCodes";
|
import { ErrorCode } from "@/lib/errorCodes";
|
||||||
import { env } from "@sourcebot/shared";
|
import { serviceErrorResponse } from "@/lib/serviceError";
|
||||||
|
import { isServiceError } from "@/lib/utils";
|
||||||
import { getEntitlements } from "@sourcebot/shared";
|
import { getEntitlements } from "@sourcebot/shared";
|
||||||
|
import { StatusCodes } from "http-status-codes";
|
||||||
|
|
||||||
export const GET = async (request: NextRequest) => {
|
export const GET = async () => {
|
||||||
const domain = request.headers.get("X-Org-Domain");
|
const entitlements = getEntitlements();
|
||||||
const apiKey = request.headers.get("X-Sourcebot-Api-Key") ?? undefined;
|
if (!entitlements.includes('audit')) {
|
||||||
|
return serviceErrorResponse({
|
||||||
|
statusCode: StatusCodes.FORBIDDEN,
|
||||||
|
errorCode: ErrorCode.NOT_FOUND,
|
||||||
|
message: "Audit logging is not enabled for your license",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!domain) {
|
const result = await fetchAuditRecords();
|
||||||
return serviceErrorResponse({
|
if (isServiceError(result)) {
|
||||||
statusCode: StatusCodes.BAD_REQUEST,
|
return serviceErrorResponse(result);
|
||||||
errorCode: ErrorCode.MISSING_ORG_DOMAIN_HEADER,
|
}
|
||||||
message: "Missing X-Org-Domain header",
|
return Response.json(result);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (env.SOURCEBOT_EE_AUDIT_LOGGING_ENABLED === 'false') {
|
|
||||||
return serviceErrorResponse({
|
|
||||||
statusCode: StatusCodes.NOT_FOUND,
|
|
||||||
errorCode: ErrorCode.NOT_FOUND,
|
|
||||||
message: "Audit logging is not enabled",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const entitlements = getEntitlements();
|
|
||||||
if (!entitlements.includes('audit')) {
|
|
||||||
return serviceErrorResponse({
|
|
||||||
statusCode: StatusCodes.FORBIDDEN,
|
|
||||||
errorCode: ErrorCode.NOT_FOUND,
|
|
||||||
message: "Audit logging is not enabled for your license",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await fetchAuditRecords(domain, apiKey);
|
|
||||||
if (isServiceError(result)) {
|
|
||||||
return serviceErrorResponse(result);
|
|
||||||
}
|
|
||||||
return Response.json(result);
|
|
||||||
};
|
};
|
||||||
|
|
@ -4,6 +4,7 @@ import { search, searchRequestSchema } from "@/features/search";
|
||||||
import { isServiceError } from "@/lib/utils";
|
import { isServiceError } from "@/lib/utils";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
||||||
|
import { captureEvent } from "@/lib/posthog";
|
||||||
|
|
||||||
export const POST = async (request: NextRequest) => {
|
export const POST = async (request: NextRequest) => {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
@ -16,8 +17,14 @@ export const POST = async (request: NextRequest) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
|
source,
|
||||||
...options
|
...options
|
||||||
} = parsed.data;
|
} = parsed.data;
|
||||||
|
|
||||||
|
await captureEvent('api_code_search_request', {
|
||||||
|
source: source ?? 'unknown',
|
||||||
|
type: 'blocking',
|
||||||
|
});
|
||||||
|
|
||||||
const response = await search({
|
const response = await search({
|
||||||
queryType: 'string',
|
queryType: 'string',
|
||||||
|
|
@ -28,5 +35,6 @@ export const POST = async (request: NextRequest) => {
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
return serviceErrorResponse(response);
|
return serviceErrorResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json(response);
|
return Response.json(response);
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { streamSearch, searchRequestSchema } from '@/features/search';
|
import { streamSearch, searchRequestSchema } from '@/features/search';
|
||||||
|
import { captureEvent } from '@/lib/posthog';
|
||||||
import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError';
|
import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError';
|
||||||
import { isServiceError } from '@/lib/utils';
|
import { isServiceError } from '@/lib/utils';
|
||||||
import { NextRequest } from 'next/server';
|
import { NextRequest } from 'next/server';
|
||||||
|
|
@ -15,9 +16,15 @@ export const POST = async (request: NextRequest) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
|
source,
|
||||||
...options
|
...options
|
||||||
} = parsed.data;
|
} = parsed.data;
|
||||||
|
|
||||||
|
await captureEvent('api_code_search_request', {
|
||||||
|
source: source ?? 'unknown',
|
||||||
|
type: 'streamed',
|
||||||
|
});
|
||||||
|
|
||||||
const stream = await streamSearch({
|
const stream = await streamSearch({
|
||||||
queryType: 'string',
|
queryType: 'string',
|
||||||
query,
|
query,
|
||||||
|
|
|
||||||
|
|
@ -6,28 +6,32 @@ import { WebhookEventDefinition} from "@octokit/webhooks/types";
|
||||||
import { EndpointDefaults } from "@octokit/types";
|
import { EndpointDefaults } from "@octokit/types";
|
||||||
import { env } from "@sourcebot/shared";
|
import { env } from "@sourcebot/shared";
|
||||||
import { processGitHubPullRequest } from "@/features/agents/review-agent/app";
|
import { processGitHubPullRequest } from "@/features/agents/review-agent/app";
|
||||||
import { throttling } from "@octokit/plugin-throttling";
|
import { throttling, type ThrottlingOptions } from "@octokit/plugin-throttling";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { GitHubPullRequest } from "@/features/agents/review-agent/types";
|
import { GitHubPullRequest } from "@/features/agents/review-agent/types";
|
||||||
import { createLogger } from "@sourcebot/shared";
|
import { createLogger } from "@sourcebot/shared";
|
||||||
|
|
||||||
const logger = createLogger('github-webhook');
|
const logger = createLogger('github-webhook');
|
||||||
|
|
||||||
let githubApp: App | undefined;
|
const DEFAULT_GITHUB_API_BASE_URL = "https://api.github.com";
|
||||||
|
type GitHubAppBaseOptions = Omit<ConstructorParameters<typeof App>[0], "Octokit"> & { throttle: ThrottlingOptions };
|
||||||
|
|
||||||
|
let githubAppBaseOptions: GitHubAppBaseOptions | undefined;
|
||||||
|
const githubAppCache = new Map<string, App>();
|
||||||
|
|
||||||
if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET && env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH) {
|
if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET && env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH) {
|
||||||
try {
|
try {
|
||||||
const privateKey = fs.readFileSync(env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH, "utf8");
|
const privateKey = fs.readFileSync(env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH, "utf8");
|
||||||
|
|
||||||
const throttledOctokit = Octokit.plugin(throttling);
|
githubAppBaseOptions = {
|
||||||
githubApp = new App({
|
|
||||||
appId: env.GITHUB_REVIEW_AGENT_APP_ID,
|
appId: env.GITHUB_REVIEW_AGENT_APP_ID,
|
||||||
privateKey: privateKey,
|
privateKey,
|
||||||
webhooks: {
|
webhooks: {
|
||||||
secret: env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET,
|
secret: env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET,
|
||||||
},
|
},
|
||||||
Octokit: throttledOctokit,
|
|
||||||
throttle: {
|
throttle: {
|
||||||
onRateLimit: (retryAfter: number, options: Required<EndpointDefaults>, octokit: Octokit, retryCount: number) => {
|
enabled: true,
|
||||||
|
onRateLimit: (retryAfter, _options, _octokit, retryCount) => {
|
||||||
if (retryCount > 3) {
|
if (retryCount > 3) {
|
||||||
logger.warn(`Rate limit exceeded: ${retryAfter} seconds`);
|
logger.warn(`Rate limit exceeded: ${retryAfter} seconds`);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -35,13 +39,55 @@ if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}
|
onSecondaryRateLimit: (_retryAfter, options) => {
|
||||||
});
|
// no retries on secondary rate limits
|
||||||
|
logger.warn(`SecondaryRateLimit detected for ${options.method} ${options.url}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error initializing GitHub app: ${error}`);
|
logger.error(`Error initializing GitHub app: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeGithubApiBaseUrl = (baseUrl?: string) => {
|
||||||
|
if (!baseUrl) {
|
||||||
|
return DEFAULT_GITHUB_API_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl.replace(/\/+$/, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveGithubApiBaseUrl = (headers: Record<string, string>) => {
|
||||||
|
const enterpriseHost = headers["x-github-enterprise-host"];
|
||||||
|
if (enterpriseHost) {
|
||||||
|
return normalizeGithubApiBaseUrl(`https://${enterpriseHost}/api/v3`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_GITHUB_API_BASE_URL;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGithubAppForBaseUrl = (baseUrl: string) => {
|
||||||
|
if (!githubAppBaseOptions) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedBaseUrl = normalizeGithubApiBaseUrl(baseUrl);
|
||||||
|
const cachedApp = githubAppCache.get(normalizedBaseUrl);
|
||||||
|
if (cachedApp) {
|
||||||
|
return cachedApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OctokitWithBaseUrl = Octokit.plugin(throttling).defaults({ baseUrl: normalizedBaseUrl });
|
||||||
|
const app = new App({
|
||||||
|
...githubAppBaseOptions,
|
||||||
|
Octokit: OctokitWithBaseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
githubAppCache.set(normalizedBaseUrl, app);
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
function isPullRequestEvent(eventHeader: string, payload: unknown): payload is WebhookEventDefinition<"pull-request-opened"> | WebhookEventDefinition<"pull-request-synchronize"> {
|
function isPullRequestEvent(eventHeader: string, payload: unknown): payload is WebhookEventDefinition<"pull-request-opened"> | WebhookEventDefinition<"pull-request-synchronize"> {
|
||||||
return eventHeader === "pull_request" && typeof payload === "object" && payload !== null && "action" in payload && typeof payload.action === "string" && (payload.action === "opened" || payload.action === "synchronize");
|
return eventHeader === "pull_request" && typeof payload === "object" && payload !== null && "action" in payload && typeof payload.action === "string" && (payload.action === "opened" || payload.action === "synchronize");
|
||||||
}
|
}
|
||||||
|
|
@ -52,12 +98,16 @@ function isIssueCommentEvent(eventHeader: string, payload: unknown): payload is
|
||||||
|
|
||||||
export const POST = async (request: NextRequest) => {
|
export const POST = async (request: NextRequest) => {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const headers = Object.fromEntries(request.headers.entries());
|
const headers = Object.fromEntries(Array.from(request.headers.entries(), ([key, value]) => [key.toLowerCase(), value]));
|
||||||
|
|
||||||
const githubEvent = headers['x-github-event'] || headers['X-GitHub-Event'];
|
const githubEvent = headers['x-github-event'];
|
||||||
if (githubEvent) {
|
if (githubEvent) {
|
||||||
logger.info('GitHub event received:', githubEvent);
|
logger.info('GitHub event received:', githubEvent);
|
||||||
|
|
||||||
|
const githubApiBaseUrl = resolveGithubApiBaseUrl(headers);
|
||||||
|
logger.debug('Using GitHub API base URL for event', { githubApiBaseUrl });
|
||||||
|
const githubApp = getGithubAppForBaseUrl(githubApiBaseUrl);
|
||||||
|
|
||||||
if (!githubApp) {
|
if (!githubApp) {
|
||||||
logger.warn('Received GitHub webhook event but GitHub app env vars are not set');
|
logger.warn('Received GitHub webhook event but GitHub app env vars are not set');
|
||||||
return Response.json({ status: 'ok' });
|
return Response.json({ status: 'ok' });
|
||||||
|
|
@ -113,4 +163,4 @@ export const POST = async (request: NextRequest) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ status: 'ok' });
|
return Response.json({ status: 'ok' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface KeyboardShortcutHintProps {
|
interface KeyboardShortcutHintProps {
|
||||||
shortcut: string
|
shortcut: string
|
||||||
label?: string
|
label?: string
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function KeyboardShortcutHint({ shortcut, label }: KeyboardShortcutHintProps) {
|
export function KeyboardShortcutHint({ shortcut, label, className }: KeyboardShortcutHintProps) {
|
||||||
return (
|
return (
|
||||||
<div className="inline-flex items-center" aria-label={label || `Keyboard shortcut: ${shortcut}`}>
|
<div className={cn("inline-flex items-center", className)} aria-label={label || `Keyboard shortcut: ${shortcut}`}>
|
||||||
<kbd
|
<kbd
|
||||||
className="px-2 py-1 font-semibold font-sans border rounded-md"
|
className="px-2 py-1 font-semibold font-sans border rounded-md"
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,12 @@ export default function RootLayout({
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<PlanProvider entitlements={getEntitlements()}>
|
<PlanProvider entitlements={getEntitlements()}>
|
||||||
<PostHogProvider disabled={env.SOURCEBOT_TELEMETRY_DISABLED === "true"}>
|
<PostHogProvider
|
||||||
|
isDisabled={env.SOURCEBOT_TELEMETRY_DISABLED === "true"}
|
||||||
|
// @note: the posthog api key doesn't need to be kept secret,
|
||||||
|
// so we are safe to send it to the client.
|
||||||
|
posthogApiKey={env.POSTHOG_PAPIK}
|
||||||
|
>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="system"
|
defaultTheme="system"
|
||||||
|
|
|
||||||
|
|
@ -32,16 +32,16 @@ function PostHogPageView() {
|
||||||
|
|
||||||
interface PostHogProviderProps {
|
interface PostHogProviderProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
disabled: boolean
|
isDisabled: boolean
|
||||||
|
posthogApiKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
|
export function PostHogProvider({ children, isDisabled, posthogApiKey }: PostHogProviderProps) {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
|
if (!isDisabled) {
|
||||||
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
|
posthog.init(posthogApiKey, {
|
||||||
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
|
|
||||||
// @see next.config.mjs for path rewrites to the "/ingest" route.
|
// @see next.config.mjs for path rewrites to the "/ingest" route.
|
||||||
api_host: "/ingest",
|
api_host: "/ingest",
|
||||||
person_profiles: 'identified_only',
|
person_profiles: 'identified_only',
|
||||||
|
|
@ -66,7 +66,7 @@ export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
|
||||||
} else {
|
} else {
|
||||||
console.debug("PostHog telemetry disabled");
|
console.debug("PostHog telemetry disabled");
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [isDisabled, posthogApiKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const toggleVariants = cva(
|
const toggleVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
|
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2 cursor-pointer",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
@ -16,9 +16,7 @@ const toggleVariants = cva(
|
||||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-3 min-w-10",
|
default: "h-7 w-7 min-w-7 p-0",
|
||||||
sm: "h-9 px-2.5 min-w-9",
|
|
||||||
lg: "h-11 px-5 min-w-11",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,64 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { prisma } from "@/prisma";
|
import { sew } from "@/actions";
|
||||||
import { ErrorCode } from "@/lib/errorCodes";
|
|
||||||
import { StatusCodes } from "http-status-codes";
|
|
||||||
import { sew, withAuth, withOrgMembership } from "@/actions";
|
|
||||||
import { OrgRole } from "@sourcebot/db";
|
|
||||||
import { createLogger } from "@sourcebot/shared";
|
|
||||||
import { ServiceError } from "@/lib/serviceError";
|
|
||||||
import { getAuditService } from "@/ee/features/audit/factory";
|
import { getAuditService } from "@/ee/features/audit/factory";
|
||||||
|
import { ErrorCode } from "@/lib/errorCodes";
|
||||||
|
import { ServiceError } from "@/lib/serviceError";
|
||||||
|
import { prisma } from "@/prisma";
|
||||||
|
import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2";
|
||||||
|
import { createLogger } from "@sourcebot/shared";
|
||||||
|
import { StatusCodes } from "http-status-codes";
|
||||||
import { AuditEvent } from "./types";
|
import { AuditEvent } from "./types";
|
||||||
|
import { OrgRole } from "@sourcebot/db";
|
||||||
|
|
||||||
const auditService = getAuditService();
|
const auditService = getAuditService();
|
||||||
const logger = createLogger('audit-utils');
|
const logger = createLogger('audit-utils');
|
||||||
|
|
||||||
export const createAuditAction = async (event: Omit<AuditEvent, 'sourcebotVersion' | 'orgId' | 'actor' | 'target'>, domain: string) => sew(async () =>
|
export const createAuditAction = async (event: Omit<AuditEvent, 'sourcebotVersion' | 'orgId' | 'actor' | 'target'>) => sew(async () =>
|
||||||
withAuth((userId) =>
|
withAuthV2(async ({ user, org }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
|
||||||
await auditService.createAudit({ ...event, orgId: org.id, actor: { id: userId, type: "user" }, target: { id: org.id.toString(), type: "org" } })
|
|
||||||
}, /* minRequiredRole = */ OrgRole.MEMBER), /* allowAnonymousAccess = */ true)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const fetchAuditRecords = async (domain: string, apiKey: string | undefined = undefined) => sew(() =>
|
|
||||||
withAuth((userId) =>
|
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
|
||||||
try {
|
|
||||||
const auditRecords = await prisma.audit.findMany({
|
|
||||||
where: {
|
|
||||||
orgId: org.id,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
timestamp: 'desc'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await auditService.createAudit({
|
await auditService.createAudit({
|
||||||
action: "audit.fetch",
|
...event,
|
||||||
actor: {
|
orgId: org.id,
|
||||||
id: userId,
|
actor: { id: user.id, type: "user" },
|
||||||
type: "user"
|
target: { id: org.id.toString(), type: "org" },
|
||||||
},
|
|
||||||
target: {
|
|
||||||
id: org.id.toString(),
|
|
||||||
type: "org"
|
|
||||||
},
|
|
||||||
orgId: org.id
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
return auditRecords;
|
);
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching audit logs', { error });
|
export const fetchAuditRecords = async () => sew(() =>
|
||||||
return {
|
withAuthV2(async ({ user, org, role }) =>
|
||||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
withMinimumOrgRole(role, OrgRole.OWNER, async () => {
|
||||||
errorCode: ErrorCode.UNEXPECTED_ERROR,
|
try {
|
||||||
message: "Failed to fetch audit logs",
|
const auditRecords = await prisma.audit.findMany({
|
||||||
} satisfies ServiceError;
|
where: {
|
||||||
}
|
orgId: org.id,
|
||||||
}, /* minRequiredRole = */ OrgRole.OWNER), /* allowAnonymousAccess = */ true, apiKey ? { apiKey, domain } : undefined)
|
},
|
||||||
|
orderBy: {
|
||||||
|
timestamp: 'desc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await auditService.createAudit({
|
||||||
|
action: "audit.fetch",
|
||||||
|
actor: {
|
||||||
|
id: user.id,
|
||||||
|
type: "user"
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
id: org.id.toString(),
|
||||||
|
type: "org"
|
||||||
|
},
|
||||||
|
orgId: org.id
|
||||||
|
})
|
||||||
|
|
||||||
|
return auditRecords;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error fetching audit logs', { error });
|
||||||
|
return {
|
||||||
|
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
errorCode: ErrorCode.UNEXPECTED_ERROR,
|
||||||
|
message: "Failed to fetch audit logs",
|
||||||
|
} satisfies ServiceError;
|
||||||
|
}
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
|
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
|
||||||
import { findSearchBasedSymbolReferences, findSearchBasedSymbolDefinitions} from "@/app/api/(client)/client";
|
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "@/app/api/(client)/client";
|
||||||
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
|
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||||
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
import { useDomain } from "@/hooks/useDomain";
|
||||||
import { unwrapServiceError } from "@/lib/utils";
|
import { measure, unwrapServiceError } from "@/lib/utils";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Loader2 } from "lucide-react";
|
import { GlobeIcon, Loader2 } from "lucide-react";
|
||||||
import { useMemo } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { VscSymbolMisc } from "react-icons/vsc";
|
import { VscSymbolMisc } from "react-icons/vsc";
|
||||||
import { ReferenceList } from "./referenceList";
|
import { ReferenceList } from "./referenceList";
|
||||||
|
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
|
|
||||||
interface ExploreMenuProps {
|
interface ExploreMenuProps {
|
||||||
selectedSymbolInfo: {
|
selectedSymbolInfo: {
|
||||||
|
|
@ -27,27 +31,39 @@ interface ExploreMenuProps {
|
||||||
export const ExploreMenu = ({
|
export const ExploreMenu = ({
|
||||||
selectedSymbolInfo,
|
selectedSymbolInfo,
|
||||||
}: ExploreMenuProps) => {
|
}: ExploreMenuProps) => {
|
||||||
|
|
||||||
const domain = useDomain();
|
const domain = useDomain();
|
||||||
|
const captureEvent = useCaptureEvent();
|
||||||
const {
|
const {
|
||||||
state: { activeExploreMenuTab },
|
state: { activeExploreMenuTab },
|
||||||
updateBrowseState,
|
updateBrowseState,
|
||||||
} = useBrowseState();
|
} = useBrowseState();
|
||||||
|
|
||||||
|
const [isGlobalSearchEnabled, setIsGlobalSearchEnabled] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: referencesResponse,
|
data: referencesResponse,
|
||||||
isError: isReferencesResponseError,
|
isError: isReferencesResponseError,
|
||||||
isPending: isReferencesResponsePending,
|
isPending: isReferencesResponsePending,
|
||||||
isLoading: isReferencesResponseLoading,
|
isLoading: isReferencesResponseLoading,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["references", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain],
|
queryKey: ["references", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain, isGlobalSearchEnabled],
|
||||||
queryFn: () => unwrapServiceError(
|
queryFn: async () => {
|
||||||
findSearchBasedSymbolReferences({
|
const response = await measure(() => unwrapServiceError(
|
||||||
symbolName: selectedSymbolInfo.symbolName,
|
findSearchBasedSymbolReferences({
|
||||||
language: selectedSymbolInfo.language,
|
symbolName: selectedSymbolInfo.symbolName,
|
||||||
revisionName: selectedSymbolInfo.revisionName,
|
language: selectedSymbolInfo.language,
|
||||||
|
revisionName: selectedSymbolInfo.revisionName,
|
||||||
|
repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName
|
||||||
|
})
|
||||||
|
), 'findSearchBasedSymbolReferences', false);
|
||||||
|
|
||||||
|
captureEvent('wa_explore_menu_references_loaded', {
|
||||||
|
durationMs: response.durationMs,
|
||||||
|
isGlobalSearchEnabled,
|
||||||
})
|
})
|
||||||
),
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -56,14 +72,32 @@ export const ExploreMenu = ({
|
||||||
isPending: isDefinitionsResponsePending,
|
isPending: isDefinitionsResponsePending,
|
||||||
isLoading: isDefinitionsResponseLoading,
|
isLoading: isDefinitionsResponseLoading,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["definitions", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain],
|
queryKey: ["definitions", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain, isGlobalSearchEnabled],
|
||||||
queryFn: () => unwrapServiceError(
|
queryFn: async () => {
|
||||||
findSearchBasedSymbolDefinitions({
|
const response = await measure(() => unwrapServiceError(
|
||||||
symbolName: selectedSymbolInfo.symbolName,
|
findSearchBasedSymbolDefinitions({
|
||||||
language: selectedSymbolInfo.language,
|
symbolName: selectedSymbolInfo.symbolName,
|
||||||
revisionName: selectedSymbolInfo.revisionName,
|
language: selectedSymbolInfo.language,
|
||||||
|
revisionName: selectedSymbolInfo.revisionName,
|
||||||
|
repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName
|
||||||
|
})
|
||||||
|
), 'findSearchBasedSymbolDefinitions', false);
|
||||||
|
|
||||||
|
captureEvent('wa_explore_menu_definitions_loaded', {
|
||||||
|
durationMs: response.durationMs,
|
||||||
|
isGlobalSearchEnabled,
|
||||||
})
|
})
|
||||||
),
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useHotkeys('shift+a', () => {
|
||||||
|
setIsGlobalSearchEnabled(prev => !prev);
|
||||||
|
}, {
|
||||||
|
enableOnFormTags: true,
|
||||||
|
enableOnContentEditable: true,
|
||||||
|
description: "Search all repositories",
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPending = isReferencesResponsePending || isDefinitionsResponsePending;
|
const isPending = isReferencesResponsePending || isDefinitionsResponsePending;
|
||||||
|
|
@ -98,29 +132,52 @@ export const ExploreMenu = ({
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
minSize={10}
|
minSize={10}
|
||||||
maxSize={20}
|
maxSize={20}
|
||||||
|
className="flex flex-col h-full"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col p-2">
|
<div className="flex flex-col p-2">
|
||||||
<Tooltip
|
<div className="flex flex-row items-center justify-between">
|
||||||
delayDuration={100}
|
|
||||||
>
|
<Tooltip
|
||||||
<TooltipTrigger
|
delayDuration={100}
|
||||||
disabled={true}
|
|
||||||
className="mr-auto"
|
|
||||||
>
|
>
|
||||||
<Badge
|
<TooltipTrigger
|
||||||
variant="outline"
|
disabled={true}
|
||||||
className="w-fit h-fit flex-shrink-0 select-none"
|
className="mr-auto"
|
||||||
>
|
>
|
||||||
Search Based
|
<Badge
|
||||||
</Badge>
|
variant="outline"
|
||||||
</TooltipTrigger>
|
className="w-fit h-fit flex-shrink-0 select-none"
|
||||||
<TooltipContent
|
>
|
||||||
side="top"
|
Search Based
|
||||||
align="start"
|
</Badge>
|
||||||
>
|
</TooltipTrigger>
|
||||||
Symbol references and definitions found using a best-guess search heuristic.
|
<TooltipContent
|
||||||
</TooltipContent>
|
side="top"
|
||||||
</Tooltip>
|
align="center"
|
||||||
|
>
|
||||||
|
Symbol references and definitions found using a best-guess search heuristic.
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>
|
||||||
|
<Toggle
|
||||||
|
pressed={isGlobalSearchEnabled}
|
||||||
|
onPressedChange={setIsGlobalSearchEnabled}
|
||||||
|
>
|
||||||
|
<GlobeIcon className="w-4 h-4" />
|
||||||
|
</Toggle>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" align="center">
|
||||||
|
Search all repositories
|
||||||
|
<KeyboardShortcutHint
|
||||||
|
shortcut="⇧ A"
|
||||||
|
className="ml-2"
|
||||||
|
/>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
<div className="flex flex-col gap-1 mt-4">
|
<div className="flex flex-col gap-1 mt-4">
|
||||||
<Entry
|
<Entry
|
||||||
name="References"
|
name="References"
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,50 @@
|
||||||
|
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
||||||
|
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
|
||||||
|
import { useToast } from "@/components/hooks/use-toast";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { LoadingButton } from "@/components/ui/loading-button";
|
import { LoadingButton } from "@/components/ui/loading-button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { createAuditAction } from "@/ee/features/audit/actions";
|
||||||
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
|
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
|
||||||
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { SymbolDefinition, useHoveredOverSymbolInfo } from "./useHoveredOverSymbolInfo";
|
|
||||||
import { SymbolDefinitionPreview } from "./symbolDefinitionPreview";
|
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import { useToast } from "@/components/hooks/use-toast";
|
import { SymbolDefinitionPreview } from "./symbolDefinitionPreview";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { useHoveredOverSymbolInfo } from "./useHoveredOverSymbolInfo";
|
||||||
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
|
|
||||||
|
|
||||||
interface SymbolHoverPopupProps {
|
interface SymbolHoverPopupProps {
|
||||||
editorRef: ReactCodeMirrorRef;
|
editorRef: ReactCodeMirrorRef;
|
||||||
language: string;
|
language: string;
|
||||||
revisionName: string;
|
revisionName: string;
|
||||||
onFindReferences: (symbolName: string) => void;
|
repoName: string;
|
||||||
onGotoDefinition: (symbolName: string, symbolDefinitions: SymbolDefinition[]) => void;
|
fileName: string;
|
||||||
|
source: 'browse' | 'preview' | 'chat';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
|
export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
|
||||||
editorRef,
|
editorRef,
|
||||||
revisionName,
|
revisionName,
|
||||||
language,
|
language,
|
||||||
onFindReferences,
|
repoName,
|
||||||
onGotoDefinition: _onGotoDefinition,
|
fileName,
|
||||||
|
source,
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [isSticky, setIsSticky] = useState(false);
|
const [isSticky, setIsSticky] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { navigateToPath } = useBrowseNavigation();
|
||||||
|
const captureEvent = useCaptureEvent();
|
||||||
|
|
||||||
const symbolInfo = useHoveredOverSymbolInfo({
|
const symbolInfo = useHoveredOverSymbolInfo({
|
||||||
editorRef,
|
editorRef,
|
||||||
isSticky,
|
isSticky,
|
||||||
revisionName,
|
revisionName,
|
||||||
language,
|
language,
|
||||||
|
repoName,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Positions the popup relative to the symbol
|
// Positions the popup relative to the symbol
|
||||||
|
|
@ -77,13 +85,118 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
|
||||||
}
|
}
|
||||||
}, [symbolInfo, editorRef]);
|
}, [symbolInfo, editorRef]);
|
||||||
|
|
||||||
|
// Multiple symbol definitions can exist for the same symbol, but we can only navigate
|
||||||
|
// and display a preview of one. If the symbol definition exists in the current file,
|
||||||
|
// then we use that one, otherwise we fallback to the first definition in the list.
|
||||||
|
const previewedSymbolDefinition = useMemo(() => {
|
||||||
|
if (!symbolInfo?.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingDefinition = symbolInfo.symbolDefinitions.find(
|
||||||
|
(definition) => (
|
||||||
|
definition.fileName === fileName && definition.repoName === repoName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingDefinition) {
|
||||||
|
return matchingDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return symbolInfo.symbolDefinitions[0];
|
||||||
|
}, [fileName, repoName, symbolInfo?.symbolDefinitions]);
|
||||||
|
|
||||||
const onGotoDefinition = useCallback(() => {
|
const onGotoDefinition = useCallback(() => {
|
||||||
if (!symbolInfo || !symbolInfo.symbolDefinitions) {
|
if (
|
||||||
|
!symbolInfo ||
|
||||||
|
!symbolInfo.symbolDefinitions ||
|
||||||
|
!previewedSymbolDefinition
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onGotoDefinition(symbolInfo.symbolName, symbolInfo.symbolDefinitions);
|
captureEvent('wa_goto_definition_pressed', {
|
||||||
}, [symbolInfo, _onGotoDefinition]);
|
source,
|
||||||
|
});
|
||||||
|
|
||||||
|
createAuditAction({
|
||||||
|
action: "user.performed_goto_definition",
|
||||||
|
metadata: {
|
||||||
|
message: symbolInfo.symbolName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
fileName,
|
||||||
|
repoName,
|
||||||
|
revisionName,
|
||||||
|
language,
|
||||||
|
range: highlightRange,
|
||||||
|
} = previewedSymbolDefinition;
|
||||||
|
|
||||||
|
navigateToPath({
|
||||||
|
// Always navigate to the preview symbol definition.
|
||||||
|
repoName,
|
||||||
|
revisionName,
|
||||||
|
path: fileName,
|
||||||
|
pathType: 'blob',
|
||||||
|
highlightRange,
|
||||||
|
// If there are multiple definitions, we should open the Explore panel with the definitions.
|
||||||
|
...(symbolInfo.symbolDefinitions.length > 1 ? {
|
||||||
|
setBrowseState: {
|
||||||
|
selectedSymbolInfo: {
|
||||||
|
symbolName: symbolInfo.symbolName,
|
||||||
|
repoName,
|
||||||
|
revisionName,
|
||||||
|
language,
|
||||||
|
},
|
||||||
|
activeExploreMenuTab: "definitions",
|
||||||
|
isBottomPanelCollapsed: false,
|
||||||
|
}
|
||||||
|
} : {}),
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
captureEvent,
|
||||||
|
previewedSymbolDefinition,
|
||||||
|
navigateToPath,
|
||||||
|
source,
|
||||||
|
symbolInfo
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onFindReferences = useCallback(() => {
|
||||||
|
if (!symbolInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
captureEvent('wa_find_references_pressed', {
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
|
||||||
|
createAuditAction({
|
||||||
|
action: "user.performed_find_references",
|
||||||
|
metadata: {
|
||||||
|
message: symbolInfo.symbolName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
navigateToPath({
|
||||||
|
repoName,
|
||||||
|
revisionName,
|
||||||
|
path: fileName,
|
||||||
|
pathType: 'blob',
|
||||||
|
highlightRange: symbolInfo.range,
|
||||||
|
setBrowseState: {
|
||||||
|
selectedSymbolInfo: {
|
||||||
|
symbolName: symbolInfo.symbolName,
|
||||||
|
repoName,
|
||||||
|
revisionName,
|
||||||
|
language,
|
||||||
|
},
|
||||||
|
activeExploreMenuTab: "references",
|
||||||
|
isBottomPanelCollapsed: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [captureEvent, fileName, language, navigateToPath, repoName, revisionName, source, symbolInfo]);
|
||||||
|
|
||||||
// @todo: We should probably make the behaviour s.t., the ctrl / cmd key needs to be held
|
// @todo: We should probably make the behaviour s.t., the ctrl / cmd key needs to be held
|
||||||
// down to navigate to the definition. We should also only show the underline when the key
|
// down to navigate to the definition. We should also only show the underline when the key
|
||||||
|
|
@ -100,9 +213,7 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
|
||||||
}, [symbolInfo, onGotoDefinition]);
|
}, [symbolInfo, onGotoDefinition]);
|
||||||
|
|
||||||
useHotkeys('alt+shift+f12', () => {
|
useHotkeys('alt+shift+f12', () => {
|
||||||
if (symbolInfo?.symbolName) {
|
onFindReferences();
|
||||||
onFindReferences(symbolInfo.symbolName);
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
|
|
@ -147,9 +258,9 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
) : symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 0 ? (
|
) : previewedSymbolDefinition ? (
|
||||||
<SymbolDefinitionPreview
|
<SymbolDefinitionPreview
|
||||||
symbolDefinition={symbolInfo.symbolDefinitions[0]}
|
symbolDefinition={previewedSymbolDefinition}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm font-medium text-muted-foreground">No hover info found</p>
|
<p className="text-sm font-medium text-muted-foreground">No hover info found</p>
|
||||||
|
|
@ -160,13 +271,13 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={symbolInfo.isSymbolDefinitionsLoading}
|
loading={symbolInfo.isSymbolDefinitionsLoading}
|
||||||
disabled={!symbolInfo.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0}
|
disabled={!previewedSymbolDefinition}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onGotoDefinition}
|
onClick={onGotoDefinition}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!symbolInfo.isSymbolDefinitionsLoading && (!symbolInfo.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0) ?
|
!symbolInfo.isSymbolDefinitionsLoading && !previewedSymbolDefinition ?
|
||||||
"No definition found" :
|
"No definition found" :
|
||||||
`Go to ${symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 1 ? "definitions" : "definition"}`
|
`Go to ${symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 1 ? "definitions" : "definition"}`
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +297,7 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onFindReferences(symbolInfo.symbolName)}
|
onClick={onFindReferences}
|
||||||
>
|
>
|
||||||
Find references
|
Find references
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { findSearchBasedSymbolDefinitions } from "@/app/api/(client)/client";
|
import { findSearchBasedSymbolDefinitions } from "@/app/api/(client)/client";
|
||||||
import { SourceRange } from "@/features/search";
|
import { SourceRange } from "@/features/search";
|
||||||
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
import { useDomain } from "@/hooks/useDomain";
|
||||||
import { unwrapServiceError } from "@/lib/utils";
|
import { measure, unwrapServiceError } from "@/lib/utils";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
@ -12,6 +13,7 @@ interface UseHoveredOverSymbolInfoProps {
|
||||||
isSticky: boolean;
|
isSticky: boolean;
|
||||||
revisionName: string;
|
revisionName: string;
|
||||||
language: string;
|
language: string;
|
||||||
|
repoName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SymbolDefinition = {
|
export type SymbolDefinition = {
|
||||||
|
|
@ -19,12 +21,14 @@ export type SymbolDefinition = {
|
||||||
language: string;
|
language: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
repoName: string;
|
repoName: string;
|
||||||
|
revisionName: string;
|
||||||
range: SourceRange;
|
range: SourceRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HoveredOverSymbolInfo {
|
interface HoveredOverSymbolInfo {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
symbolName: string;
|
symbolName: string;
|
||||||
|
range: SourceRange;
|
||||||
isSymbolDefinitionsLoading: boolean;
|
isSymbolDefinitionsLoading: boolean;
|
||||||
symbolDefinitions?: SymbolDefinition[];
|
symbolDefinitions?: SymbolDefinition[];
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +41,7 @@ export const useHoveredOverSymbolInfo = ({
|
||||||
isSticky,
|
isSticky,
|
||||||
revisionName,
|
revisionName,
|
||||||
language,
|
language,
|
||||||
|
repoName,
|
||||||
}: UseHoveredOverSymbolInfoProps): HoveredOverSymbolInfo | undefined => {
|
}: UseHoveredOverSymbolInfoProps): HoveredOverSymbolInfo | undefined => {
|
||||||
const mouseOverTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const mouseOverTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const mouseOutTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const mouseOutTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
@ -49,15 +54,26 @@ export const useHoveredOverSymbolInfo = ({
|
||||||
return (symbolElement && symbolElement.textContent) ?? undefined;
|
return (symbolElement && symbolElement.textContent) ?? undefined;
|
||||||
}, [symbolElement]);
|
}, [symbolElement]);
|
||||||
|
|
||||||
|
const captureEvent = useCaptureEvent();
|
||||||
|
|
||||||
const { data: symbolDefinitions, isLoading: isSymbolDefinitionsLoading } = useQuery({
|
const { data: symbolDefinitions, isLoading: isSymbolDefinitionsLoading } = useQuery({
|
||||||
queryKey: ["definitions", symbolName, revisionName, language, domain],
|
queryKey: ["definitions", symbolName, revisionName, language, domain, repoName],
|
||||||
queryFn: () => unwrapServiceError(
|
queryFn: async () => {
|
||||||
findSearchBasedSymbolDefinitions({
|
const response = await measure(() => unwrapServiceError(
|
||||||
symbolName: symbolName!,
|
findSearchBasedSymbolDefinitions({
|
||||||
language,
|
symbolName: symbolName!,
|
||||||
revisionName,
|
language,
|
||||||
})
|
revisionName,
|
||||||
),
|
repoName,
|
||||||
|
})
|
||||||
|
), 'findSearchBasedSymbolDefinitions', false);
|
||||||
|
|
||||||
|
captureEvent('wa_symbol_hover_popup_definitions_loaded', {
|
||||||
|
durationMs: response.durationMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
select: ((data) => {
|
select: ((data) => {
|
||||||
return data.files.flatMap((file) => {
|
return data.files.flatMap((file) => {
|
||||||
return file.matches.map((match) => {
|
return file.matches.map((match) => {
|
||||||
|
|
@ -66,6 +82,7 @@ export const useHoveredOverSymbolInfo = ({
|
||||||
language: file.language,
|
language: file.language,
|
||||||
fileName: file.fileName,
|
fileName: file.fileName,
|
||||||
repoName: file.repository,
|
repoName: file.repository,
|
||||||
|
revisionName: revisionName,
|
||||||
range: match.range,
|
range: match.range,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -107,7 +124,7 @@ export const useHoveredOverSymbolInfo = ({
|
||||||
|
|
||||||
const handleMouseOut = () => {
|
const handleMouseOut = () => {
|
||||||
clearTimers();
|
clearTimers();
|
||||||
|
|
||||||
mouseOutTimerRef.current = setTimeout(() => {
|
mouseOutTimerRef.current = setTimeout(() => {
|
||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
}, SYMBOL_HOVER_POPUP_MOUSE_OUT_TIMEOUT_MS);
|
}, SYMBOL_HOVER_POPUP_MOUSE_OUT_TIMEOUT_MS);
|
||||||
|
|
@ -122,17 +139,64 @@ export const useHoveredOverSymbolInfo = ({
|
||||||
};
|
};
|
||||||
}, [editorRef, domain, clearTimers]);
|
}, [editorRef, domain, clearTimers]);
|
||||||
|
|
||||||
|
// Extract the highlight range of the symbolElement from the editor view.
|
||||||
|
const highlightRange = useMemo((): SourceRange | undefined => {
|
||||||
|
if (!symbolElement || !editorRef.view) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = editorRef.view;
|
||||||
|
const rect = symbolElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Get the start position (left edge, middle vertically)
|
||||||
|
const startPos = view.posAtCoords({
|
||||||
|
x: rect.left,
|
||||||
|
y: rect.top + rect.height / 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the end position (right edge, middle vertically)
|
||||||
|
const endPos = view.posAtCoords({
|
||||||
|
x: rect.right,
|
||||||
|
y: rect.top + rect.height / 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (startPos === null || endPos === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert CodeMirror positions to SourceRange format
|
||||||
|
const startLine = view.state.doc.lineAt(startPos);
|
||||||
|
const endLine = view.state.doc.lineAt(endPos);
|
||||||
|
|
||||||
|
const startColumn = startPos - startLine.from + 1; // 1-based column
|
||||||
|
const endColumn = endPos - endLine.from + 1; // 1-based column
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: {
|
||||||
|
byteOffset: startPos, // 0-based byte offset
|
||||||
|
lineNumber: startLine.number, // 1-based line number
|
||||||
|
column: startColumn, // 1-based column
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
byteOffset: endPos, // 0-based byte offset
|
||||||
|
lineNumber: endLine.number, // 1-based line number
|
||||||
|
column: endColumn, // 1-based column
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [symbolElement, editorRef.view]);
|
||||||
|
|
||||||
if (!isVisible && !isSticky) {
|
if (!isVisible && !isSticky) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!symbolElement || !symbolName) {
|
if (!symbolElement || !symbolName || !highlightRange) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
element: symbolElement,
|
element: symbolElement,
|
||||||
symbolName,
|
symbolName,
|
||||||
|
range: highlightRange,
|
||||||
isSymbolDefinitionsLoading: isSymbolDefinitionsLoading,
|
isSymbolDefinitionsLoading: isSymbolDefinitionsLoading,
|
||||||
symbolDefinitions,
|
symbolDefinitions,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { sew, withAuth, withOrgMembership } from "@/actions";
|
import { sew } from "@/actions";
|
||||||
import { SOURCEBOT_GUEST_USER_ID } from "@/lib/constants";
|
|
||||||
import { ErrorCode } from "@/lib/errorCodes";
|
import { ErrorCode } from "@/lib/errorCodes";
|
||||||
import { chatIsReadonly, notFound, ServiceError, serviceErrorResponse } from "@/lib/serviceError";
|
import { chatIsReadonly, notFound, ServiceError, serviceErrorResponse } from "@/lib/serviceError";
|
||||||
import { prisma } from "@/prisma";
|
|
||||||
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
||||||
import { AnthropicProviderOptions, createAnthropic } from '@ai-sdk/anthropic';
|
import { AnthropicProviderOptions, createAnthropic } from '@ai-sdk/anthropic';
|
||||||
import { createAzure } from '@ai-sdk/azure';
|
import { createAzure } from '@ai-sdk/azure';
|
||||||
|
|
@ -20,7 +18,7 @@ import { createXai } from '@ai-sdk/xai';
|
||||||
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
|
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
|
||||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||||
import { getTokenFromConfig, createLogger, env } from "@sourcebot/shared";
|
import { getTokenFromConfig, createLogger, env } from "@sourcebot/shared";
|
||||||
import { ChatVisibility, OrgRole, Prisma } from "@sourcebot/db";
|
import { ChatVisibility, Prisma } from "@sourcebot/db";
|
||||||
import { LanguageModel } from "@sourcebot/schemas/v3/languageModel.type";
|
import { LanguageModel } from "@sourcebot/schemas/v3/languageModel.type";
|
||||||
import { Token } from "@sourcebot/schemas/v3/shared.type";
|
import { Token } from "@sourcebot/schemas/v3/shared.type";
|
||||||
import { generateText, JSONValue, extractReasoningMiddleware, wrapLanguageModel } from "ai";
|
import { generateText, JSONValue, extractReasoningMiddleware, wrapLanguageModel } from "ai";
|
||||||
|
|
@ -29,168 +27,161 @@ import fs from 'fs';
|
||||||
import { StatusCodes } from "http-status-codes";
|
import { StatusCodes } from "http-status-codes";
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { LanguageModelInfo, SBChatMessage } from "./types";
|
import { LanguageModelInfo, SBChatMessage } from "./types";
|
||||||
|
import { withAuthV2, withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
|
|
||||||
const logger = createLogger('chat-actions');
|
const logger = createLogger('chat-actions');
|
||||||
|
|
||||||
export const createChat = async (domain: string) => sew(() =>
|
export const createChat = async () => sew(() =>
|
||||||
withAuth((userId) =>
|
withOptionalAuthV2(async ({ org, user, prisma }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
const isGuestUser = user === undefined;
|
||||||
|
|
||||||
const isGuestUser = userId === SOURCEBOT_GUEST_USER_ID;
|
const chat = await prisma.chat.create({
|
||||||
|
data: {
|
||||||
|
orgId: org.id,
|
||||||
|
messages: [] as unknown as Prisma.InputJsonValue,
|
||||||
|
createdById: user?.id,
|
||||||
|
visibility: isGuestUser ? ChatVisibility.PUBLIC : ChatVisibility.PRIVATE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const chat = await prisma.chat.create({
|
return {
|
||||||
data: {
|
id: chat.id,
|
||||||
orgId: org.id,
|
}
|
||||||
messages: [] as unknown as Prisma.InputJsonValue,
|
})
|
||||||
createdById: userId,
|
|
||||||
visibility: isGuestUser ? ChatVisibility.PUBLIC : ChatVisibility.PRIVATE,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: chat.id,
|
|
||||||
}
|
|
||||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getChatInfo = async ({ chatId }: { chatId: string }, domain: string) => sew(() =>
|
export const getChatInfo = async ({ chatId }: { chatId: string }) => sew(() =>
|
||||||
withAuth((userId) =>
|
withOptionalAuthV2(async ({ org, user, prisma }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
const chat = await prisma.chat.findUnique({
|
||||||
const chat = await prisma.chat.findUnique({
|
where: {
|
||||||
where: {
|
id: chatId,
|
||||||
id: chatId,
|
orgId: org.id,
|
||||||
orgId: org.id,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== userId) {
|
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== user?.id) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages: chat.messages as unknown as SBChatMessage[],
|
messages: chat.messages as unknown as SBChatMessage[],
|
||||||
visibility: chat.visibility,
|
visibility: chat.visibility,
|
||||||
name: chat.name,
|
name: chat.name,
|
||||||
isReadonly: chat.isReadonly,
|
isReadonly: chat.isReadonly,
|
||||||
};
|
};
|
||||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true)
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const updateChatMessages = async ({ chatId, messages }: { chatId: string, messages: SBChatMessage[] }, domain: string) => sew(() =>
|
export const updateChatMessages = async ({ chatId, messages }: { chatId: string, messages: SBChatMessage[] }) => sew(() =>
|
||||||
withAuth((userId) =>
|
withOptionalAuthV2(async ({ org, user, prisma }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
const chat = await prisma.chat.findUnique({
|
||||||
const chat = await prisma.chat.findUnique({
|
where: {
|
||||||
where: {
|
id: chatId,
|
||||||
id: chatId,
|
orgId: org.id,
|
||||||
orgId: org.id,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return notFound();
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== user?.id) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chat.isReadonly) {
|
||||||
|
return chatIsReadonly();
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.chat.update({
|
||||||
|
where: {
|
||||||
|
id: chatId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
messages: messages as unknown as Prisma.InputJsonValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (env.DEBUG_WRITE_CHAT_MESSAGES_TO_FILE) {
|
||||||
|
const chatDir = path.join(env.DATA_CACHE_DIR, 'chats');
|
||||||
|
if (!fs.existsSync(chatDir)) {
|
||||||
|
fs.mkdirSync(chatDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== userId) {
|
const chatFile = path.join(chatDir, `${chatId}.json`);
|
||||||
return notFound();
|
fs.writeFileSync(chatFile, JSON.stringify(messages, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.isReadonly) {
|
return {
|
||||||
return chatIsReadonly();
|
success: true,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
await prisma.chat.update({
|
|
||||||
where: {
|
|
||||||
id: chatId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
messages: messages as unknown as Prisma.InputJsonValue,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (env.DEBUG_WRITE_CHAT_MESSAGES_TO_FILE) {
|
|
||||||
const chatDir = path.join(env.DATA_CACHE_DIR, 'chats');
|
|
||||||
if (!fs.existsSync(chatDir)) {
|
|
||||||
fs.mkdirSync(chatDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatFile = path.join(chatDir, `${chatId}.json`);
|
|
||||||
fs.writeFileSync(chatFile, JSON.stringify(messages, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
}
|
|
||||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getUserChatHistory = async (domain: string) => sew(() =>
|
export const getUserChatHistory = async () => sew(() =>
|
||||||
withAuth((userId) =>
|
withAuthV2(async ({ org, user, prisma }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
const chats = await prisma.chat.findMany({
|
||||||
const chats = await prisma.chat.findMany({
|
where: {
|
||||||
where: {
|
orgId: org.id,
|
||||||
orgId: org.id,
|
createdById: user.id,
|
||||||
createdById: userId,
|
},
|
||||||
},
|
orderBy: {
|
||||||
orderBy: {
|
updatedAt: 'desc',
|
||||||
updatedAt: 'desc',
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return chats.map((chat) => ({
|
return chats.map((chat) => ({
|
||||||
id: chat.id,
|
id: chat.id,
|
||||||
createdAt: chat.createdAt,
|
createdAt: chat.createdAt,
|
||||||
name: chat.name,
|
name: chat.name,
|
||||||
visibility: chat.visibility,
|
visibility: chat.visibility,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const updateChatName = async ({ chatId, name }: { chatId: string, name: string }, domain: string) => sew(() =>
|
export const updateChatName = async ({ chatId, name }: { chatId: string, name: string }) => sew(() =>
|
||||||
withAuth((userId) =>
|
withOptionalAuthV2(async ({ org, user, prisma }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
const chat = await prisma.chat.findUnique({
|
||||||
const chat = await prisma.chat.findUnique({
|
where: {
|
||||||
where: {
|
id: chatId,
|
||||||
id: chatId,
|
orgId: org.id,
|
||||||
orgId: org.id,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== userId) {
|
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== user?.id) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.isReadonly) {
|
if (chat.isReadonly) {
|
||||||
return chatIsReadonly();
|
return chatIsReadonly();
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.chat.update({
|
await prisma.chat.update({
|
||||||
where: {
|
where: {
|
||||||
id: chatId,
|
id: chatId,
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
}
|
}
|
||||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true)
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const generateAndUpdateChatNameFromMessage = async ({ chatId, languageModelId, message }: { chatId: string, languageModelId: string, message: string }, domain: string) => sew(() =>
|
export const generateAndUpdateChatNameFromMessage = async ({ chatId, languageModelId, message }: { chatId: string, languageModelId: string, message: string }) => sew(() =>
|
||||||
withAuth((userId) =>
|
withOptionalAuthV2(async () => {
|
||||||
withOrgMembership(userId, domain, async () => {
|
|
||||||
// From the language model ID, attempt to find the
|
// From the language model ID, attempt to find the
|
||||||
// corresponding config in `config.json`.
|
// corresponding config in `config.json`.
|
||||||
const languageModelConfig =
|
const languageModelConfig =
|
||||||
|
|
@ -231,48 +222,6 @@ User question: ${message}`;
|
||||||
await updateChatName({
|
await updateChatName({
|
||||||
chatId,
|
chatId,
|
||||||
name: result.text,
|
name: result.text,
|
||||||
}, domain);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
}
|
|
||||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const deleteChat = async ({ chatId }: { chatId: string }, domain: string) => sew(() =>
|
|
||||||
withAuth((userId) =>
|
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
|
||||||
const chat = await prisma.chat.findUnique({
|
|
||||||
where: {
|
|
||||||
id: chatId,
|
|
||||||
orgId: org.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!chat) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public chats cannot be deleted.
|
|
||||||
if (chat.visibility === ChatVisibility.PUBLIC) {
|
|
||||||
return {
|
|
||||||
statusCode: StatusCodes.FORBIDDEN,
|
|
||||||
errorCode: ErrorCode.UNEXPECTED_ERROR,
|
|
||||||
message: 'You are not allowed to delete this chat.',
|
|
||||||
} satisfies ServiceError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only the creator of a chat can delete it.
|
|
||||||
if (chat.createdById !== userId) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.chat.delete({
|
|
||||||
where: {
|
|
||||||
id: chatId,
|
|
||||||
orgId: org.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -280,6 +229,45 @@ export const deleteChat = async ({ chatId }: { chatId: string }, domain: string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const deleteChat = async ({ chatId }: { chatId: string }) => sew(() =>
|
||||||
|
withAuthV2(async ({ org, user, prisma }) => {
|
||||||
|
const chat = await prisma.chat.findUnique({
|
||||||
|
where: {
|
||||||
|
id: chatId,
|
||||||
|
orgId: org.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public chats cannot be deleted.
|
||||||
|
if (chat.visibility === ChatVisibility.PUBLIC) {
|
||||||
|
return {
|
||||||
|
statusCode: StatusCodes.FORBIDDEN,
|
||||||
|
errorCode: ErrorCode.UNEXPECTED_ERROR,
|
||||||
|
message: 'You are not allowed to delete this chat.',
|
||||||
|
} satisfies ServiceError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the creator of a chat can delete it.
|
||||||
|
if (chat.createdById !== user.id) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.chat.delete({
|
||||||
|
where: {
|
||||||
|
id: chatId,
|
||||||
|
orgId: org.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const submitFeedback = async ({
|
export const submitFeedback = async ({
|
||||||
|
|
@ -290,56 +278,55 @@ export const submitFeedback = async ({
|
||||||
chatId: string,
|
chatId: string,
|
||||||
messageId: string,
|
messageId: string,
|
||||||
feedbackType: 'like' | 'dislike'
|
feedbackType: 'like' | 'dislike'
|
||||||
}, domain: string) => sew(() =>
|
}) => sew(() =>
|
||||||
withAuth((userId) =>
|
withOptionalAuthV2(async ({ org, user, prisma }) => {
|
||||||
withOrgMembership(userId, domain, async ({ org }) => {
|
const chat = await prisma.chat.findUnique({
|
||||||
const chat = await prisma.chat.findUnique({
|
where: {
|
||||||
where: {
|
id: chatId,
|
||||||
id: chatId,
|
orgId: org.id,
|
||||||
orgId: org.id,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return notFound();
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a chat is private, only the creator can submit feedback.
|
||||||
|
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== user?.id) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = chat.messages as unknown as SBChatMessage[];
|
||||||
|
const updatedMessages = messages.map(message => {
|
||||||
|
if (message.id === messageId && message.role === 'assistant') {
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
metadata: {
|
||||||
|
...message.metadata,
|
||||||
|
feedback: [
|
||||||
|
...(message.metadata?.feedback ?? []),
|
||||||
|
{
|
||||||
|
type: feedbackType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
userId: user?.id,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} satisfies SBChatMessage;
|
||||||
}
|
}
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
|
||||||
// When a chat is private, only the creator can submit feedback.
|
await prisma.chat.update({
|
||||||
if (chat.visibility === ChatVisibility.PRIVATE && chat.createdById !== userId) {
|
where: { id: chatId },
|
||||||
return notFound();
|
data: {
|
||||||
}
|
messages: updatedMessages as unknown as Prisma.InputJsonValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const messages = chat.messages as unknown as SBChatMessage[];
|
return { success: true };
|
||||||
const updatedMessages = messages.map(message => {
|
})
|
||||||
if (message.id === messageId && message.role === 'assistant') {
|
)
|
||||||
return {
|
|
||||||
...message,
|
|
||||||
metadata: {
|
|
||||||
...message.metadata,
|
|
||||||
feedback: [
|
|
||||||
...(message.metadata?.feedback ?? []),
|
|
||||||
{
|
|
||||||
type: feedbackType,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
userId: userId,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
} satisfies SBChatMessage;
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.chat.update({
|
|
||||||
where: { id: chatId },
|
|
||||||
data: {
|
|
||||||
messages: updatedMessages as unknown as Prisma.InputJsonValue,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the subset of information about the configured language models
|
* Returns the subset of information about the configured language models
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { insertMention, slateContentToString } from "@/features/chat/utils";
|
||||||
import { cn, IS_MAC } from "@/lib/utils";
|
import { cn, IS_MAC } from "@/lib/utils";
|
||||||
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
|
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
|
||||||
import { ArrowUp, Loader2, StopCircleIcon, TriangleAlertIcon } from "lucide-react";
|
import { ArrowUp, Loader2, StopCircleIcon, TriangleAlertIcon } from "lucide-react";
|
||||||
import { Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { Fragment, KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import { Descendant, insertText } from "slate";
|
import { Descendant, insertText } from "slate";
|
||||||
import { Editable, ReactEditor, RenderElementProps, RenderLeafProps, useFocused, useSelected, useSlate } from "slate-react";
|
import { Editable, ReactEditor, RenderElementProps, RenderLeafProps, useFocused, useSelected, useSlate } from "slate-react";
|
||||||
|
|
@ -19,6 +19,7 @@ import { useSuggestionModeAndQuery } from "./useSuggestionModeAndQuery";
|
||||||
import { useSuggestionsData } from "./useSuggestionsData";
|
import { useSuggestionsData } from "./useSuggestionsData";
|
||||||
import { useToast } from "@/components/hooks/use-toast";
|
import { useToast } from "@/components/hooks/use-toast";
|
||||||
import { SearchContextQuery } from "@/lib/types";
|
import { SearchContextQuery } from "@/lib/types";
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
interface ChatBoxProps {
|
interface ChatBoxProps {
|
||||||
onSubmit: (children: Descendant[], editor: CustomEditor) => void;
|
onSubmit: (children: Descendant[], editor: CustomEditor) => void;
|
||||||
|
|
@ -34,7 +35,7 @@ interface ChatBoxProps {
|
||||||
onContextSelectorOpenChanged: (isOpen: boolean) => void;
|
onContextSelectorOpenChanged: (isOpen: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatBox = ({
|
const ChatBoxComponent = ({
|
||||||
onSubmit: _onSubmit,
|
onSubmit: _onSubmit,
|
||||||
onStop,
|
onStop,
|
||||||
preferredSuggestionsBoxPlacement = "bottom-start",
|
preferredSuggestionsBoxPlacement = "bottom-start",
|
||||||
|
|
@ -368,6 +369,8 @@ export const ChatBox = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ChatBox = memo(ChatBoxComponent, isEqual);
|
||||||
|
|
||||||
const DefaultElement = (props: RenderElementProps) => {
|
const DefaultElement = (props: RenderElementProps) => {
|
||||||
return <p {...props.attributes}>{props.children}</p>
|
return <p {...props.attributes}>{props.children}</p>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,22 @@ export const useSuggestionsData = ({
|
||||||
const { data: fileSuggestions, isLoading: _isLoadingFileSuggestions } = useQuery({
|
const { data: fileSuggestions, isLoading: _isLoadingFileSuggestions } = useQuery({
|
||||||
queryKey: ["fileSuggestions-agentic", suggestionQuery, domain, selectedRepos],
|
queryKey: ["fileSuggestions-agentic", suggestionQuery, domain, selectedRepos],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
let query = `file:${suggestionQuery}`;
|
const query = [];
|
||||||
|
if (suggestionQuery.length > 0) {
|
||||||
|
query.push(`file:${suggestionQuery}`);
|
||||||
|
} else {
|
||||||
|
query.push('file:.*');
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedRepos.length > 0) {
|
if (selectedRepos.length > 0) {
|
||||||
query += ` reposet:${selectedRepos.join(',')}`;
|
query.push(`reposet:${selectedRepos.join(',')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return unwrapServiceError(search({
|
return unwrapServiceError(search({
|
||||||
query,
|
query: query.join(' '),
|
||||||
matches: 10,
|
matches: 10,
|
||||||
contextLines: 1,
|
contextLines: 1,
|
||||||
|
source: 'chat-file-suggestions'
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
select: (data): FileSuggestion[] => {
|
select: (data): FileSuggestion[] => {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { TableOfContentsIcon, ThumbsDown, ThumbsUp } from "lucide-react";
|
import { TableOfContentsIcon, ThumbsDown, ThumbsUp } from "lucide-react";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { MarkdownRenderer } from "./markdownRenderer";
|
import { MarkdownRenderer } from "./markdownRenderer";
|
||||||
import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from "react";
|
import { forwardRef, memo, useCallback, useImperativeHandle, useRef, useState } from "react";
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { CopyIconButton } from "@/app/[domain]/components/copyIconButton";
|
import { CopyIconButton } from "@/app/[domain]/components/copyIconButton";
|
||||||
|
|
@ -14,10 +14,10 @@ import { useToast } from "@/components/hooks/use-toast";
|
||||||
import { convertLLMOutputToPortableMarkdown } from "../../utils";
|
import { convertLLMOutputToPortableMarkdown } from "../../utils";
|
||||||
import { submitFeedback } from "../../actions";
|
import { submitFeedback } from "../../actions";
|
||||||
import { isServiceError } from "@/lib/utils";
|
import { isServiceError } from "@/lib/utils";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
import { LangfuseWeb } from "langfuse";
|
import { LangfuseWeb } from "langfuse";
|
||||||
import { env } from "@sourcebot/shared/client";
|
import { env } from "@sourcebot/shared/client";
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
interface AnswerCardProps {
|
interface AnswerCardProps {
|
||||||
answerText: string;
|
answerText: string;
|
||||||
|
|
@ -31,7 +31,7 @@ const langfuseWeb = (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined &
|
||||||
baseUrl: env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
|
baseUrl: env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
|
||||||
}) : null;
|
}) : null;
|
||||||
|
|
||||||
export const AnswerCard = forwardRef<HTMLDivElement, AnswerCardProps>(({
|
const AnswerCardComponent = forwardRef<HTMLDivElement, AnswerCardProps>(({
|
||||||
answerText,
|
answerText,
|
||||||
messageId,
|
messageId,
|
||||||
chatId,
|
chatId,
|
||||||
|
|
@ -41,7 +41,6 @@ export const AnswerCard = forwardRef<HTMLDivElement, AnswerCardProps>(({
|
||||||
const { tocItems, activeId } = useExtractTOCItems({ target: markdownRendererRef.current });
|
const { tocItems, activeId } = useExtractTOCItems({ target: markdownRendererRef.current });
|
||||||
const [isTOCButtonToggled, setIsTOCButtonToggled] = useState(false);
|
const [isTOCButtonToggled, setIsTOCButtonToggled] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const domain = useDomain();
|
|
||||||
const [isSubmittingFeedback, setIsSubmittingFeedback] = useState(false);
|
const [isSubmittingFeedback, setIsSubmittingFeedback] = useState(false);
|
||||||
const [feedback, setFeedback] = useState<'like' | 'dislike' | undefined>(undefined);
|
const [feedback, setFeedback] = useState<'like' | 'dislike' | undefined>(undefined);
|
||||||
const captureEvent = useCaptureEvent();
|
const captureEvent = useCaptureEvent();
|
||||||
|
|
@ -67,7 +66,7 @@ export const AnswerCard = forwardRef<HTMLDivElement, AnswerCardProps>(({
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
feedbackType
|
feedbackType
|
||||||
}, domain);
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
toast({
|
toast({
|
||||||
|
|
@ -93,7 +92,7 @@ export const AnswerCard = forwardRef<HTMLDivElement, AnswerCardProps>(({
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSubmittingFeedback(false);
|
setIsSubmittingFeedback(false);
|
||||||
}, [chatId, messageId, domain, toast, captureEvent, traceId]);
|
}, [chatId, messageId, toast, captureEvent, traceId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row w-full relative scroll-mt-16">
|
<div className="flex flex-row w-full relative scroll-mt-16">
|
||||||
|
|
@ -178,4 +177,6 @@ export const AnswerCard = forwardRef<HTMLDivElement, AnswerCardProps>(({
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
AnswerCard.displayName = 'AnswerCard';
|
AnswerCardComponent.displayName = 'AnswerCard';
|
||||||
|
|
||||||
|
export const AnswerCard = memo(AnswerCardComponent, isEqual);
|
||||||
|
|
@ -7,7 +7,6 @@ import { Separator } from '@/components/ui/separator';
|
||||||
import { CustomSlateEditor } from '@/features/chat/customSlateEditor';
|
import { CustomSlateEditor } from '@/features/chat/customSlateEditor';
|
||||||
import { AdditionalChatRequestParams, CustomEditor, LanguageModelInfo, SBChatMessage, SearchScope, Source } from '@/features/chat/types';
|
import { AdditionalChatRequestParams, CustomEditor, LanguageModelInfo, SBChatMessage, SearchScope, Source } from '@/features/chat/types';
|
||||||
import { createUIMessage, getAllMentionElements, resetEditor, slateContentToString } from '@/features/chat/utils';
|
import { createUIMessage, getAllMentionElements, resetEditor, slateContentToString } from '@/features/chat/utils';
|
||||||
import { useDomain } from '@/hooks/useDomain';
|
|
||||||
import { useChat } from '@ai-sdk/react';
|
import { useChat } from '@ai-sdk/react';
|
||||||
import { CreateUIMessage, DefaultChatTransport } from 'ai';
|
import { CreateUIMessage, DefaultChatTransport } from 'ai';
|
||||||
import { ArrowDownIcon } from 'lucide-react';
|
import { ArrowDownIcon } from 'lucide-react';
|
||||||
|
|
@ -54,7 +53,6 @@ export const ChatThread = ({
|
||||||
onSelectedSearchScopesChange,
|
onSelectedSearchScopesChange,
|
||||||
isChatReadonly,
|
isChatReadonly,
|
||||||
}: ChatThreadProps) => {
|
}: ChatThreadProps) => {
|
||||||
const domain = useDomain();
|
|
||||||
const [isErrorBannerVisible, setIsErrorBannerVisible] = useState(false);
|
const [isErrorBannerVisible, setIsErrorBannerVisible] = useState(false);
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
const latestMessagePairRef = useRef<HTMLDivElement>(null);
|
const latestMessagePairRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -89,9 +87,6 @@ export const ChatThread = ({
|
||||||
messages: initialMessages,
|
messages: initialMessages,
|
||||||
transport: new DefaultChatTransport({
|
transport: new DefaultChatTransport({
|
||||||
api: '/api/chat',
|
api: '/api/chat',
|
||||||
headers: {
|
|
||||||
"X-Org-Domain": domain,
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
onData: (dataPart) => {
|
onData: (dataPart) => {
|
||||||
// Keeps sources added by the assistant in sync.
|
// Keeps sources added by the assistant in sync.
|
||||||
|
|
@ -134,7 +129,6 @@ export const ChatThread = ({
|
||||||
languageModelId: selectedLanguageModel.model,
|
languageModelId: selectedLanguageModel.model,
|
||||||
message: message.parts[0].text,
|
message: message.parts[0].text,
|
||||||
},
|
},
|
||||||
domain
|
|
||||||
).then((response) => {
|
).then((response) => {
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
toast({
|
toast({
|
||||||
|
|
@ -153,7 +147,6 @@ export const ChatThread = ({
|
||||||
messages.length,
|
messages.length,
|
||||||
toast,
|
toast,
|
||||||
chatId,
|
chatId,
|
||||||
domain,
|
|
||||||
router,
|
router,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -224,7 +217,7 @@ export const ChatThread = ({
|
||||||
'',
|
'',
|
||||||
window.location.href
|
window.location.href
|
||||||
);
|
);
|
||||||
}, 300);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollElement.addEventListener('scroll', handleScroll, { passive: true });
|
scrollElement.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
|
|
@ -243,11 +236,17 @@ export const ChatThread = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { scrollOffset } = (history.state ?? {}) as ChatHistoryState;
|
// @hack: without this setTimeout, the scroll position would not be restored
|
||||||
scrollElement.scrollTo({
|
// at the correct position (it was slightly too high). The theory is that the
|
||||||
top: scrollOffset ?? 0,
|
// content hasn't fully rendered yet, so restoring the scroll position too
|
||||||
behavior: 'instant',
|
// early results in weirdness. Waiting 10ms seems to fix the issue.
|
||||||
});
|
setTimeout(() => {
|
||||||
|
const { scrollOffset } = (history.state ?? {}) as ChatHistoryState;
|
||||||
|
scrollElement.scrollTo({
|
||||||
|
top: scrollOffset ?? 0,
|
||||||
|
behavior: 'instant',
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// When messages are being streamed, scroll to the latest message
|
// When messages are being streamed, scroll to the latest message
|
||||||
|
|
@ -313,9 +312,11 @@ export const ChatThread = ({
|
||||||
{messagePairs.map(([userMessage, assistantMessage], index) => {
|
{messagePairs.map(([userMessage, assistantMessage], index) => {
|
||||||
const isLastPair = index === messagePairs.length - 1;
|
const isLastPair = index === messagePairs.length - 1;
|
||||||
const isStreaming = isLastPair && (status === "streaming" || status === "submitted");
|
const isStreaming = isLastPair && (status === "streaming" || status === "submitted");
|
||||||
|
// Use a stable key based on user message ID
|
||||||
|
const key = userMessage.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={index}>
|
<Fragment key={key}>
|
||||||
<ChatThreadListItem
|
<ChatThreadListItem
|
||||||
index={index}
|
index={index}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,17 @@ import { AnimatedResizableHandle } from '@/components/ui/animatedResizableHandle
|
||||||
import { ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
|
import { ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { CheckCircle, Loader2 } from 'lucide-react';
|
import { CheckCircle, Loader2 } from 'lucide-react';
|
||||||
import { CSSProperties, forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { CSSProperties, forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||||
import { Reference, referenceSchema, SBChatMessage, Source } from "../../types";
|
import { Reference, referenceSchema, SBChatMessage, Source } from "../../types";
|
||||||
import { useExtractReferences } from '../../useExtractReferences';
|
import { useExtractReferences } from '../../useExtractReferences';
|
||||||
import { getAnswerPartFromAssistantMessage, groupMessageIntoSteps, repairReferences } from '../../utils';
|
import { getAnswerPartFromAssistantMessage, groupMessageIntoSteps, repairReferences, tryResolveFileReference } from '../../utils';
|
||||||
import { AnswerCard } from './answerCard';
|
import { AnswerCard } from './answerCard';
|
||||||
import { DetailsCard } from './detailsCard';
|
import { DetailsCard } from './detailsCard';
|
||||||
import { MarkdownRenderer, REFERENCE_PAYLOAD_ATTRIBUTE } from './markdownRenderer';
|
import { MarkdownRenderer, REFERENCE_PAYLOAD_ATTRIBUTE } from './markdownRenderer';
|
||||||
import { ReferencedSourcesListView } from './referencedSourcesListView';
|
import { ReferencedSourcesListView } from './referencedSourcesListView';
|
||||||
import { uiVisiblePartTypes } from '../../constants';
|
import { uiVisiblePartTypes } from '../../constants';
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
interface ChatThreadListItemProps {
|
interface ChatThreadListItemProps {
|
||||||
userMessage: SBChatMessage;
|
userMessage: SBChatMessage;
|
||||||
|
|
@ -24,7 +25,7 @@ interface ChatThreadListItemProps {
|
||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatThreadListItem = forwardRef<HTMLDivElement, ChatThreadListItemProps>(({
|
const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThreadListItemProps>(({
|
||||||
userMessage,
|
userMessage,
|
||||||
assistantMessage: _assistantMessage,
|
assistantMessage: _assistantMessage,
|
||||||
isStreaming,
|
isStreaming,
|
||||||
|
|
@ -80,7 +81,6 @@ export const ChatThreadListItem = forwardRef<HTMLDivElement, ChatThreadListItemP
|
||||||
return getAnswerPartFromAssistantMessage(assistantMessage, isStreaming);
|
return getAnswerPartFromAssistantMessage(assistantMessage, isStreaming);
|
||||||
}, [assistantMessage, isStreaming]);
|
}, [assistantMessage, isStreaming]);
|
||||||
|
|
||||||
const references = useExtractReferences(answerPart);
|
|
||||||
|
|
||||||
// Groups parts into steps that are associated with thinking steps that
|
// Groups parts into steps that are associated with thinking steps that
|
||||||
// should be visible to the user. By "steps", we mean parts that originated
|
// should be visible to the user. By "steps", we mean parts that originated
|
||||||
|
|
@ -279,6 +279,26 @@ export const ChatThreadListItem = forwardRef<HTMLDivElement, ChatThreadListItemP
|
||||||
};
|
};
|
||||||
}, [hoveredReference]);
|
}, [hoveredReference]);
|
||||||
|
|
||||||
|
const references = useExtractReferences(answerPart);
|
||||||
|
|
||||||
|
// Extract the file sources that are referenced by the answer part.
|
||||||
|
const referencedFileSources = useMemo(() => {
|
||||||
|
const fileSources = sources.filter((source) => source.type === 'file');
|
||||||
|
|
||||||
|
return references
|
||||||
|
.filter((reference) => reference.type === 'file')
|
||||||
|
.map((reference) => tryResolveFileReference(reference, fileSources))
|
||||||
|
.filter((file) => file !== undefined)
|
||||||
|
// de-duplicate files
|
||||||
|
.filter((file, index, self) =>
|
||||||
|
index === self.findIndex((t) =>
|
||||||
|
t?.path === file?.path
|
||||||
|
&& t?.repo === file?.repo
|
||||||
|
&& t?.revision === file?.revision
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [references, sources]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -364,11 +384,11 @@ export const ChatThreadListItem = forwardRef<HTMLDivElement, ChatThreadListItemP
|
||||||
<div
|
<div
|
||||||
className="sticky top-0"
|
className="sticky top-0"
|
||||||
>
|
>
|
||||||
{references.length > 0 ? (
|
{referencedFileSources.length > 0 ? (
|
||||||
<ReferencedSourcesListView
|
<ReferencedSourcesListView
|
||||||
index={index}
|
index={index}
|
||||||
references={references}
|
references={references}
|
||||||
sources={sources}
|
sources={referencedFileSources}
|
||||||
hoveredReference={hoveredReference}
|
hoveredReference={hoveredReference}
|
||||||
selectedReference={selectedReference}
|
selectedReference={selectedReference}
|
||||||
onSelectedReferenceChanged={setSelectedReference}
|
onSelectedReferenceChanged={setSelectedReference}
|
||||||
|
|
@ -393,7 +413,34 @@ export const ChatThreadListItem = forwardRef<HTMLDivElement, ChatThreadListItemP
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
ChatThreadListItem.displayName = 'ChatThreadListItem';
|
ChatThreadListItemComponent.displayName = 'ChatThreadListItem';
|
||||||
|
|
||||||
|
// Custom comparison function that handles the known issue where useChat mutates
|
||||||
|
// message objects in place during streaming, causing fast-deep-equal to return
|
||||||
|
// true even when content changes (because it checks reference equality first).
|
||||||
|
// See: https://github.com/vercel/ai/issues/6466
|
||||||
|
const arePropsEqual = (
|
||||||
|
prevProps: ChatThreadListItemProps,
|
||||||
|
nextProps: ChatThreadListItemProps
|
||||||
|
): boolean => {
|
||||||
|
// Always re-render if streaming status changes
|
||||||
|
if (prevProps.isStreaming !== nextProps.isStreaming) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If currently streaming, always allow re-render
|
||||||
|
// This bypasses the fast-deep-equal reference check issue when useChat
|
||||||
|
// mutates message objects in place during token streaming
|
||||||
|
if (nextProps.isStreaming) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-streaming messages, use deep equality
|
||||||
|
// At this point, useChat should have finished and created final objects
|
||||||
|
return isEqual(prevProps, nextProps);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChatThreadListItem = memo(ChatThreadListItemComponent, arePropsEqual);
|
||||||
|
|
||||||
// Finds the nearest reference element to the viewport center.
|
// Finds the nearest reference element to the viewport center.
|
||||||
const getNearestReferenceElement = (referenceElements: Element[]) => {
|
const getNearestReferenceElement = (referenceElements: Element[]) => {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Brain, ChevronDown, ChevronRight, Clock, InfoIcon, Loader2, List, ScanSearchIcon, Zap } from 'lucide-react';
|
import { Brain, ChevronDown, ChevronRight, Clock, InfoIcon, Loader2, List, ScanSearchIcon, Zap } from 'lucide-react';
|
||||||
|
import { memo } from 'react';
|
||||||
import { MarkdownRenderer } from './markdownRenderer';
|
import { MarkdownRenderer } from './markdownRenderer';
|
||||||
import { FindSymbolDefinitionsToolComponent } from './tools/findSymbolDefinitionsToolComponent';
|
import { FindSymbolDefinitionsToolComponent } from './tools/findSymbolDefinitionsToolComponent';
|
||||||
import { FindSymbolReferencesToolComponent } from './tools/findSymbolReferencesToolComponent';
|
import { FindSymbolReferencesToolComponent } from './tools/findSymbolReferencesToolComponent';
|
||||||
|
|
@ -16,6 +17,7 @@ import { SearchReposToolComponent } from './tools/searchReposToolComponent';
|
||||||
import { ListAllReposToolComponent } from './tools/listAllReposToolComponent';
|
import { ListAllReposToolComponent } from './tools/listAllReposToolComponent';
|
||||||
import { SBChatMessageMetadata, SBChatMessagePart } from '../../types';
|
import { SBChatMessageMetadata, SBChatMessagePart } from '../../types';
|
||||||
import { SearchScopeIcon } from '../searchScopeIcon';
|
import { SearchScopeIcon } from '../searchScopeIcon';
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
|
|
||||||
interface DetailsCardProps {
|
interface DetailsCardProps {
|
||||||
|
|
@ -27,7 +29,7 @@ interface DetailsCardProps {
|
||||||
metadata?: SBChatMessageMetadata;
|
metadata?: SBChatMessageMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DetailsCard = ({
|
const DetailsCardComponent = ({
|
||||||
isExpanded,
|
isExpanded,
|
||||||
onExpandedChanged,
|
onExpandedChanged,
|
||||||
isThinking,
|
isThinking,
|
||||||
|
|
@ -35,7 +37,6 @@ export const DetailsCard = ({
|
||||||
metadata,
|
metadata,
|
||||||
thinkingSteps,
|
thinkingSteps,
|
||||||
}: DetailsCardProps) => {
|
}: DetailsCardProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="mb-4">
|
<Card className="mb-4">
|
||||||
<Collapsible open={isExpanded} onOpenChange={onExpandedChanged}>
|
<Collapsible open={isExpanded} onOpenChange={onExpandedChanged}>
|
||||||
|
|
@ -209,4 +210,6 @@ export const DetailsCard = ({
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DetailsCard = memo(DetailsCardComponent, isEqual);
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { CodeSnippet } from '@/app/components/codeSnippet';
|
import { CodeSnippet } from '@/app/components/codeSnippet';
|
||||||
import { useDomain } from '@/hooks/useDomain';
|
|
||||||
import { SearchQueryParams } from '@/lib/types';
|
import { SearchQueryParams } from '@/lib/types';
|
||||||
import { cn, createPathWithQueryParams } from '@/lib/utils';
|
import { cn, createPathWithQueryParams } from '@/lib/utils';
|
||||||
import type { Element, Root } from "hast";
|
import type { Element, Root } from "hast";
|
||||||
|
|
@ -10,7 +9,7 @@ import { CopyIcon, SearchIcon } from 'lucide-react';
|
||||||
import type { Heading, Nodes } from "mdast";
|
import type { Heading, Nodes } from "mdast";
|
||||||
import { findAndReplace } from 'mdast-util-find-and-replace';
|
import { findAndReplace } from 'mdast-util-find-and-replace';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React, { useCallback, useMemo, forwardRef } from 'react';
|
import React, { useCallback, useMemo, forwardRef, memo } from 'react';
|
||||||
import Markdown from 'react-markdown';
|
import Markdown from 'react-markdown';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
||||||
|
|
@ -20,6 +19,8 @@ import { visit } from 'unist-util-visit';
|
||||||
import { CodeBlock } from './codeBlock';
|
import { CodeBlock } from './codeBlock';
|
||||||
import { FILE_REFERENCE_REGEX } from '@/features/chat/constants';
|
import { FILE_REFERENCE_REGEX } from '@/features/chat/constants';
|
||||||
import { createFileReference } from '@/features/chat/utils';
|
import { createFileReference } from '@/features/chat/utils';
|
||||||
|
import { SINGLE_TENANT_ORG_DOMAIN } from '@/lib/constants';
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
export const REFERENCE_PAYLOAD_ATTRIBUTE = 'data-reference-payload';
|
export const REFERENCE_PAYLOAD_ATTRIBUTE = 'data-reference-payload';
|
||||||
|
|
||||||
|
|
@ -102,8 +103,7 @@ interface MarkdownRendererProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MarkdownRenderer = forwardRef<HTMLDivElement, MarkdownRendererProps>(({ content, className }, ref) => {
|
const MarkdownRendererComponent = forwardRef<HTMLDivElement, MarkdownRendererProps>(({ content, className }, ref) => {
|
||||||
const domain = useDomain();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const remarkPlugins = useMemo((): PluggableList => {
|
const remarkPlugins = useMemo((): PluggableList => {
|
||||||
|
|
@ -176,7 +176,7 @@ export const MarkdownRenderer = forwardRef<HTMLDivElement, MarkdownRendererProps
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const url = createPathWithQueryParams(`/${domain}/search`, [SearchQueryParams.query, `"${text}"`])
|
const url = createPathWithQueryParams(`/${SINGLE_TENANT_ORG_DOMAIN}/search`, [SearchQueryParams.query, `"${text}"`])
|
||||||
router.push(url);
|
router.push(url);
|
||||||
}}
|
}}
|
||||||
title="Search for snippet"
|
title="Search for snippet"
|
||||||
|
|
@ -199,7 +199,7 @@ export const MarkdownRenderer = forwardRef<HTMLDivElement, MarkdownRendererProps
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
}, [domain, router]);
|
}, [router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -220,4 +220,6 @@ export const MarkdownRenderer = forwardRef<HTMLDivElement, MarkdownRendererProps
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
MarkdownRenderer.displayName = 'MarkdownRenderer';
|
MarkdownRendererComponent.displayName = 'MarkdownRenderer';
|
||||||
|
|
||||||
|
export const MarkdownRenderer = memo(MarkdownRendererComponent, isEqual);
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
|
||||||
import { PathHeader } from "@/app/[domain]/components/pathHeader";
|
import { PathHeader } from "@/app/[domain]/components/pathHeader";
|
||||||
import { SymbolHoverPopup } from '@/ee/features/codeNav/components/symbolHoverPopup';
|
import { SymbolHoverPopup } from '@/ee/features/codeNav/components/symbolHoverPopup';
|
||||||
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
|
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
|
||||||
import { SymbolDefinition } from '@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo';
|
|
||||||
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
|
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
|
||||||
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
|
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
|
||||||
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
|
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
|
||||||
|
|
@ -12,15 +10,13 @@ import { useKeymapExtension } from "@/hooks/useKeymapExtension";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Range } from "@codemirror/state";
|
import { Range } from "@codemirror/state";
|
||||||
import { Decoration, DecorationSet, EditorView } from '@codemirror/view';
|
import { Decoration, DecorationSet, EditorView } from '@codemirror/view';
|
||||||
|
import { CodeHostType } from "@sourcebot/db";
|
||||||
import CodeMirror, { ReactCodeMirrorRef, StateField } from '@uiw/react-codemirror';
|
import CodeMirror, { ReactCodeMirrorRef, StateField } from '@uiw/react-codemirror';
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
import { ChevronDown, ChevronRight } from "lucide-react";
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||||
import { forwardRef, Ref, useCallback, useImperativeHandle, useMemo, useState } from "react";
|
import { forwardRef, memo, Ref, useCallback, useImperativeHandle, useMemo, useState } from "react";
|
||||||
import { FileReference } from "../../types";
|
import { FileReference } from "../../types";
|
||||||
import { createCodeFoldingExtension } from "./codeFoldingExtension";
|
import { createCodeFoldingExtension } from "./codeFoldingExtension";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
|
||||||
import { createAuditAction } from "@/ee/features/audit/actions";
|
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import { CodeHostType } from "@sourcebot/db";
|
|
||||||
|
|
||||||
const lineDecoration = Decoration.line({
|
const lineDecoration = Decoration.line({
|
||||||
attributes: { class: "cm-range-border-radius chat-lineHighlight" },
|
attributes: { class: "cm-range-border-radius chat-lineHighlight" },
|
||||||
|
|
@ -74,8 +70,6 @@ const ReferencedFileSourceListItem = ({
|
||||||
}: ReferencedFileSourceListItemProps, forwardedRef: Ref<ReactCodeMirrorRef>) => {
|
}: ReferencedFileSourceListItemProps, forwardedRef: Ref<ReactCodeMirrorRef>) => {
|
||||||
const theme = useCodeMirrorTheme();
|
const theme = useCodeMirrorTheme();
|
||||||
const [editorRef, setEditorRef] = useState<ReactCodeMirrorRef | null>(null);
|
const [editorRef, setEditorRef] = useState<ReactCodeMirrorRef | null>(null);
|
||||||
const captureEvent = useCaptureEvent();
|
|
||||||
const domain = useDomain();
|
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
|
|
@ -85,7 +79,6 @@ const ReferencedFileSourceListItem = ({
|
||||||
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
|
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
|
||||||
|
|
||||||
const languageExtension = useCodeMirrorLanguageExtension(language, editorRef?.view);
|
const languageExtension = useCodeMirrorLanguageExtension(language, editorRef?.view);
|
||||||
const { navigateToPath } = useBrowseNavigation();
|
|
||||||
|
|
||||||
const getReferenceAtPos = useCallback((x: number, y: number, view: EditorView): FileReference | undefined => {
|
const getReferenceAtPos = useCallback((x: number, y: number, view: EditorView): FileReference | undefined => {
|
||||||
const pos = view.posAtCoords({ x, y });
|
const pos = view.posAtCoords({ x, y });
|
||||||
|
|
@ -218,83 +211,6 @@ const ReferencedFileSourceListItem = ({
|
||||||
codeFoldingExtension,
|
codeFoldingExtension,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
|
|
||||||
if (symbolDefinitions.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
captureEvent('wa_goto_definition_pressed', {
|
|
||||||
source: 'chat',
|
|
||||||
});
|
|
||||||
createAuditAction({
|
|
||||||
action: "user.performed_goto_definition",
|
|
||||||
metadata: {
|
|
||||||
message: symbolName,
|
|
||||||
},
|
|
||||||
}, domain);
|
|
||||||
|
|
||||||
if (symbolDefinitions.length === 1) {
|
|
||||||
const symbolDefinition = symbolDefinitions[0];
|
|
||||||
const { fileName, repoName } = symbolDefinition;
|
|
||||||
|
|
||||||
navigateToPath({
|
|
||||||
repoName,
|
|
||||||
revisionName: revision,
|
|
||||||
path: fileName,
|
|
||||||
pathType: 'blob',
|
|
||||||
highlightRange: symbolDefinition.range,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
navigateToPath({
|
|
||||||
repoName,
|
|
||||||
revisionName: revision,
|
|
||||||
path: fileName,
|
|
||||||
pathType: 'blob',
|
|
||||||
setBrowseState: {
|
|
||||||
selectedSymbolInfo: {
|
|
||||||
symbolName,
|
|
||||||
repoName,
|
|
||||||
revisionName: revision,
|
|
||||||
language: language,
|
|
||||||
},
|
|
||||||
activeExploreMenuTab: "definitions",
|
|
||||||
isBottomPanelCollapsed: false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}, [captureEvent, domain, navigateToPath, revision, repoName, fileName, language]);
|
|
||||||
|
|
||||||
const onFindReferences = useCallback((symbolName: string) => {
|
|
||||||
captureEvent('wa_find_references_pressed', {
|
|
||||||
source: 'chat',
|
|
||||||
});
|
|
||||||
createAuditAction({
|
|
||||||
action: "user.performed_find_references",
|
|
||||||
metadata: {
|
|
||||||
message: symbolName,
|
|
||||||
},
|
|
||||||
}, domain);
|
|
||||||
|
|
||||||
navigateToPath({
|
|
||||||
repoName,
|
|
||||||
revisionName: revision,
|
|
||||||
path: fileName,
|
|
||||||
pathType: 'blob',
|
|
||||||
setBrowseState: {
|
|
||||||
selectedSymbolInfo: {
|
|
||||||
symbolName,
|
|
||||||
repoName,
|
|
||||||
revisionName: revision,
|
|
||||||
language: language,
|
|
||||||
},
|
|
||||||
activeExploreMenuTab: "references",
|
|
||||||
isBottomPanelCollapsed: false,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}, [captureEvent, domain, fileName, language, navigateToPath, repoName, revision]);
|
|
||||||
|
|
||||||
const ExpandCollapseIcon = useMemo(() => {
|
const ExpandCollapseIcon = useMemo(() => {
|
||||||
return isExpanded ? ChevronDown : ChevronRight;
|
return isExpanded ? ChevronDown : ChevronRight;
|
||||||
}, [isExpanded]);
|
}, [isExpanded]);
|
||||||
|
|
@ -342,11 +258,12 @@ const ReferencedFileSourceListItem = ({
|
||||||
>
|
>
|
||||||
{editorRef && hasCodeNavEntitlement && (
|
{editorRef && hasCodeNavEntitlement && (
|
||||||
<SymbolHoverPopup
|
<SymbolHoverPopup
|
||||||
|
source="chat"
|
||||||
editorRef={editorRef}
|
editorRef={editorRef}
|
||||||
revisionName={revision}
|
revisionName={revision}
|
||||||
language={language}
|
language={language}
|
||||||
onFindReferences={onFindReferences}
|
repoName={repoName}
|
||||||
onGotoDefinition={onGotoDefinition}
|
fileName={fileName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CodeMirror>
|
</CodeMirror>
|
||||||
|
|
@ -355,6 +272,6 @@ const ReferencedFileSourceListItem = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default forwardRef(ReferencedFileSourceListItem) as (
|
export default memo(forwardRef(ReferencedFileSourceListItem), isEqual) as (
|
||||||
props: ReferencedFileSourceListItemProps & { ref?: Ref<ReactCodeMirrorRef> },
|
props: ReferencedFileSourceListItemProps & { ref?: Ref<ReactCodeMirrorRef> },
|
||||||
) => ReturnType<typeof ReferencedFileSourceListItem>;
|
) => ReturnType<typeof ReferencedFileSourceListItem>;
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,19 @@ import { getFileSource } from "@/app/api/(client)/client";
|
||||||
import { VscodeFileIcon } from "@/app/components/vscodeFileIcon";
|
import { VscodeFileIcon } from "@/app/components/vscodeFileIcon";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import { isServiceError, unwrapServiceError } from "@/lib/utils";
|
import { isServiceError, unwrapServiceError } from "@/lib/utils";
|
||||||
import { useQueries } from "@tanstack/react-query";
|
import { useQueries } from "@tanstack/react-query";
|
||||||
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||||
import { FileReference, FileSource, Reference, Source } from "../../types";
|
import { FileReference, FileSource, Reference } from "../../types";
|
||||||
|
import { tryResolveFileReference } from '../../utils';
|
||||||
import ReferencedFileSourceListItem from "./referencedFileSourceListItem";
|
import ReferencedFileSourceListItem from "./referencedFileSourceListItem";
|
||||||
|
import isEqual from 'fast-deep-equal/react';
|
||||||
|
|
||||||
interface ReferencedSourcesListViewProps {
|
interface ReferencedSourcesListViewProps {
|
||||||
references: FileReference[];
|
references: FileReference[];
|
||||||
sources: Source[];
|
sources: FileSource[];
|
||||||
index: number;
|
index: number;
|
||||||
hoveredReference?: Reference;
|
hoveredReference?: Reference;
|
||||||
onHoveredReferenceChanged: (reference?: Reference) => void;
|
onHoveredReferenceChanged: (reference?: Reference) => void;
|
||||||
|
|
@ -24,14 +25,7 @@ interface ReferencedSourcesListViewProps {
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveFileReference = (reference: FileReference, sources: FileSource[]): FileSource | undefined => {
|
const ReferencedSourcesListViewComponent = ({
|
||||||
return sources.find(
|
|
||||||
(source) => source.repo.endsWith(reference.repo) &&
|
|
||||||
source.path.endsWith(reference.path)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ReferencedSourcesListView = ({
|
|
||||||
references,
|
references,
|
||||||
sources,
|
sources,
|
||||||
index,
|
index,
|
||||||
|
|
@ -43,7 +37,6 @@ export const ReferencedSourcesListView = ({
|
||||||
}: ReferencedSourcesListViewProps) => {
|
}: ReferencedSourcesListViewProps) => {
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
const editorRefsMap = useRef<Map<string, ReactCodeMirrorRef>>(new Map());
|
const editorRefsMap = useRef<Map<string, ReactCodeMirrorRef>>(new Map());
|
||||||
const domain = useDomain();
|
|
||||||
const [collapsedFileIds, setCollapsedFileIds] = useState<string[]>([]);
|
const [collapsedFileIds, setCollapsedFileIds] = useState<string[]>([]);
|
||||||
|
|
||||||
const getFileId = useCallback((fileSource: FileSource) => {
|
const getFileId = useCallback((fileSource: FileSource) => {
|
||||||
|
|
@ -61,44 +54,27 @@ export const ReferencedSourcesListView = ({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const referencedFileSources = useMemo((): FileSource[] => {
|
|
||||||
const fileSources = sources.filter((source) => source.type === 'file');
|
|
||||||
|
|
||||||
return references
|
|
||||||
.filter((reference) => reference.type === 'file')
|
|
||||||
.map((reference) => resolveFileReference(reference, fileSources))
|
|
||||||
.filter((file) => file !== undefined)
|
|
||||||
// de-duplicate files
|
|
||||||
.filter((file, index, self) =>
|
|
||||||
index === self.findIndex((t) =>
|
|
||||||
t?.path === file?.path
|
|
||||||
&& t?.repo === file?.repo
|
|
||||||
&& t?.revision === file?.revision
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [references, sources]);
|
|
||||||
|
|
||||||
// Memoize the computation of references grouped by file source
|
// Memoize the computation of references grouped by file source
|
||||||
const referencesGroupedByFile = useMemo(() => {
|
const referencesGroupedByFile = useMemo(() => {
|
||||||
const groupedReferences = new Map<string, FileReference[]>();
|
const groupedReferences = new Map<string, FileReference[]>();
|
||||||
|
|
||||||
for (const fileSource of referencedFileSources) {
|
for (const fileSource of sources) {
|
||||||
const fileKey = getFileId(fileSource);
|
const fileKey = getFileId(fileSource);
|
||||||
const referencesInFile = references.filter((reference) => {
|
const referencesInFile = references.filter((reference) => {
|
||||||
if (reference.type !== 'file') {
|
if (reference.type !== 'file') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return resolveFileReference(reference, [fileSource]) !== undefined;
|
return tryResolveFileReference(reference, [fileSource]) !== undefined;
|
||||||
});
|
});
|
||||||
groupedReferences.set(fileKey, referencesInFile);
|
groupedReferences.set(fileKey, referencesInFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupedReferences;
|
return groupedReferences;
|
||||||
}, [references, referencedFileSources, getFileId]);
|
}, [references, sources, getFileId]);
|
||||||
|
|
||||||
const fileSourceQueries = useQueries({
|
const fileSourceQueries = useQueries({
|
||||||
queries: referencedFileSources.map((file) => ({
|
queries: sources.map((file) => ({
|
||||||
queryKey: ['fileSource', file.path, file.repo, file.revision, domain],
|
queryKey: ['fileSource', file.path, file.repo, file.revision],
|
||||||
queryFn: () => unwrapServiceError(getFileSource({
|
queryFn: () => unwrapServiceError(getFileSource({
|
||||||
fileName: file.path,
|
fileName: file.path,
|
||||||
repository: file.repo,
|
repository: file.repo,
|
||||||
|
|
@ -114,7 +90,7 @@ export const ReferencedSourcesListView = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileSource = resolveFileReference(selectedReference, referencedFileSources);
|
const fileSource = tryResolveFileReference(selectedReference, sources);
|
||||||
if (!fileSource) {
|
if (!fileSource) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -181,9 +157,28 @@ export const ReferencedSourcesListView = ({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [getFileId, referencedFileSources, selectedReference]);
|
}, [getFileId, sources, selectedReference]);
|
||||||
|
|
||||||
if (referencedFileSources.length === 0) {
|
const onExpandedChanged = useCallback((fileId: string, isExpanded: boolean) => {
|
||||||
|
if (isExpanded) {
|
||||||
|
setCollapsedFileIds(collapsedFileIds => collapsedFileIds.filter((id) => id !== fileId));
|
||||||
|
} else {
|
||||||
|
setCollapsedFileIds(collapsedFileIds => [...collapsedFileIds, fileId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isExpanded) {
|
||||||
|
const fileSourceStart = document.getElementById(`${fileId}-start`);
|
||||||
|
if (fileSourceStart) {
|
||||||
|
scrollIntoView(fileSourceStart, {
|
||||||
|
scrollMode: 'if-needed',
|
||||||
|
block: 'start',
|
||||||
|
behavior: 'instant',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (sources.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-center text-muted-foreground text-sm">
|
<div className="p-4 text-center text-muted-foreground text-sm">
|
||||||
No file references found
|
No file references found
|
||||||
|
|
@ -198,7 +193,7 @@ export const ReferencedSourcesListView = ({
|
||||||
>
|
>
|
||||||
<div className="space-y-4 pr-2">
|
<div className="space-y-4 pr-2">
|
||||||
{fileSourceQueries.map((query, index) => {
|
{fileSourceQueries.map((query, index) => {
|
||||||
const fileSource = referencedFileSources[index];
|
const fileSource = sources[index];
|
||||||
const fileName = fileSource.path.split('/').pop() ?? fileSource.path;
|
const fileName = fileSource.path.split('/').pop() ?? fileSource.path;
|
||||||
|
|
||||||
if (query.isLoading) {
|
if (query.isLoading) {
|
||||||
|
|
@ -253,30 +248,7 @@ export const ReferencedSourcesListView = ({
|
||||||
selectedReference={selectedReference}
|
selectedReference={selectedReference}
|
||||||
hoveredReference={hoveredReference}
|
hoveredReference={hoveredReference}
|
||||||
isExpanded={!collapsedFileIds.includes(fileId)}
|
isExpanded={!collapsedFileIds.includes(fileId)}
|
||||||
onExpandedChanged={(isExpanded) => {
|
onExpandedChanged={(isExpanded) => onExpandedChanged(fileId, isExpanded)}
|
||||||
if (isExpanded) {
|
|
||||||
setCollapsedFileIds(collapsedFileIds.filter((id) => id !== fileId));
|
|
||||||
} else {
|
|
||||||
setCollapsedFileIds([...collapsedFileIds, fileId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When collapsing a file when you are deep in a scroll, it's a better
|
|
||||||
// experience to have the scroll automatically restored to the top of the file
|
|
||||||
// s.t., header is still sticky to the top of the scroll area.
|
|
||||||
if (!isExpanded) {
|
|
||||||
const fileSourceStart = document.getElementById(`${fileId}-start`);
|
|
||||||
if (!fileSourceStart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollIntoView(fileSourceStart, {
|
|
||||||
scrollMode: 'if-needed',
|
|
||||||
block: 'start',
|
|
||||||
behavior: 'instant',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -284,3 +256,6 @@ export const ReferencedSourcesListView = ({
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoize to prevent unnecessary re-renders
|
||||||
|
export const ReferencedSourcesListView = memo(ReferencedSourcesListViewComponent, isEqual);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { SearchCodeToolUIPart } from "@/features/chat/tools";
|
import { SearchCodeToolUIPart } from "@/features/chat/tools";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import { createPathWithQueryParams, isServiceError } from "@/lib/utils";
|
import { createPathWithQueryParams, isServiceError } from "@/lib/utils";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { FileListItem, ToolHeader, TreeList } from "./shared";
|
import { FileListItem, ToolHeader, TreeList } from "./shared";
|
||||||
|
|
@ -12,10 +11,10 @@ import Link from "next/link";
|
||||||
import { SearchQueryParams } from "@/lib/types";
|
import { SearchQueryParams } from "@/lib/types";
|
||||||
import { PlayIcon } from "@radix-ui/react-icons";
|
import { PlayIcon } from "@radix-ui/react-icons";
|
||||||
import { buildSearchQuery } from "@/features/chat/utils";
|
import { buildSearchQuery } from "@/features/chat/utils";
|
||||||
|
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants";
|
||||||
|
|
||||||
export const SearchCodeToolComponent = ({ part }: { part: SearchCodeToolUIPart }) => {
|
export const SearchCodeToolComponent = ({ part }: { part: SearchCodeToolUIPart }) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const domain = useDomain();
|
|
||||||
|
|
||||||
const displayQuery = useMemo(() => {
|
const displayQuery = useMemo(() => {
|
||||||
if (part.state !== 'input-available' && part.state !== 'output-available') {
|
if (part.state !== 'input-available' && part.state !== 'output-available') {
|
||||||
|
|
@ -78,7 +77,7 @@ export const SearchCodeToolComponent = ({ part }: { part: SearchCodeToolUIPart }
|
||||||
</TreeList>
|
</TreeList>
|
||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
href={createPathWithQueryParams(`/${domain}/search`,
|
href={createPathWithQueryParams(`/${SINGLE_TENANT_ORG_DOMAIN}/search`,
|
||||||
[SearchQueryParams.query, part.output.query],
|
[SearchQueryParams.query, part.output.query],
|
||||||
)}
|
)}
|
||||||
className='flex flex-row items-center gap-2 text-sm text-muted-foreground mt-2 ml-auto w-fit hover:text-foreground'
|
className='flex flex-row items-center gap-2 text-sm text-muted-foreground mt-2 ml-auto w-fit hover:text-foreground'
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
import { VscodeFileIcon } from '@/app/components/vscodeFileIcon';
|
import { VscodeFileIcon } from '@/app/components/vscodeFileIcon';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { useDomain } from '@/hooks/useDomain';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
|
import { ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils";
|
import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils";
|
||||||
|
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants";
|
||||||
|
|
||||||
|
|
||||||
export const FileListItem = ({
|
export const FileListItem = ({
|
||||||
|
|
@ -17,8 +17,6 @@ export const FileListItem = ({
|
||||||
path: string,
|
path: string,
|
||||||
repoName: string,
|
repoName: string,
|
||||||
}) => {
|
}) => {
|
||||||
const domain = useDomain();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={path} className="flex flex-row items-center overflow-hidden hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer p-0.5">
|
<div key={path} className="flex flex-row items-center overflow-hidden hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer p-0.5">
|
||||||
<VscodeFileIcon fileName={path} className="mr-1 flex-shrink-0" />
|
<VscodeFileIcon fileName={path} className="mr-1 flex-shrink-0" />
|
||||||
|
|
@ -28,7 +26,7 @@ export const FileListItem = ({
|
||||||
repoName,
|
repoName,
|
||||||
revisionName: 'HEAD',
|
revisionName: 'HEAD',
|
||||||
path,
|
path,
|
||||||
domain,
|
domain: SINGLE_TENANT_ORG_DOMAIN,
|
||||||
pathType: 'blob',
|
pathType: 'blob',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@ export const findSymbolReferencesTool = tool({
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
symbol: z.string().describe("The symbol to find references to"),
|
symbol: z.string().describe("The symbol to find references to"),
|
||||||
language: z.string().describe("The programming language of the symbol"),
|
language: z.string().describe("The programming language of the symbol"),
|
||||||
|
repository: z.string().describe("The repository to scope the search to").optional(),
|
||||||
}),
|
}),
|
||||||
execute: async ({ symbol, language }) => {
|
execute: async ({ symbol, language, repository }) => {
|
||||||
// @todo: make revision configurable.
|
// @todo: make revision configurable.
|
||||||
const revision = "HEAD";
|
const revision = "HEAD";
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@ export const findSymbolReferencesTool = tool({
|
||||||
symbolName: symbol,
|
symbolName: symbol,
|
||||||
language,
|
language,
|
||||||
revisionName: "HEAD",
|
revisionName: "HEAD",
|
||||||
|
repoName: repository,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
|
|
@ -63,8 +65,9 @@ export const findSymbolDefinitionsTool = tool({
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
symbol: z.string().describe("The symbol to find definitions of"),
|
symbol: z.string().describe("The symbol to find definitions of"),
|
||||||
language: z.string().describe("The programming language of the symbol"),
|
language: z.string().describe("The programming language of the symbol"),
|
||||||
|
repository: z.string().describe("The repository to scope the search to").optional(),
|
||||||
}),
|
}),
|
||||||
execute: async ({ symbol, language }) => {
|
execute: async ({ symbol, language, repository }) => {
|
||||||
// @todo: make revision configurable.
|
// @todo: make revision configurable.
|
||||||
const revision = "HEAD";
|
const revision = "HEAD";
|
||||||
|
|
||||||
|
|
@ -72,6 +75,7 @@ export const findSymbolDefinitionsTool = tool({
|
||||||
symbolName: symbol,
|
symbolName: symbol,
|
||||||
language,
|
language,
|
||||||
revisionName: revision,
|
revisionName: revision,
|
||||||
|
repoName: repository,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export const sbChatMessageMetadataSchema = z.object({
|
||||||
feedback: z.array(z.object({
|
feedback: z.array(z.object({
|
||||||
type: z.enum(['like', 'dislike']),
|
type: z.enum(['like', 'dislike']),
|
||||||
timestamp: z.string(), // ISO date string
|
timestamp: z.string(), // ISO date string
|
||||||
userId: z.string(),
|
userId: z.string().optional(),
|
||||||
})).optional(),
|
})).optional(),
|
||||||
selectedSearchScopes: z.array(searchScopeSchema).optional(),
|
selectedSearchScopes: z.array(searchScopeSchema).optional(),
|
||||||
traceId: z.string().optional(),
|
traceId: z.string().optional(),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { useCallback, useState } from "react";
|
||||||
import { Descendant } from "slate";
|
import { Descendant } from "slate";
|
||||||
import { createUIMessage, getAllMentionElements } from "./utils";
|
import { createUIMessage, getAllMentionElements } from "./utils";
|
||||||
import { slateContentToString } from "./utils";
|
import { slateContentToString } from "./utils";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import { useToast } from "@/components/hooks/use-toast";
|
import { useToast } from "@/components/hooks/use-toast";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { createChat } from "./actions";
|
import { createChat } from "./actions";
|
||||||
|
|
@ -13,9 +12,9 @@ import { createPathWithQueryParams } from "@/lib/utils";
|
||||||
import { SearchScope, SET_CHAT_STATE_SESSION_STORAGE_KEY, SetChatStatePayload } from "./types";
|
import { SearchScope, SET_CHAT_STATE_SESSION_STORAGE_KEY, SetChatStatePayload } from "./types";
|
||||||
import { useSessionStorage } from "usehooks-ts";
|
import { useSessionStorage } from "usehooks-ts";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
|
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants";
|
||||||
|
|
||||||
export const useCreateNewChatThread = () => {
|
export const useCreateNewChatThread = () => {
|
||||||
const domain = useDomain();
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -31,7 +30,7 @@ export const useCreateNewChatThread = () => {
|
||||||
const inputMessage = createUIMessage(text, mentions.map((mention) => mention.data), selectedSearchScopes);
|
const inputMessage = createUIMessage(text, mentions.map((mention) => mention.data), selectedSearchScopes);
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const response = await createChat(domain);
|
const response = await createChat();
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
toast({
|
toast({
|
||||||
description: `❌ Failed to create chat. Reason: ${response.message}`
|
description: `❌ Failed to create chat. Reason: ${response.message}`
|
||||||
|
|
@ -47,11 +46,11 @@ export const useCreateNewChatThread = () => {
|
||||||
selectedSearchScopes,
|
selectedSearchScopes,
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = createPathWithQueryParams(`/${domain}/chat/${response.id}`);
|
const url = createPathWithQueryParams(`/${SINGLE_TENANT_ORG_DOMAIN}/chat/${response.id}`);
|
||||||
|
|
||||||
router.push(url);
|
router.push(url);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}, [domain, router, toast, setChatState, captureEvent]);
|
}, [router, toast, setChatState, captureEvent]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createNewChatThread,
|
createNewChatThread,
|
||||||
|
|
|
||||||
|
|
@ -374,3 +374,13 @@ export const buildSearchQuery = (options: {
|
||||||
export const getLanguageModelKey = (model: LanguageModelInfo) => {
|
export const getLanguageModelKey = (model: LanguageModelInfo) => {
|
||||||
return `${model.provider}-${model.model}-${model.displayName}`;
|
return `${model.provider}-${model.model}-${model.displayName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a file reference and a list of file sources, attempts to resolve the file source that the reference points to.
|
||||||
|
*/
|
||||||
|
export const tryResolveFileReference = (reference: FileReference, sources: FileSource[]): FileSource | undefined => {
|
||||||
|
return sources.find(
|
||||||
|
(source) => source.repo.endsWith(reference.repo) &&
|
||||||
|
source.path.endsWith(reference.path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
import { SearchResponse } from "../search/types";
|
import { SearchResponse } from "../search/types";
|
||||||
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
|
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
|
||||||
import { QueryIR } from '../search/ir';
|
import { QueryIR } from '../search/ir';
|
||||||
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
|
||||||
// The maximum number of matches to return from the search API.
|
// The maximum number of matches to return from the search API.
|
||||||
const MAX_REFERENCE_COUNT = 1000;
|
const MAX_REFERENCE_COUNT = 1000;
|
||||||
|
|
@ -18,6 +19,7 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR
|
||||||
symbolName,
|
symbolName,
|
||||||
language,
|
language,
|
||||||
revisionName = "HEAD",
|
revisionName = "HEAD",
|
||||||
|
repoName,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const languageFilter = getExpandedLanguageFilter(language);
|
const languageFilter = getExpandedLanguageFilter(language);
|
||||||
|
|
@ -40,6 +42,11 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
languageFilter,
|
languageFilter,
|
||||||
|
...(repoName ? [{
|
||||||
|
repo: {
|
||||||
|
regexp: `^${escapeStringRegexp(repoName)}$`,
|
||||||
|
}
|
||||||
|
}]: [])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +74,7 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols
|
||||||
symbolName,
|
symbolName,
|
||||||
language,
|
language,
|
||||||
revisionName = "HEAD",
|
revisionName = "HEAD",
|
||||||
|
repoName
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const languageFilter = getExpandedLanguageFilter(language);
|
const languageFilter = getExpandedLanguageFilter(language);
|
||||||
|
|
@ -93,6 +101,11 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
languageFilter,
|
languageFilter,
|
||||||
|
...(repoName ? [{
|
||||||
|
repo: {
|
||||||
|
regexp: `^${escapeStringRegexp(repoName)}$`,
|
||||||
|
}
|
||||||
|
}]: [])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,16 @@ import { rangeSchema, repositoryInfoSchema } from "../search/types";
|
||||||
export const findRelatedSymbolsRequestSchema = z.object({
|
export const findRelatedSymbolsRequestSchema = z.object({
|
||||||
symbolName: z.string(),
|
symbolName: z.string(),
|
||||||
language: z.string(),
|
language: z.string(),
|
||||||
|
/**
|
||||||
|
* Optional revision name to scope search to.
|
||||||
|
* If not provided, the search will be scoped to HEAD.
|
||||||
|
*/
|
||||||
revisionName: z.string().optional(),
|
revisionName: z.string().optional(),
|
||||||
|
/**
|
||||||
|
* Optional repository name to scope search to.
|
||||||
|
* If not provided, the search will be across all repositories.
|
||||||
|
*/
|
||||||
|
repoName: z.string().optional(),
|
||||||
});
|
});
|
||||||
export type FindRelatedSymbolsRequest = z.infer<typeof findRelatedSymbolsRequestSchema>;
|
export type FindRelatedSymbolsRequest = z.infer<typeof findRelatedSymbolsRequestSchema>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { search } from "./searchApi";
|
||||||
import { sew } from "@/actions";
|
import { sew } from "@/actions";
|
||||||
import { withOptionalAuthV2 } from "@/withAuthV2";
|
import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
import { QueryIR } from './ir';
|
import { QueryIR } from './ir';
|
||||||
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
|
||||||
// @todo (bkellam) #574 : We should really be using `git show <hash>:<path>` to fetch file contents here.
|
// @todo (bkellam) #574 : We should really be using `git show <hash>:<path>` to fetch file contents here.
|
||||||
// This will allow us to support permalinks to files at a specific revision that may not be indexed
|
// This will allow us to support permalinks to files at a specific revision that may not be indexed
|
||||||
// by zoekt. We should also refactor this out of the /search folder.
|
// by zoekt. We should also refactor this out of the /search folder.
|
||||||
|
|
@ -17,16 +19,16 @@ export const getFileSource = async ({ fileName, repository, branch }: FileSource
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
repo: {
|
repo: {
|
||||||
regexp: `^${repository}$`,
|
regexp: `^${escapeStringRegexp(repository)}$`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
regexp: {
|
substring: {
|
||||||
regexp: fileName,
|
pattern: fileName,
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
file_name: true,
|
file_name: true,
|
||||||
content: false
|
content: false,
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
...(branch ? [{
|
...(branch ? [{
|
||||||
branch: {
|
branch: {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
SymExpr,
|
SymExpr,
|
||||||
SyntaxNode,
|
SyntaxNode,
|
||||||
Term,
|
Term,
|
||||||
|
QuotedTerm,
|
||||||
Tree,
|
Tree,
|
||||||
VisibilityExpr,
|
VisibilityExpr,
|
||||||
} from '@sourcebot/query-language';
|
} from '@sourcebot/query-language';
|
||||||
|
|
@ -175,8 +176,12 @@ const transformTreeToIR = async ({
|
||||||
// PrefixExpr contains specific prefix types
|
// PrefixExpr contains specific prefix types
|
||||||
return transformPrefixExpr(node);
|
return transformPrefixExpr(node);
|
||||||
|
|
||||||
|
case QuotedTerm:
|
||||||
case Term: {
|
case Term: {
|
||||||
const termText = input.substring(node.from, node.to).replace(/^"|"$/g, '');
|
const fullText = input.substring(node.from, node.to);
|
||||||
|
// If the term is quoted, then we remove the quotes as they are
|
||||||
|
// not interpreted.
|
||||||
|
const termText = node.type.id === QuotedTerm ? fullText.replace(/^"|"$/g, '') : fullText;
|
||||||
|
|
||||||
return isRegexEnabled ? {
|
return isRegexEnabled ? {
|
||||||
regexp: {
|
regexp: {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { sew } from "@/actions";
|
||||||
import { getRepoPermissionFilterForUser } from "@/prisma";
|
import { getRepoPermissionFilterForUser } from "@/prisma";
|
||||||
import { withOptionalAuthV2 } from "@/withAuthV2";
|
import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
import { PrismaClient, UserWithAccounts } from "@sourcebot/db";
|
import { PrismaClient, UserWithAccounts } from "@sourcebot/db";
|
||||||
import { createLogger, env, hasEntitlement } from "@sourcebot/shared";
|
import { env, hasEntitlement } from "@sourcebot/shared";
|
||||||
import { QueryIR } from './ir';
|
import { QueryIR } from './ir';
|
||||||
import { parseQuerySyntaxIntoIR } from './parser';
|
import { parseQuerySyntaxIntoIR } from './parser';
|
||||||
import { SearchOptions } from "./types";
|
import { SearchOptions } from "./types";
|
||||||
import { createZoektSearchRequest, zoektSearch, zoektStreamSearch } from './zoektSearcher';
|
import { createZoektSearchRequest, zoektSearch, zoektStreamSearch } from './zoektSearcher';
|
||||||
|
|
||||||
const logger = createLogger("searchApi");
|
|
||||||
|
|
||||||
type QueryStringSearchRequest = {
|
type QueryStringSearchRequest = {
|
||||||
queryType: 'string';
|
queryType: 'string';
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ export type SearchOptions = z.infer<typeof searchOptionsSchema>;
|
||||||
|
|
||||||
export const searchRequestSchema = z.object({
|
export const searchRequestSchema = z.object({
|
||||||
query: z.string(), // The zoekt query to execute.
|
query: z.string(), // The zoekt query to execute.
|
||||||
|
source: z.string().optional(), // The source of the search request.
|
||||||
...searchOptionsSchema.shape,
|
...searchOptionsSchema.shape,
|
||||||
});
|
});
|
||||||
export type SearchRequest = z.infer<typeof searchRequestSchema>;
|
export type SearchRequest = z.infer<typeof searchRequestSchema>;
|
||||||
|
|
|
||||||
69
packages/web/src/lib/posthog.ts
Normal file
69
packages/web/src/lib/posthog.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { PostHog } from 'posthog-node'
|
||||||
|
import { env } from '@sourcebot/shared'
|
||||||
|
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
|
||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import { PosthogEvent, PosthogEventMap } from './posthogEvents';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note: This is a subset of the properties stored in the
|
||||||
|
* ph_phc_<id>_posthog cookie.
|
||||||
|
*/
|
||||||
|
export type PostHogCookie = {
|
||||||
|
distinct_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPostHogCookie = (cookie: unknown): cookie is PostHogCookie => {
|
||||||
|
return typeof cookie === 'object' &&
|
||||||
|
cookie !== null &&
|
||||||
|
'distinct_id' in cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to retrieve the PostHog cookie from the given cookie store, returning
|
||||||
|
* undefined if the cookie is not found or is invalid.
|
||||||
|
*/
|
||||||
|
const getPostHogCookie = (cookieStore: Pick<RequestCookies, 'get'>): PostHogCookie | undefined => {
|
||||||
|
const phCookieKey = `ph_${env.POSTHOG_PAPIK}_posthog`;
|
||||||
|
const cookie = cookieStore.get(phCookieKey);
|
||||||
|
|
||||||
|
if (!cookie) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedCookie = (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(cookie.value);
|
||||||
|
} catch (e) {
|
||||||
|
Sentry.captureException(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (isPostHogCookie(parsedCookie)) {
|
||||||
|
return parsedCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E]) {
|
||||||
|
if (env.SOURCEBOT_TELEMETRY_DISABLED === 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const cookie = getPostHogCookie(cookieStore);
|
||||||
|
|
||||||
|
const posthog = new PostHog(env.POSTHOG_PAPIK, {
|
||||||
|
host: 'https://us.i.posthog.com',
|
||||||
|
flushAt: 1,
|
||||||
|
flushInterval: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
posthog.capture({
|
||||||
|
event,
|
||||||
|
properties,
|
||||||
|
distinctId: cookie?.distinct_id ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -273,15 +273,6 @@ export type PosthogEventMap = {
|
||||||
wa_api_key_created: {},
|
wa_api_key_created: {},
|
||||||
wa_api_key_creation_fail: {},
|
wa_api_key_creation_fail: {},
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
wa_goto_definition_pressed: {
|
|
||||||
source: 'chat' | 'browse' | 'preview',
|
|
||||||
},
|
|
||||||
wa_find_references_pressed: {
|
|
||||||
source: 'chat' | 'browse' | 'preview',
|
|
||||||
},
|
|
||||||
//////////////////////////////////////////////////////////////////
|
|
||||||
wa_explore_menu_reference_clicked: {},
|
|
||||||
//////////////////////////////////////////////////////////////////
|
|
||||||
wa_chat_feedback_submitted: {
|
wa_chat_feedback_submitted: {
|
||||||
feedback: 'like' | 'dislike',
|
feedback: 'like' | 'dislike',
|
||||||
chatId: string,
|
chatId: string,
|
||||||
|
|
@ -302,5 +293,31 @@ export type PosthogEventMap = {
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
wa_github_star_toast_displayed: {},
|
wa_github_star_toast_displayed: {},
|
||||||
wa_github_star_toast_clicked: {},
|
wa_github_star_toast_clicked: {},
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
wa_goto_definition_pressed: {
|
||||||
|
source: 'chat' | 'browse' | 'preview',
|
||||||
|
},
|
||||||
|
wa_find_references_pressed: {
|
||||||
|
source: 'chat' | 'browse' | 'preview',
|
||||||
|
},
|
||||||
|
wa_symbol_hover_popup_definitions_loaded: {
|
||||||
|
durationMs: number,
|
||||||
|
},
|
||||||
|
wa_explore_menu_reference_clicked: {},
|
||||||
|
wa_explore_menu_references_loaded: {
|
||||||
|
durationMs: number,
|
||||||
|
// Whether or not the user is searching all repositories.
|
||||||
|
isGlobalSearchEnabled: boolean,
|
||||||
|
},
|
||||||
|
wa_explore_menu_definitions_loaded: {
|
||||||
|
durationMs: number,
|
||||||
|
// Whether or not the user is searching all repositories.
|
||||||
|
isGlobalSearchEnabled: boolean,
|
||||||
|
},
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
api_code_search_request: {
|
||||||
|
source: string;
|
||||||
|
type: 'streamed' | 'blocking';
|
||||||
|
},
|
||||||
}
|
}
|
||||||
export type PosthogEvent = keyof PosthogEventMap;
|
export type PosthogEvent = keyof PosthogEventMap;
|
||||||
|
|
@ -35,7 +35,8 @@
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|
|
||||||
422
yarn.lock
422
yarn.lock
|
|
@ -1649,12 +1649,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@emnapi/runtime@npm:^1.4.4":
|
"@emnapi/runtime@npm:^1.7.0":
|
||||||
version: 1.4.5
|
version: 1.7.1
|
||||||
resolution: "@emnapi/runtime@npm:1.4.5"
|
resolution: "@emnapi/runtime@npm:1.7.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: "npm:^2.4.0"
|
tslib: "npm:^2.4.0"
|
||||||
checksum: 10c0/37a0278be5ac81e918efe36f1449875cbafba947039c53c65a1f8fc238001b866446fc66041513b286baaff5d6f9bec667f5164b3ca481373a8d9cb65bfc984b
|
checksum: 10c0/26b851cd3e93877d8732a985a2ebf5152325bbacc6204ef5336a47359dedcc23faeb08cdfcb8bb389b5401b3e894b882bc1a1e55b4b7c1ed1e67c991a760ddd5
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -2426,6 +2426,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/colour@npm:^1.0.0":
|
||||||
|
version: 1.0.0
|
||||||
|
resolution: "@img/colour@npm:1.0.0"
|
||||||
|
checksum: 10c0/02261719c1e0d7aa5a2d585981954f2ac126f0c432400aa1a01b925aa2c41417b7695da8544ee04fd29eba7ecea8eaf9b8bef06f19dc8faba78f94eeac40667d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64@npm:0.33.5":
|
"@img/sharp-darwin-arm64@npm:0.33.5":
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
resolution: "@img/sharp-darwin-arm64@npm:0.33.5"
|
resolution: "@img/sharp-darwin-arm64@npm:0.33.5"
|
||||||
|
|
@ -2438,11 +2445,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64@npm:0.34.3":
|
"@img/sharp-darwin-arm64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-darwin-arm64@npm:0.34.3"
|
resolution: "@img/sharp-darwin-arm64@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0"
|
"@img/sharp-libvips-darwin-arm64": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-darwin-arm64":
|
"@img/sharp-libvips-darwin-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2462,11 +2469,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-darwin-x64@npm:0.34.3":
|
"@img/sharp-darwin-x64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-darwin-x64@npm:0.34.3"
|
resolution: "@img/sharp-darwin-x64@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-darwin-x64": "npm:1.2.0"
|
"@img/sharp-libvips-darwin-x64": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-darwin-x64":
|
"@img/sharp-libvips-darwin-x64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2481,9 +2488,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-arm64@npm:1.2.0":
|
"@img/sharp-libvips-darwin-arm64@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.0"
|
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4"
|
||||||
conditions: os=darwin & cpu=arm64
|
conditions: os=darwin & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2495,9 +2502,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-x64@npm:1.2.0":
|
"@img/sharp-libvips-darwin-x64@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.0"
|
resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4"
|
||||||
conditions: os=darwin & cpu=x64
|
conditions: os=darwin & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2509,9 +2516,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm64@npm:1.2.0":
|
"@img/sharp-libvips-linux-arm64@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.0"
|
resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4"
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2523,20 +2530,27 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm@npm:1.2.0":
|
"@img/sharp-libvips-linux-arm@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-linux-arm@npm:1.2.0"
|
resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4"
|
||||||
conditions: os=linux & cpu=arm & libc=glibc
|
conditions: os=linux & cpu=arm & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-ppc64@npm:1.2.0":
|
"@img/sharp-libvips-linux-ppc64@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.0"
|
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4"
|
||||||
conditions: os=linux & cpu=ppc64 & libc=glibc
|
conditions: os=linux & cpu=ppc64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-riscv64@npm:1.2.4":
|
||||||
|
version: 1.2.4
|
||||||
|
resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4"
|
||||||
|
conditions: os=linux & cpu=riscv64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-s390x@npm:1.0.4":
|
"@img/sharp-libvips-linux-s390x@npm:1.0.4":
|
||||||
version: 1.0.4
|
version: 1.0.4
|
||||||
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4"
|
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4"
|
||||||
|
|
@ -2544,9 +2558,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-s390x@npm:1.2.0":
|
"@img/sharp-libvips-linux-s390x@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.0"
|
resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4"
|
||||||
conditions: os=linux & cpu=s390x & libc=glibc
|
conditions: os=linux & cpu=s390x & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2558,9 +2572,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-x64@npm:1.2.0":
|
"@img/sharp-libvips-linux-x64@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-linux-x64@npm:1.2.0"
|
resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4"
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2572,9 +2586,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0":
|
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0"
|
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4"
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2586,9 +2600,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.0":
|
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4":
|
||||||
version: 1.2.0
|
version: 1.2.4
|
||||||
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.0"
|
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4"
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2605,11 +2619,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-arm64@npm:0.34.3":
|
"@img/sharp-linux-arm64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-linux-arm64@npm:0.34.3"
|
resolution: "@img/sharp-linux-arm64@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-arm64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-arm64": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-arm64":
|
"@img/sharp-libvips-linux-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2629,11 +2643,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-arm@npm:0.34.3":
|
"@img/sharp-linux-arm@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-linux-arm@npm:0.34.3"
|
resolution: "@img/sharp-linux-arm@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-arm": "npm:1.2.0"
|
"@img/sharp-libvips-linux-arm": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-arm":
|
"@img/sharp-libvips-linux-arm":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2641,11 +2655,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-ppc64@npm:0.34.3":
|
"@img/sharp-linux-ppc64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-linux-ppc64@npm:0.34.3"
|
resolution: "@img/sharp-linux-ppc64@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-ppc64": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-ppc64":
|
"@img/sharp-libvips-linux-ppc64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2653,6 +2667,18 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linux-riscv64@npm:0.34.5":
|
||||||
|
version: 0.34.5
|
||||||
|
resolution: "@img/sharp-linux-riscv64@npm:0.34.5"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linux-riscv64": "npm:1.2.4"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linux-riscv64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=riscv64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-s390x@npm:0.33.5":
|
"@img/sharp-linux-s390x@npm:0.33.5":
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
resolution: "@img/sharp-linux-s390x@npm:0.33.5"
|
resolution: "@img/sharp-linux-s390x@npm:0.33.5"
|
||||||
|
|
@ -2665,11 +2691,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-s390x@npm:0.34.3":
|
"@img/sharp-linux-s390x@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-linux-s390x@npm:0.34.3"
|
resolution: "@img/sharp-linux-s390x@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-s390x": "npm:1.2.0"
|
"@img/sharp-libvips-linux-s390x": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-s390x":
|
"@img/sharp-libvips-linux-s390x":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2689,11 +2715,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linux-x64@npm:0.34.3":
|
"@img/sharp-linux-x64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-linux-x64@npm:0.34.3"
|
resolution: "@img/sharp-linux-x64@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linux-x64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-x64": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linux-x64":
|
"@img/sharp-libvips-linux-x64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2713,11 +2739,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-arm64@npm:0.34.3":
|
"@img/sharp-linuxmusl-arm64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.3"
|
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0"
|
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linuxmusl-arm64":
|
"@img/sharp-libvips-linuxmusl-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2737,11 +2763,11 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-x64@npm:0.34.3":
|
"@img/sharp-linuxmusl-x64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.3"
|
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0"
|
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-libvips-linuxmusl-x64":
|
"@img/sharp-libvips-linuxmusl-x64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -2758,18 +2784,18 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-wasm32@npm:0.34.3":
|
"@img/sharp-wasm32@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-wasm32@npm:0.34.3"
|
resolution: "@img/sharp-wasm32@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@emnapi/runtime": "npm:^1.4.4"
|
"@emnapi/runtime": "npm:^1.7.0"
|
||||||
conditions: cpu=wasm32
|
conditions: cpu=wasm32
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-win32-arm64@npm:0.34.3":
|
"@img/sharp-win32-arm64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-win32-arm64@npm:0.34.3"
|
resolution: "@img/sharp-win32-arm64@npm:0.34.5"
|
||||||
conditions: os=win32 & cpu=arm64
|
conditions: os=win32 & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2781,9 +2807,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-win32-ia32@npm:0.34.3":
|
"@img/sharp-win32-ia32@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-win32-ia32@npm:0.34.3"
|
resolution: "@img/sharp-win32-ia32@npm:0.34.5"
|
||||||
conditions: os=win32 & cpu=ia32
|
conditions: os=win32 & cpu=ia32
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -2795,9 +2821,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@img/sharp-win32-x64@npm:0.34.3":
|
"@img/sharp-win32-x64@npm:0.34.5":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "@img/sharp-win32-x64@npm:0.34.3"
|
resolution: "@img/sharp-win32-x64@npm:0.34.5"
|
||||||
conditions: os=win32 & cpu=x64
|
conditions: os=win32 & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3268,10 +3294,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/env@npm:15.5.0":
|
"@next/env@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/env@npm:15.5.0"
|
resolution: "@next/env@npm:15.5.7"
|
||||||
checksum: 10c0/13ed931688c26b764499840022c9855763fb583e728cf30933b2ff71d3ca681eb71611ec4d7580ecb45197c18a1a4498ba5a61cb60edc6e18966103620d11bad
|
checksum: 10c0/f92d99e5fa3516c6b7699abafd9bd813f5c1889dd257ab098f1b71f93137f5e4f49792e22f6dddf8a59efcb134e8e84277c983ff88607b2a42aac651bfde78ea
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3291,9 +3317,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-darwin-arm64@npm:15.5.0":
|
"@next/swc-darwin-arm64@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-darwin-arm64@npm:15.5.0"
|
resolution: "@next/swc-darwin-arm64@npm:15.5.7"
|
||||||
conditions: os=darwin & cpu=arm64
|
conditions: os=darwin & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3305,9 +3331,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-darwin-x64@npm:15.5.0":
|
"@next/swc-darwin-x64@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-darwin-x64@npm:15.5.0"
|
resolution: "@next/swc-darwin-x64@npm:15.5.7"
|
||||||
conditions: os=darwin & cpu=x64
|
conditions: os=darwin & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3319,9 +3345,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu@npm:15.5.0":
|
"@next/swc-linux-arm64-gnu@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-linux-arm64-gnu@npm:15.5.0"
|
resolution: "@next/swc-linux-arm64-gnu@npm:15.5.7"
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3333,9 +3359,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl@npm:15.5.0":
|
"@next/swc-linux-arm64-musl@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-linux-arm64-musl@npm:15.5.0"
|
resolution: "@next/swc-linux-arm64-musl@npm:15.5.7"
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3347,9 +3373,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu@npm:15.5.0":
|
"@next/swc-linux-x64-gnu@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-linux-x64-gnu@npm:15.5.0"
|
resolution: "@next/swc-linux-x64-gnu@npm:15.5.7"
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3361,9 +3387,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl@npm:15.5.0":
|
"@next/swc-linux-x64-musl@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-linux-x64-musl@npm:15.5.0"
|
resolution: "@next/swc-linux-x64-musl@npm:15.5.7"
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3375,9 +3401,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc@npm:15.5.0":
|
"@next/swc-win32-arm64-msvc@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-win32-arm64-msvc@npm:15.5.0"
|
resolution: "@next/swc-win32-arm64-msvc@npm:15.5.7"
|
||||||
conditions: os=win32 & cpu=arm64
|
conditions: os=win32 & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -3396,9 +3422,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc@npm:15.5.0":
|
"@next/swc-win32-x64-msvc@npm:15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "@next/swc-win32-x64-msvc@npm:15.5.0"
|
resolution: "@next/swc-win32-x64-msvc@npm:15.5.7"
|
||||||
conditions: os=win32 & cpu=x64
|
conditions: os=win32 & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -4573,6 +4599,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@posthog/core@npm:1.6.0":
|
||||||
|
version: 1.6.0
|
||||||
|
resolution: "@posthog/core@npm:1.6.0"
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: "npm:^7.0.6"
|
||||||
|
checksum: 10c0/28aa907bb21b18587bc5f47c44349ebc834b37d9a4cedb1a18d7b673d4d7cdad2120dda80deceaee707b2f52333e9a08a8e591e1fc4066521ce05e820b76309f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/client@npm:6.2.1":
|
"@prisma/client@npm:6.2.1":
|
||||||
version: 6.2.1
|
version: 6.2.1
|
||||||
resolution: "@prisma/client@npm:6.2.1"
|
resolution: "@prisma/client@npm:6.2.1"
|
||||||
|
|
@ -8155,8 +8190,8 @@ __metadata:
|
||||||
"@types/node": "npm:^20"
|
"@types/node": "npm:^20"
|
||||||
"@types/nodemailer": "npm:^6.4.17"
|
"@types/nodemailer": "npm:^6.4.17"
|
||||||
"@types/psl": "npm:^1.1.3"
|
"@types/psl": "npm:^1.1.3"
|
||||||
"@types/react": "npm:19.1.10"
|
"@types/react": "npm:19.2.1"
|
||||||
"@types/react-dom": "npm:19.1.7"
|
"@types/react-dom": "npm:19.2.1"
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^8.40.0"
|
"@typescript-eslint/eslint-plugin": "npm:^8.40.0"
|
||||||
"@typescript-eslint/parser": "npm:^8.40.0"
|
"@typescript-eslint/parser": "npm:^8.40.0"
|
||||||
"@uidotdev/usehooks": "npm:^2.4.1"
|
"@uidotdev/usehooks": "npm:^2.4.1"
|
||||||
|
|
@ -8197,6 +8232,7 @@ __metadata:
|
||||||
eslint-config-next: "npm:15.5.0"
|
eslint-config-next: "npm:15.5.0"
|
||||||
eslint-plugin-react: "npm:^7.37.5"
|
eslint-plugin-react: "npm:^7.37.5"
|
||||||
eslint-plugin-react-hooks: "npm:^5.2.0"
|
eslint-plugin-react-hooks: "npm:^5.2.0"
|
||||||
|
fast-deep-equal: "npm:^3.1.3"
|
||||||
fuse.js: "npm:^7.0.0"
|
fuse.js: "npm:^7.0.0"
|
||||||
google-auth-library: "npm:^10.1.0"
|
google-auth-library: "npm:^10.1.0"
|
||||||
graphql: "npm:^16.9.0"
|
graphql: "npm:^16.9.0"
|
||||||
|
|
@ -8207,7 +8243,7 @@ __metadata:
|
||||||
langfuse-vercel: "npm:^3.38.4"
|
langfuse-vercel: "npm:^3.38.4"
|
||||||
lucide-react: "npm:^0.517.0"
|
lucide-react: "npm:^0.517.0"
|
||||||
micromatch: "npm:^4.0.8"
|
micromatch: "npm:^4.0.8"
|
||||||
next: "npm:15.5.0"
|
next: "npm:^15.5.7"
|
||||||
next-auth: "npm:^5.0.0-beta.30"
|
next-auth: "npm:^5.0.0-beta.30"
|
||||||
next-navigation-guard: "npm:^0.2.0"
|
next-navigation-guard: "npm:^0.2.0"
|
||||||
next-themes: "npm:^0.3.0"
|
next-themes: "npm:^0.3.0"
|
||||||
|
|
@ -8218,11 +8254,12 @@ __metadata:
|
||||||
parse-diff: "npm:^0.11.1"
|
parse-diff: "npm:^0.11.1"
|
||||||
postcss: "npm:^8"
|
postcss: "npm:^8"
|
||||||
posthog-js: "npm:^1.161.5"
|
posthog-js: "npm:^1.161.5"
|
||||||
|
posthog-node: "npm:^5.15.0"
|
||||||
pretty-bytes: "npm:^6.1.1"
|
pretty-bytes: "npm:^6.1.1"
|
||||||
psl: "npm:^1.15.0"
|
psl: "npm:^1.15.0"
|
||||||
react: "npm:19.1.1"
|
react: "npm:^19.2.1"
|
||||||
react-device-detect: "npm:^2.2.3"
|
react-device-detect: "npm:^2.2.3"
|
||||||
react-dom: "npm:19.1.1"
|
react-dom: "npm:^19.2.1"
|
||||||
react-email: "npm:3.0.3"
|
react-email: "npm:3.0.3"
|
||||||
react-hook-form: "npm:^7.53.0"
|
react-hook-form: "npm:^7.53.0"
|
||||||
react-hotkeys-hook: "npm:^4.5.1"
|
react-hotkeys-hook: "npm:^4.5.1"
|
||||||
|
|
@ -8873,21 +8910,21 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react-dom@npm:19.1.7":
|
"@types/react-dom@npm:19.2.1":
|
||||||
version: 19.1.7
|
version: 19.2.1
|
||||||
resolution: "@types/react-dom@npm:19.1.7"
|
resolution: "@types/react-dom@npm:19.2.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/react": ^19.0.0
|
"@types/react": ^19.2.0
|
||||||
checksum: 10c0/8db5751c1567552fe4e1ece9f5823b682f2994ec8d30ed34ba0ef984e3c8ace1435f8be93d02f55c350147e78ac8c4dbcd8ed2c3b6a60f575bc5374f588c51c9
|
checksum: 10c0/0dbbc5b7ecd74681bfac95a413133b26118a70b8840748277abafa47e5c7a037beae6a660e6a21fb53f5cbdb0b2d33e117ea7bbd976a888c298392a8a96bc68f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react@npm:19.1.10":
|
"@types/react@npm:19.2.1":
|
||||||
version: 19.1.10
|
version: 19.2.1
|
||||||
resolution: "@types/react@npm:19.1.10"
|
resolution: "@types/react@npm:19.2.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
csstype: "npm:^3.0.2"
|
csstype: "npm:^3.0.2"
|
||||||
checksum: 10c0/fb583deacd0a815e2775dc1b9f764532d8cacb748ddd2c2914805a46c257ce6c237b4078f44009692074db212ab61a390301c6470f07f5aa5bfdeb78a2acfda1
|
checksum: 10c0/c44881c275da91156ce02986ab1f59c9724db256f4850d3937c9acea561a6ab1fe1028f7a1fc4da3a2c1bcb00de29e238922e8c6d42a727ef2e6e0cd40b3db9f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -11309,10 +11346,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"detect-libc@npm:^2.0.4":
|
"detect-libc@npm:^2.1.2":
|
||||||
version: 2.0.4
|
version: 2.1.2
|
||||||
resolution: "detect-libc@npm:2.0.4"
|
resolution: "detect-libc@npm:2.1.2"
|
||||||
checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c
|
checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -16141,19 +16178,19 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"next@npm:15.5.0":
|
"next@npm:^15.5.7":
|
||||||
version: 15.5.0
|
version: 15.5.7
|
||||||
resolution: "next@npm:15.5.0"
|
resolution: "next@npm:15.5.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@next/env": "npm:15.5.0"
|
"@next/env": "npm:15.5.7"
|
||||||
"@next/swc-darwin-arm64": "npm:15.5.0"
|
"@next/swc-darwin-arm64": "npm:15.5.7"
|
||||||
"@next/swc-darwin-x64": "npm:15.5.0"
|
"@next/swc-darwin-x64": "npm:15.5.7"
|
||||||
"@next/swc-linux-arm64-gnu": "npm:15.5.0"
|
"@next/swc-linux-arm64-gnu": "npm:15.5.7"
|
||||||
"@next/swc-linux-arm64-musl": "npm:15.5.0"
|
"@next/swc-linux-arm64-musl": "npm:15.5.7"
|
||||||
"@next/swc-linux-x64-gnu": "npm:15.5.0"
|
"@next/swc-linux-x64-gnu": "npm:15.5.7"
|
||||||
"@next/swc-linux-x64-musl": "npm:15.5.0"
|
"@next/swc-linux-x64-musl": "npm:15.5.7"
|
||||||
"@next/swc-win32-arm64-msvc": "npm:15.5.0"
|
"@next/swc-win32-arm64-msvc": "npm:15.5.7"
|
||||||
"@next/swc-win32-x64-msvc": "npm:15.5.0"
|
"@next/swc-win32-x64-msvc": "npm:15.5.7"
|
||||||
"@swc/helpers": "npm:0.5.15"
|
"@swc/helpers": "npm:0.5.15"
|
||||||
caniuse-lite: "npm:^1.0.30001579"
|
caniuse-lite: "npm:^1.0.30001579"
|
||||||
postcss: "npm:8.4.31"
|
postcss: "npm:8.4.31"
|
||||||
|
|
@ -16196,7 +16233,7 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
next: dist/bin/next
|
next: dist/bin/next
|
||||||
checksum: 10c0/8691787562666713e9ee8bc3edad646328d9cea85d5c4c10bd05a978afa25bc9927bbfd50acc69ceb268296741b5f71e78fa0213fe65b731a55e73b0f5807c24
|
checksum: 10c0/baf5b9f42416c478702b3894479b3d7862bc4abf18afe0e43b7fc7ed35567b8dc6cb76cd94906505bab9013cb8d0f3370cdc0451c01ec15ae5a638d37b5ba7c7
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -17208,6 +17245,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"posthog-node@npm:^5.15.0":
|
||||||
|
version: 5.15.0
|
||||||
|
resolution: "posthog-node@npm:5.15.0"
|
||||||
|
dependencies:
|
||||||
|
"@posthog/core": "npm:1.6.0"
|
||||||
|
checksum: 10c0/7db929453cefc9b2d0017e1f7c9ffe7e6ecd2a495ee9861bd5e8f3873f72fa29a318dd7bccf10eb15639e1860aab396d4be502af88afba96ed15ac8b46d57e9d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"preact-render-to-string@npm:6.5.11":
|
"preact-render-to-string@npm:6.5.11":
|
||||||
version: 6.5.11
|
version: 6.5.11
|
||||||
resolution: "preact-render-to-string@npm:6.5.11"
|
resolution: "preact-render-to-string@npm:6.5.11"
|
||||||
|
|
@ -17538,14 +17584,14 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-dom@npm:19.1.1":
|
"react-dom@npm:^19.2.1":
|
||||||
version: 19.1.1
|
version: 19.2.1
|
||||||
resolution: "react-dom@npm:19.1.1"
|
resolution: "react-dom@npm:19.2.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
scheduler: "npm:^0.26.0"
|
scheduler: "npm:^0.27.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^19.1.1
|
react: ^19.2.1
|
||||||
checksum: 10c0/8c91198510521299c56e4e8d5e3a4508b2734fb5e52f29eeac33811de64e76fe586ad32c32182e2e84e070d98df67125da346c3360013357228172dbcd20bcdd
|
checksum: 10c0/e56b6b3d72314df580ca800b70a69a21c6372703c8f45d9b5451ca6519faefb2496d76ffa9c5adb94136d2bbf2fd303d0dfc208a2cd77ede3132877471af9470
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -17762,10 +17808,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react@npm:19.1.1":
|
"react@npm:^19.2.1":
|
||||||
version: 19.1.1
|
version: 19.2.1
|
||||||
resolution: "react@npm:19.1.1"
|
resolution: "react@npm:19.2.1"
|
||||||
checksum: 10c0/8c9769a2dfd02e603af6445058325e6c8a24b47b185d0e461f66a6454765ddcaecb3f0a90184836c68bb509f3c38248359edbc42f0d07c23eb500a5c30c87b4e
|
checksum: 10c0/2b5eaf407abb3db84090434c20d6c5a8e447ab7abcd8fe9eaf1ddc299babcf31284ee9db7ea5671d21c85ac5298bd632fa1a7da1ed78d5b368a537f5e1cd5d62
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -18489,10 +18535,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"scheduler@npm:^0.26.0":
|
"scheduler@npm:^0.27.0":
|
||||||
version: 0.26.0
|
version: 0.27.0
|
||||||
resolution: "scheduler@npm:0.26.0"
|
resolution: "scheduler@npm:0.27.0"
|
||||||
checksum: 10c0/5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356
|
checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -18541,12 +18587,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"semver@npm:^7.7.2":
|
"semver@npm:^7.7.3":
|
||||||
version: 7.7.2
|
version: 7.7.3
|
||||||
resolution: "semver@npm:7.7.2"
|
resolution: "semver@npm:7.7.3"
|
||||||
bin:
|
bin:
|
||||||
semver: bin/semver.js
|
semver: bin/semver.js
|
||||||
checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea
|
checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -18744,34 +18790,36 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"sharp@npm:^0.34.3":
|
"sharp@npm:^0.34.3":
|
||||||
version: 0.34.3
|
version: 0.34.5
|
||||||
resolution: "sharp@npm:0.34.3"
|
resolution: "sharp@npm:0.34.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-darwin-arm64": "npm:0.34.3"
|
"@img/colour": "npm:^1.0.0"
|
||||||
"@img/sharp-darwin-x64": "npm:0.34.3"
|
"@img/sharp-darwin-arm64": "npm:0.34.5"
|
||||||
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0"
|
"@img/sharp-darwin-x64": "npm:0.34.5"
|
||||||
"@img/sharp-libvips-darwin-x64": "npm:1.2.0"
|
"@img/sharp-libvips-darwin-arm64": "npm:1.2.4"
|
||||||
"@img/sharp-libvips-linux-arm": "npm:1.2.0"
|
"@img/sharp-libvips-darwin-x64": "npm:1.2.4"
|
||||||
"@img/sharp-libvips-linux-arm64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-arm": "npm:1.2.4"
|
||||||
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-arm64": "npm:1.2.4"
|
||||||
"@img/sharp-libvips-linux-s390x": "npm:1.2.0"
|
"@img/sharp-libvips-linux-ppc64": "npm:1.2.4"
|
||||||
"@img/sharp-libvips-linux-x64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-riscv64": "npm:1.2.4"
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-s390x": "npm:1.2.4"
|
||||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0"
|
"@img/sharp-libvips-linux-x64": "npm:1.2.4"
|
||||||
"@img/sharp-linux-arm": "npm:0.34.3"
|
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4"
|
||||||
"@img/sharp-linux-arm64": "npm:0.34.3"
|
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4"
|
||||||
"@img/sharp-linux-ppc64": "npm:0.34.3"
|
"@img/sharp-linux-arm": "npm:0.34.5"
|
||||||
"@img/sharp-linux-s390x": "npm:0.34.3"
|
"@img/sharp-linux-arm64": "npm:0.34.5"
|
||||||
"@img/sharp-linux-x64": "npm:0.34.3"
|
"@img/sharp-linux-ppc64": "npm:0.34.5"
|
||||||
"@img/sharp-linuxmusl-arm64": "npm:0.34.3"
|
"@img/sharp-linux-riscv64": "npm:0.34.5"
|
||||||
"@img/sharp-linuxmusl-x64": "npm:0.34.3"
|
"@img/sharp-linux-s390x": "npm:0.34.5"
|
||||||
"@img/sharp-wasm32": "npm:0.34.3"
|
"@img/sharp-linux-x64": "npm:0.34.5"
|
||||||
"@img/sharp-win32-arm64": "npm:0.34.3"
|
"@img/sharp-linuxmusl-arm64": "npm:0.34.5"
|
||||||
"@img/sharp-win32-ia32": "npm:0.34.3"
|
"@img/sharp-linuxmusl-x64": "npm:0.34.5"
|
||||||
"@img/sharp-win32-x64": "npm:0.34.3"
|
"@img/sharp-wasm32": "npm:0.34.5"
|
||||||
color: "npm:^4.2.3"
|
"@img/sharp-win32-arm64": "npm:0.34.5"
|
||||||
detect-libc: "npm:^2.0.4"
|
"@img/sharp-win32-ia32": "npm:0.34.5"
|
||||||
semver: "npm:^7.7.2"
|
"@img/sharp-win32-x64": "npm:0.34.5"
|
||||||
|
detect-libc: "npm:^2.1.2"
|
||||||
|
semver: "npm:^7.7.3"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
"@img/sharp-darwin-arm64":
|
"@img/sharp-darwin-arm64":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -18787,6 +18835,8 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-libvips-linux-ppc64":
|
"@img/sharp-libvips-linux-ppc64":
|
||||||
optional: true
|
optional: true
|
||||||
|
"@img/sharp-libvips-linux-riscv64":
|
||||||
|
optional: true
|
||||||
"@img/sharp-libvips-linux-s390x":
|
"@img/sharp-libvips-linux-s390x":
|
||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-libvips-linux-x64":
|
"@img/sharp-libvips-linux-x64":
|
||||||
|
|
@ -18801,6 +18851,8 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-linux-ppc64":
|
"@img/sharp-linux-ppc64":
|
||||||
optional: true
|
optional: true
|
||||||
|
"@img/sharp-linux-riscv64":
|
||||||
|
optional: true
|
||||||
"@img/sharp-linux-s390x":
|
"@img/sharp-linux-s390x":
|
||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-linux-x64":
|
"@img/sharp-linux-x64":
|
||||||
|
|
@ -18817,7 +18869,7 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-win32-x64":
|
"@img/sharp-win32-x64":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/df9e6645e3db6ed298a0ac956ba74e468c367fc038b547936fbdddc6a29fce9af40413acbef73b3716291530760f311a20e45c8983f20ee5ea69dd2f21464a2b
|
checksum: 10c0/fd79e29df0597a7d5704b8461c51f944ead91a5243691697be6e8243b966402beda53ddc6f0a53b96ea3cb8221f0b244aa588114d3ebf8734fb4aefd41ab802f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue