diff --git a/.env.development b/.env.development index ddb981af..57d75115 100644 --- a/.env.development +++ b/.env.development @@ -6,8 +6,6 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" ZOEKT_WEBSERVER_URL="http://localhost:6070" # The command to use for generating ctags. CTAGS_COMMAND=ctags -# logging, strict -SRC_TENANT_ENFORCEMENT_MODE=strict # Auth.JS # You can generate a new secret with: @@ -23,7 +21,7 @@ AUTH_URL="http://localhost:3000" DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot) 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_FROM_ADDRESS="" # The from address for transactional emails. @@ -31,7 +29,6 @@ SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem # PostHog # POSTHOG_PAPIK="" -# NEXT_PUBLIC_POSTHOG_PAPIK="" # Sentry # SENTRY_BACKEND_DSN="" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a129e5ce..f43a46bd 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ contact_links: - name: 👾 Discord - url: https://discord.gg/GbXMEM5H + url: https://discord.gg/HDScTs3ptP about: Something else? Join the Discord! diff --git a/.github/workflows/_gcp-deploy.yml b/.github/workflows/_gcp-deploy.yml index 15fde89b..0454e5b6 100644 --- a/.github/workflows/_gcp-deploy.yml +++ b/.github/workflows/_gcp-deploy.yml @@ -55,7 +55,6 @@ jobs: ${{ env.IMAGE_PATH }}:latest build-args: | 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_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }} NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }} diff --git a/.github/workflows/ghcr-publish.yml b/.github/workflows/ghcr-publish.yml index cf96bea7..67bb9071 100644 --- a/.github/workflows/ghcr-publish.yml +++ b/.github/workflows/ghcr-publish.yml @@ -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 build-args: | NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }} - NEXT_PUBLIC_POSTHOG_PAPIK=${{ vars.NEXT_PUBLIC_POSTHOG_PAPIK }} - name: Export digest run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d34c67..0d9acdf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Fixed review agent so that it works with GHES instances [#611](https://github.com/sourcebot-dev/sourcebot/pull/611) + +## [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 + +### Added +- Added support for streaming code search results. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) +- Added buttons to toggle case sensitivity and regex patterns. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) +- Added counts to members, requets, and invites tabs in the members settings. [#621](https://github.com/sourcebot-dev/sourcebot/pull/621) +- [Sourcebot EE] Add support for Authentik as a identity provider. [#627](https://github.com/sourcebot-dev/sourcebot/pull/627) + +### Changed +- Changed the default search behaviour to match patterns as substrings and **not** regular expressions. Regular expressions can be used by toggling the regex button in search bar. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) +- Renamed `public` query prefix to `visibility`. Allowed values for `visibility` are `public`, `private`, and `any`. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) +- Changed `archived` query prefix to accept values `yes`, `no`, and `only`. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) + +### Removed +- Removed `case` query prefix. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) +- Removed `branch` and `b` query prefixes. Please use `rev:` instead. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) +- Removed `regex` query prefix. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623) + ### Fixed - Fixed spurious infinite loads with explore panel, file tree, and file search command. [#617](https://github.com/sourcebot-dev/sourcebot/pull/617) - Wipe search context on init if entitlement no longer exists [#618](https://github.com/sourcebot-dev/sourcebot/pull/618) - Fixed Bitbucket repository exclusions not supporting glob patterns. [#620](https://github.com/sourcebot-dev/sourcebot/pull/620) -- Fixed review agent so that it works with GHES instances [#611](https://github.com/sourcebot-dev/sourcebot/pull/611) +- Fixed issue where the repo driven permission syncer was attempting to sync public repositories. [#624](https://github.com/sourcebot-dev/sourcebot/pull/624) +- Fixed issue where worker would not shutdown while a permission sync job (repo or user) was in progress. [#624](https://github.com/sourcebot-dev/sourcebot/pull/624) ## [4.9.2] - 2025-11-13 diff --git a/Dockerfile b/Dockerfile index 41c67712..9a75ade0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 # ------ Global scope variables ------ # Set of global build arguments. @@ -8,11 +9,6 @@ # @see: https://docs.docker.com/build/building/variables/#scoping 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_SOURCEBOT_CLOUD_ENVIRONMENT ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN @@ -43,10 +39,12 @@ COPY .yarn ./.yarn COPY ./packages/db ./packages/db COPY ./packages/schemas ./packages/schemas COPY ./packages/shared ./packages/shared +COPY ./packages/queryLanguage ./packages/queryLanguage RUN yarn workspace @sourcebot/db install RUN yarn workspace @sourcebot/schemas install RUN yarn workspace @sourcebot/shared install +RUN yarn workspace @sourcebot/query-language install # ------------------------------------ # ------ Build Web ------ @@ -55,8 +53,6 @@ ENV SKIP_ENV_VALIDATION=1 # ----------- ARG 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 ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT @@ -92,6 +88,7 @@ COPY --from=shared-libs-builder /app/node_modules ./node_modules COPY --from=shared-libs-builder /app/packages/db ./packages/db COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas COPY --from=shared-libs-builder /app/packages/shared ./packages/shared +COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLanguage # Fixes arm64 timeouts RUN yarn workspace @sourcebot/web install @@ -130,6 +127,7 @@ COPY --from=shared-libs-builder /app/node_modules ./node_modules COPY --from=shared-libs-builder /app/packages/db ./packages/db COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas COPY --from=shared-libs-builder /app/packages/shared ./packages/shared +COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLanguage RUN yarn workspace @sourcebot/backend install RUN yarn workspace @sourcebot/backend build @@ -144,14 +142,12 @@ fi ENV SKIP_ENV_VALIDATION=0 # ------------------------------ - + # ------ Runner ------ FROM node-alpine AS runner # ----------- ARG 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 ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN @@ -173,8 +169,13 @@ ENV DATA_DIR=/data ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis -ENV SRC_TENANT_ENFORCEMENT_MODE=strict 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 ENV SOURCEBOT_LOG_LEVEL=info @@ -217,18 +218,23 @@ COPY --from=zoekt-builder \ /cmd/zoekt-index \ /usr/local/bin/ +RUN chown -R sourcebot:sourcebot /app + +# Copy zoekt proto files (needed for gRPC client at runtime) +COPY --chown=sourcebot:sourcebot vendor/zoekt/grpc/protos /app/vendor/zoekt/grpc/protos + # Copy all of the things -COPY --from=web-builder /app/packages/web/public ./packages/web/public -COPY --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/public ./packages/web/public +COPY --chown=sourcebot:sourcebot --from=web-builder /app/packages/web/.next/standalone ./ +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 --from=backend-builder /app/packages/backend ./packages/backend +COPY --chown=sourcebot:sourcebot --from=backend-builder /app/node_modules ./node_modules +COPY --chown=sourcebot:sourcebot --from=backend-builder /app/packages/backend ./packages/backend -COPY --from=shared-libs-builder /app/node_modules ./node_modules -COPY --from=shared-libs-builder /app/packages/db ./packages/db -COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas -COPY --from=shared-libs-builder /app/packages/shared ./packages/shared +COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/db ./packages/db +COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/schemas ./packages/schemas +COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/shared ./packages/shared +COPY --chown=sourcebot:sourcebot --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. RUN git config --global safe.directory "*" @@ -238,9 +244,6 @@ RUN mkdir -p /run/postgresql && \ chown -R postgres:postgres /run/postgresql && \ chmod 775 /run/postgresql -# Make app directory accessible to both root and sourcebot user -RUN chown -R sourcebot:sourcebot /app -# Make data directory accessible to both root and sourcebot user RUN chown -R sourcebot:sourcebot /data COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf diff --git a/docs/docs.json b/docs/docs.json index fccfe7f1..4237169e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -144,7 +144,7 @@ "socials": { "github": "https://github.com/sourcebot-dev/sourcebot", "twitter": "https://x.com/sourcebot_dev", - "discord": "https://discord.gg/GbXMEM5H", + "discord": "https://discord.gg/HDScTs3ptP", "linkedin": "https://www.linkedin.com/company/sourcebot" } }, diff --git a/docs/docs/configuration/auth/overview.mdx b/docs/docs/configuration/auth/overview.mdx index a71de5fe..117eed4f 100644 --- a/docs/docs/configuration/auth/overview.mdx +++ b/docs/docs/configuration/auth/overview.mdx @@ -25,4 +25,4 @@ Sourcebot's built-in authentication system gates your deployment, and allows adm # 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). -- 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) \ No newline at end of file +- 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) \ No newline at end of file diff --git a/docs/docs/configuration/environment-variables.mdx b/docs/docs/configuration/environment-variables.mdx index 87167858..e29fb88f 100644 --- a/docs/docs/configuration/environment-variables.mdx +++ b/docs/docs/configuration/environment-variables.mdx @@ -35,6 +35,7 @@ The following environment variables allow you to configure your Sourcebot deploy | `SOURCEBOT_STRUCTURED_LOGGING_FILE` | - |
Optional file to log to if structured logging is enabled
| | `SOURCEBOT_TELEMETRY_DISABLED` | `false` |Enables/disables telemetry collection in Sourcebot. See [this doc](/docs/overview.mdx#telemetry) for more info.
| | `DEFAULT_MAX_MATCH_COUNT` | `10000` |The default maximum number of search results to return when using search in the web app.
| +| `ALWAYS_INDEX_FILE_PATTERNS` | - |A comma separated list of glob patterns matching file paths that should always be indexed, regardless of size or number of trigrams.
| ### Enterprise Environment Variables | Variable | Default | Description | diff --git a/docs/docs/configuration/idp.mdx b/docs/docs/configuration/idp.mdx index 21ae756d..6d2475b5 100644 --- a/docs/docs/configuration/idp.mdx +++ b/docs/docs/configuration/idp.mdx @@ -366,3 +366,53 @@ A Microsoft Entra ID connection can be used for [authentication](/docs/configura +### Authentik + +[Auth.js Authentik Provider Docs](https://authjs.dev/getting-started/providers/authentik) + +An Authentik connection can be used for [authentication](/docs/configuration/auth). + +Sign in @@ -163,7 +162,7 @@ export const ChatSidePanel = ({ chat.id === chatId && "bg-muted" )} onClick={() => { - router.push(`/${domain}/chat/${chat.id}`); + router.push(`/${SINGLE_TENANT_ORG_DOMAIN}/chat/${chat.id}`); }} > {chat.name ?? 'Untitled chat'} diff --git a/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx b/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx index bb5912ea..f72e6485 100644 --- a/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx +++ b/packages/web/src/app/[domain]/components/lightweightCodeHighlighter.tsx @@ -6,7 +6,7 @@ import { memo, useEffect, useMemo, useState } from 'react' import { useCodeMirrorHighlighter } from '@/hooks/useCodeMirrorHighlighter' import tailwind from '@/tailwind' import { measure } from '@/lib/utils' -import { SourceRange } from '@/features/search/types' +import { SourceRange } from '@/features/search' // Define a plain text language const plainTextLanguage = StreamLanguage.define({ diff --git a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx index 34fb014b..0b089886 100644 --- a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx +++ b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx @@ -1,4 +1,4 @@ -import { getConnectionStats, getRepos, getReposStats } from "@/actions"; +import { getConnectionStats, getCurrentUserRole, getOrgAccountRequests, getRepos, getReposStats } from "@/actions"; import { SourcebotLogo } from "@/app/components/sourcebotLogo"; import { auth } from "@/auth"; import { Button } from "@/components/ui/button"; @@ -10,7 +10,7 @@ import { env } from "@sourcebot/shared"; import { ServiceErrorException } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { DiscordLogoIcon, GitHubLogoIcon } from "@radix-ui/react-icons"; -import { RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db"; +import { OrgRole, RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db"; import Link from "next/link"; import { redirect } from "next/navigation"; import { OrgSelector } from "../orgSelector"; @@ -20,7 +20,7 @@ import { NavigationItems } from "./navigationItems"; import { ProgressIndicator } from "./progressIndicator"; 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"; interface NavigationMenuProps { @@ -39,11 +39,32 @@ export const NavigationMenu = async ({ throw new ServiceErrorException(repoStats); } - const connectionStats = isAuthenticated ? await getConnectionStats() : null; - if (isServiceError(connectionStats)) { - throw new ServiceErrorException(connectionStats); + const role = isAuthenticated ? await getCurrentUserRole(domain) : null; + if (isServiceError(role)) { + throw new ServiceErrorException(role); } + const stats = await (async () => { + if (!isAuthenticated || role !== OrgRole.OWNER) { + return null; + } + + const joinRequests = await getOrgAccountRequests(domain); + if (isServiceError(joinRequests)) { + throw new ServiceErrorException(joinRequests); + } + + const connectionStats = await getConnectionStats(); + if (isServiceError(connectionStats)) { + throw new ServiceErrorException(connectionStats); + } + + return { + numJoinRequests: joinRequests.length, + connectionStats, + }; + })(); + const sampleRepos = await getRepos({ where: { jobs: { @@ -100,9 +121,10 @@ export const NavigationMenu = async ({ numberOfRepos={numberOfRepos} isReposButtonNotificationDotVisible={numberOfReposWithFirstTimeIndexingJobsInProgress > 0} isSettingsButtonNotificationDotVisible={ - connectionStats ? - connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0 : - false + stats ? ( + stats.connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0 || + stats.numJoinRequests > 0 + ) : false } isAuthenticated={isAuthenticated} /> diff --git a/packages/web/src/app/[domain]/components/pathHeader.tsx b/packages/web/src/app/[domain]/components/pathHeader.tsx index d65d2c35..7d373b2e 100644 --- a/packages/web/src/app/[domain]/components/pathHeader.tsx +++ b/packages/web/src/app/[domain]/components/pathHeader.tsx @@ -233,7 +233,7 @@ export const PathHeader = ({ }} > @ - {`${branchDisplayName}`} + {`${branchDisplayName.replace(/^refs\/(heads|tags)\//, '')}`}
)} · diff --git a/packages/web/src/app/[domain]/components/searchBar/constants.ts b/packages/web/src/app/[domain]/components/searchBar/constants.ts index c637bee9..ea93cee8 100644 --- a/packages/web/src/app/[domain]/components/searchBar/constants.ts +++ b/packages/web/src/app/[domain]/components/searchBar/constants.ts @@ -16,57 +16,53 @@ export enum SearchPrefix { sym = "sym:", content = "content:", archived = "archived:", - case = "case:", fork = "fork:", - public = "public:", + visibility = "visibility:", context = "context:", } -export const publicModeSuggestions: Suggestion[] = [ +export const visibilityModeSuggestions: Suggestion[] = [ { - value: "yes", + value: "public", description: "Only include results from public repositories." }, { - value: "no", + value: "private", description: "Only include results from private repositories." }, + { + value: "any", + description: "Include results from both public and private repositories (default)." + }, ]; export const forkModeSuggestions: Suggestion[] = [ { value: "yes", + description: "Include results from forked repositories (default)." + }, + { + value: "no", + description: "Exclude results from forked repositories." + }, + { + value: "only", description: "Only include results from forked repositories." - }, - { - value: "no", - description: "Only include results from non-forked repositories." - }, -]; - -export const caseModeSuggestions: Suggestion[] = [ - { - value: "auto", - description: "Search patterns are case-insensitive if all characters are lowercase, and case sensitive otherwise (default)." - }, - { - value: "yes", - description: "Case sensitive search." - }, - { - value: "no", - description: "Case insensitive search." - }, + } ]; export const archivedModeSuggestions: Suggestion[] = [ { value: "yes", - description: "Only include results in archived repositories." + description: "Include results from archived repositories (default)." }, { value: "no", - description: "Only include results in non-archived repositories." + description: "Exclude results from archived repositories." }, + { + value: "only", + description: "Only include results from archived repositories." + } ]; diff --git a/packages/web/src/app/[domain]/components/searchBar/searchBar.tsx b/packages/web/src/app/[domain]/components/searchBar/searchBar.tsx index 6dbd47cd..dda7ab2a 100644 --- a/packages/web/src/app/[domain]/components/searchBar/searchBar.tsx +++ b/packages/web/src/app/[domain]/components/searchBar/searchBar.tsx @@ -42,14 +42,18 @@ import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; import { Toggle } from "@/components/ui/toggle"; import { useDomain } from "@/hooks/useDomain"; -import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint"; import { createAuditAction } from "@/ee/features/audit/actions"; import tailwind from "@/tailwind"; +import { CaseSensitiveIcon, RegexIcon } from "lucide-react"; interface SearchBarProps { className?: string; size?: "default" | "sm"; - defaultQuery?: string; + defaults?: { + isRegexEnabled?: boolean; + isCaseSensitivityEnabled?: boolean; + query?: string; + } autoFocus?: boolean; } @@ -91,8 +95,12 @@ const searchBarContainerVariants = cva( export const SearchBar = ({ className, size, - defaultQuery, autoFocus, + defaults: { + isRegexEnabled: defaultIsRegexEnabled = false, + isCaseSensitivityEnabled: defaultIsCaseSensitivityEnabled = false, + query: defaultQuery = "", + } = {} }: SearchBarProps) => { const router = useRouter(); const domain = useDomain(); @@ -102,11 +110,13 @@ export const SearchBar = ({ const [isSuggestionsEnabled, setIsSuggestionsEnabled] = useState(false); const [isSuggestionsBoxFocused, setIsSuggestionsBoxFocused] = useState(false); const [isHistorySearchEnabled, setIsHistorySearchEnabled] = useState(false); + const [isRegexEnabled, setIsRegexEnabled] = useState(defaultIsRegexEnabled); + const [isCaseSensitivityEnabled, setIsCaseSensitivityEnabled] = useState(defaultIsCaseSensitivityEnabled); const focusEditor = useCallback(() => editorRef.current?.view?.focus(), []); const focusSuggestionsBox = useCallback(() => suggestionBoxRef.current?.focus(), []); - const [_query, setQuery] = useState(defaultQuery ?? ""); + const [_query, setQuery] = useState(defaultQuery); const query = useMemo(() => { // Replace any newlines with spaces to handle // copy & pasting text with newlines. @@ -211,13 +221,15 @@ export const SearchBar = ({ metadata: { message: query, }, - }, domain) + }) const url = createPathWithQueryParams(`/${domain}/search`, [SearchQueryParams.query, query], + [SearchQueryParams.isRegexEnabled, isRegexEnabled ? "true" : null], + [SearchQueryParams.isCaseSensitivityEnabled, isCaseSensitivityEnabled ? "true" : null], ); router.push(url); - }, [domain, router]); + }, [domain, router, isRegexEnabled, isCaseSensitivityEnabled]); return ( -
- {`Multiple expressions can be or'd together with `}
+ Keyword search matches search patterns exactly in file contents. Wrapping search patterns in
- Expressions can be prefixed with certain keywords to modify search behavior. Some keywords can be negated using the
+ Search queries (keyword or regex) can include multiple search filters to further refine the search results. Some filters can be negated using the
+ By default, space-seperated expressions are and'd together. Using the
Searching...
-Failed to search
-{error.message}
+{error instanceof ServiceErrorException ? error.serviceError.message : error.message}
Search stats for nerds
-{`[${searchDurationMs} ms] Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}
- ) : ( -No results
- ) - } - {isMoreResultsButtonVisible && ( -Searching...
+ {numMatches > 0 && ( +{`Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}
+ )} + > + ) : ( + <> +Search stats for nerds
+{`[${searchDurationMs} ms] Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}
+ ) : ( +No results
+ ) + } + {isMoreResultsButtonVisible && ( +Searching...
+No results found
diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatch.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatch.tsx index d6e6b8ab..33433392 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatch.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatch.tsx @@ -1,6 +1,6 @@ 'use client'; -import { SearchResultFile, SearchResultChunk } from "@/features/search/types"; +import { SearchResultFile, SearchResultChunk } from "@/features/search"; import { LightweightCodeHighlighter } from "@/app/[domain]/components/lightweightCodeHighlighter"; import Link from "next/link"; import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils"; diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx index b10d656a..2779b301 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx @@ -5,7 +5,7 @@ import { Separator } from "@/components/ui/separator"; import { DoubleArrowDownIcon, DoubleArrowUpIcon } from "@radix-ui/react-icons"; import { useMemo } from "react"; import { FileMatch } from "./fileMatch"; -import { RepositoryInfo, SearchResultFile } from "@/features/search/types"; +import { RepositoryInfo, SearchResultFile } from "@/features/search"; import { Button } from "@/components/ui/button"; export const MAX_MATCHES_TO_PREVIEW = 3; @@ -75,7 +75,7 @@ export const FileMatchContainer = ({ } return `${branches[0]}${branches.length > 1 ? ` +${branches.length - 1}` : ''}`; - }, [isBranchFilteringEnabled, branches]); + }, [branches, isBranchFilteringEnabled]); const repo = useMemo(() => { return repoInfo[file.repositoryId]; diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx index 61e41332..b2ee8e9e 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx @@ -1,10 +1,11 @@ 'use client'; -import { RepositoryInfo, SearchResultFile } from "@/features/search/types"; -import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer"; +import { RepositoryInfo, SearchResultFile } from "@/features/search"; import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { useDebounce, usePrevious } from "@uidotdev/usehooks"; +import { useDebounce } from "@uidotdev/usehooks"; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from "react"; +import { useMap } from "usehooks-ts"; +import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer"; interface SearchResultsPanelProps { fileMatches: SearchResultFile[]; @@ -15,6 +16,10 @@ interface SearchResultsPanelProps { repoInfo: RecordNo hover info found
@@ -160,13 +271,13 @@ export const SymbolHoverPopup: React.FC{props.children}
} diff --git a/packages/web/src/features/chat/components/chatBox/useSuggestionsData.ts b/packages/web/src/features/chat/components/chatBox/useSuggestionsData.ts index 4adf9694..d41367d5 100644 --- a/packages/web/src/features/chat/components/chatBox/useSuggestionsData.ts +++ b/packages/web/src/features/chat/components/chatBox/useSuggestionsData.ts @@ -32,15 +32,22 @@ export const useSuggestionsData = ({ const { data: fileSuggestions, isLoading: _isLoadingFileSuggestions } = useQuery({ queryKey: ["fileSuggestions-agentic", suggestionQuery, domain, selectedRepos], queryFn: () => { - let query = `file:${suggestionQuery}`; + const query = []; + if (suggestionQuery.length > 0) { + query.push(`file:${suggestionQuery}`); + } else { + query.push('file:.*'); + } + if (selectedRepos.length > 0) { - query += ` reposet:${selectedRepos.join(',')}`; + query.push(`reposet:${selectedRepos.join(',')}`); } return unwrapServiceError(search({ - query, + query: query.join(' '), matches: 10, contextLines: 1, + source: 'chat-file-suggestions' })) }, select: (data): FileSuggestion[] => { diff --git a/packages/web/src/features/chat/components/chatThread/answerCard.tsx b/packages/web/src/features/chat/components/chatThread/answerCard.tsx index e3f89f62..d420ce5e 100644 --- a/packages/web/src/features/chat/components/chatThread/answerCard.tsx +++ b/packages/web/src/features/chat/components/chatThread/answerCard.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"; import { TableOfContentsIcon, ThumbsDown, ThumbsUp } from "lucide-react"; import { Separator } from "@/components/ui/separator"; 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 { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { CopyIconButton } from "@/app/[domain]/components/copyIconButton"; @@ -14,10 +14,10 @@ import { useToast } from "@/components/hooks/use-toast"; import { convertLLMOutputToPortableMarkdown } from "../../utils"; import { submitFeedback } from "../../actions"; import { isServiceError } from "@/lib/utils"; -import { useDomain } from "@/hooks/useDomain"; import useCaptureEvent from "@/hooks/useCaptureEvent"; import { LangfuseWeb } from "langfuse"; import { env } from "@sourcebot/shared/client"; +import isEqual from "fast-deep-equal/react"; interface AnswerCardProps { answerText: string; @@ -31,7 +31,7 @@ const langfuseWeb = (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined & baseUrl: env.NEXT_PUBLIC_LANGFUSE_BASE_URL, }) : null; -export const AnswerCard = forwardRef