Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -24,6 +27,8 @@ interface LandingPageProps {
askCommands: AskCommandDefinition[];
isAuthenticated: boolean;
maxImageBytes: number;
repos: RepositoryQuery[];
searchContexts: SearchContextQuery[];
}

export const LandingPage = ({
Expand All @@ -35,21 +40,49 @@ export const LandingPage = ({
askCommands,
isAuthenticated,
maxImageBytes,
repos,
searchContexts,
}: LandingPageProps) => {
const { createNewChatThread, isLoading } = useCreateNewChatThread();
const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false);
const [disabledMcpServerIds, setDisabledMcpServerIds] = useLocalStorage<string[]>(DISABLED_MCP_SERVER_IDS_LOCAL_STORAGE_KEY, [], { initializeWithValue: false });
const chatBoxRef = useRef<ChatBoxHandle>(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<SearchScope[]>(
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;
Expand Down Expand Up @@ -88,7 +121,7 @@ export const LandingPage = ({
className="min-h-[50px]"
isRedirecting={isLoading}
selectedSearchScopes={selectedSearchScopes}
searchContexts={[]}
searchContexts={searchContexts}
askCommands={askCommands}
isDisabled={isChatBoxDisabled}
isAuthenticated={isAuthenticated}
Expand All @@ -100,10 +133,10 @@ export const LandingPage = ({
<div className="w-full flex flex-row items-center bg-accent rounded-b-md px-2">
<ChatBoxToolbar
languageModels={languageModels}
repos={[]}
searchContexts={[]}
repos={repos}
searchContexts={searchContexts}
selectedSearchScopes={selectedSearchScopes}
onSelectedSearchScopesChange={() => { }}
onSelectedSearchScopesChange={setSelectedSearchScopes}
isContextSelectorOpen={isContextSelectorOpen}
onContextSelectorOpenChanged={setIsContextSelectorOpen}
disabledMcpServerIds={disabledMcpServerIds}
Expand Down
13 changes: 13 additions & 0 deletions packages/web/src/app/(app)/askgh/[owner]/[repo]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>;
Expand Down Expand Up @@ -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 (
<RepoIndexedGuard initialRepoInfo={repoInfo}>
<CustomSlateEditor>
Expand All @@ -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}
/>
</CustomSlateEditor>
</RepoIndexedGuard>
Expand Down
Loading