diff --git a/CHANGELOG.md b/CHANGELOG.md index 429b650f3..e3fe4ebdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Enabled additional search scope selection in the Ask GitHub view, allowing users to include other repositories and search contexts alongside the current repository. [#1436](https://github.com/sourcebot-dev/sourcebot/pull/1436) + ## [5.1.0] - 2026-07-10 ### Changed diff --git a/packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx b/packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx index 956f958f7..9cb7ab6a6 100644 --- a/packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx +++ b/packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx @@ -7,13 +7,16 @@ import { ChatBox, ChatBoxHandle } from "@/features/chat/components/chatBox"; import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar"; import { ChatPaneDropzone } from "@/features/chat/components/chatBox/chatPaneDropzone"; import { NotConfiguredErrorBanner } from "@/features/chat/components/notConfiguredErrorBanner"; -import { LanguageModelInfo, RepoSearchScope } from "@/features/chat/types"; +import { LanguageModelInfo, RepoSearchScope, SearchScope } from "@/features/chat/types"; import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread"; import { DISABLED_MCP_SERVER_IDS_LOCAL_STORAGE_KEY } from "@/features/chat/constants"; import { getRepoImageSrc } from '@/lib/utils'; -import { useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useLocalStorage } from "usehooks-ts"; import type { AskCommandDefinition } from '@/features/chat/commands/types'; +import { RepositoryQuery, SearchContextQuery } from "@/lib/types"; + +const ASKGH_SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY = 'askGhSelectedSearchScopes'; interface LandingPageProps { languageModels: LanguageModelInfo[]; @@ -24,6 +27,8 @@ interface LandingPageProps { askCommands: AskCommandDefinition[]; isAuthenticated: boolean; maxImageBytes: number; + repos: RepositoryQuery[]; + searchContexts: SearchContextQuery[]; } export const LandingPage = ({ @@ -35,6 +40,8 @@ export const LandingPage = ({ askCommands, isAuthenticated, maxImageBytes, + repos, + searchContexts, }: LandingPageProps) => { const { createNewChatThread, isLoading } = useCreateNewChatThread(); const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false); @@ -42,14 +49,40 @@ export const LandingPage = ({ const chatBoxRef = useRef(null); const isChatBoxDisabled = languageModels.length === 0; - const selectedSearchScopes = useMemo(() => [ - { - type: 'repo', - name: repoDisplayName ?? repoName, - value: repoName, - codeHostType: 'github' as const, - } satisfies RepoSearchScope, - ], [repoDisplayName, repoName]); + // Default scope for the current repo + const defaultRepoScope = useMemo(() => ({ + type: 'repo' as const, + name: repoDisplayName ?? repoName, + value: repoName, + codeHostType: 'github' as const, + } satisfies RepoSearchScope), [repoDisplayName, repoName]); + + // Use local storage for selected scopes, with the current repo as default + const [selectedSearchScopes, setSelectedSearchScopes] = useLocalStorage( + ASKGH_SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY, + [defaultRepoScope], + { initializeWithValue: false } + ); + + // Ensure the current repo is always included in selected scopes when visiting this page + // This handles the case where the user visits a different repo's Ask GH page + const [hasInitialized, setHasInitialized] = useState(false); + useEffect(() => { + if (hasInitialized) { + return; + } + setHasInitialized(true); + + // Check if the current repo is already in the selected scopes + const currentRepoIncluded = selectedSearchScopes.some( + (scope) => scope.type === 'repo' && scope.value === repoName + ); + + // If not, add it to the scopes + if (!currentRepoIncluded) { + setSelectedSearchScopes([defaultRepoScope, ...selectedSearchScopes]); + } + }, [hasInitialized, selectedSearchScopes, repoName, defaultRepoScope, setSelectedSearchScopes]); const imageSrc = imageUrl ? getRepoImageSrc(imageUrl, repoId) : undefined; const displayName = repoDisplayName ?? repoName; @@ -88,7 +121,7 @@ export const LandingPage = ({ className="min-h-[50px]" isRedirecting={isLoading} selectedSearchScopes={selectedSearchScopes} - searchContexts={[]} + searchContexts={searchContexts} askCommands={askCommands} isDisabled={isChatBoxDisabled} isAuthenticated={isAuthenticated} @@ -100,10 +133,10 @@ export const LandingPage = ({
{ }} + onSelectedSearchScopesChange={setSelectedSearchScopes} isContextSelectorOpen={isContextSelectorOpen} onContextSelectorOpenChanged={setIsContextSelectorOpen} disabledMcpServerIds={disabledMcpServerIds} diff --git a/packages/web/src/app/(app)/askgh/[owner]/[repo]/page.tsx b/packages/web/src/app/(app)/askgh/[owner]/[repo]/page.tsx index 8d98f3624..3eecb0495 100644 --- a/packages/web/src/app/(app)/askgh/[owner]/[repo]/page.tsx +++ b/packages/web/src/app/(app)/askgh/[owner]/[repo]/page.tsx @@ -14,6 +14,7 @@ import { hasEntitlement } from "@/lib/entitlements"; import { ChatEntitlementMessage } from "@/features/chat/components/chatEntitlementMessage"; import { env } from "@sourcebot/shared"; import { listAgentSkillCommandsOrEmpty } from "@/ee/features/chat/skills/skillCommands.server"; +import { getRepos, getSearchContexts } from "@/actions"; interface PageProps { params: Promise<{ owner: string; repo: string }>; @@ -70,11 +71,21 @@ export default async function GitHubRepoPage(props: PageProps) { const askCommands = session?.user ? await listAgentSkillCommandsOrEmpty() : []; + const allRepos = await getRepos(); + const searchContexts = await getSearchContexts(); if (isServiceError(repoInfo)) { throw new ServiceErrorException(repoInfo); } + if (isServiceError(allRepos)) { + throw new ServiceErrorException(allRepos); + } + + if (isServiceError(searchContexts)) { + throw new ServiceErrorException(searchContexts); + } + return ( @@ -87,6 +98,8 @@ export default async function GitHubRepoPage(props: PageProps) { askCommands={askCommands} isAuthenticated={!!session?.user} maxImageBytes={env.SOURCEBOT_CHAT_ATTACHMENT_MAX_IMAGE_BYTES} + repos={allRepos} + searchContexts={searchContexts} />