diff --git a/CHANGELOG.md b/CHANGELOG.md index 33963c52..2569756b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add ability to include/exclude connection in search context. [#399](https://github.com/sourcebot-dev/sourcebot/pull/399) - Search context refactor to search scope and demo card UI changes. [#405](https://github.com/sourcebot-dev/sourcebot/pull/405) - Add GitHub star toast. [#409](https://github.com/sourcebot-dev/sourcebot/pull/409) +- Added a onboarding modal when first visiting the homepage when `ask` mode is selected. [#408](https://github.com/sourcebot-dev/sourcebot/pull/408) ### Fixed - Fixed multiple writes race condition on config file watcher. [#398](https://github.com/sourcebot-dev/sourcebot/pull/398) diff --git a/packages/web/public/ask_sb_tutorial_at_mentions.png b/packages/web/public/ask_sb_tutorial_at_mentions.png new file mode 100644 index 00000000..412b02fe Binary files /dev/null and b/packages/web/public/ask_sb_tutorial_at_mentions.png differ diff --git a/packages/web/public/ask_sb_tutorial_citations.png b/packages/web/public/ask_sb_tutorial_citations.png new file mode 100644 index 00000000..2266fdb5 Binary files /dev/null and b/packages/web/public/ask_sb_tutorial_citations.png differ diff --git a/packages/web/public/ask_sb_tutorial_search_scope.png b/packages/web/public/ask_sb_tutorial_search_scope.png new file mode 100644 index 00000000..33376ca8 Binary files /dev/null and b/packages/web/public/ask_sb_tutorial_search_scope.png differ diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts index 430c003b..117d2a2e 100644 --- a/packages/web/src/actions.ts +++ b/packages/web/src/actions.ts @@ -25,7 +25,7 @@ import { auth } from "./auth"; import { getConnection } from "./data/connection"; import { IS_BILLING_ENABLED } from "./ee/features/billing/stripe"; import InviteUserEmail from "./emails/inviteUserEmail"; -import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SEARCH_MODE_COOKIE_NAME, SINGLE_TENANT_ORG_DOMAIN, SOURCEBOT_GUEST_USER_ID, SOURCEBOT_SUPPORT_EMAIL } from "./lib/constants"; +import { AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME, MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SEARCH_MODE_COOKIE_NAME, SINGLE_TENANT_ORG_DOMAIN, SOURCEBOT_GUEST_USER_ID, SOURCEBOT_SUPPORT_EMAIL } from "./lib/constants"; import { orgDomainSchema, orgNameSchema, repositoryQuerySchema } from "./lib/schemas"; import { TenancyMode, ApiKeyPayload } from "./lib/types"; import { decrementOrgSeatCount, getSubscriptionForOrg } from "./ee/features/billing/serverUtils"; @@ -2015,6 +2015,13 @@ export async function setSearchModeCookie(searchMode: "precise" | "agentic") { }); } +export async function setAgenticSearchTutorialDismissedCookie(dismissed: boolean) { + const cookieStore = await cookies(); + cookieStore.set(AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME, dismissed ? "true" : "false", { + httpOnly: false, // Allow client-side access + }); +} + ////// Helpers /////// const parseConnectionConfig = (config: string) => { diff --git a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx index 259c738b..76b2907d 100644 --- a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx +++ b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx @@ -6,11 +6,13 @@ import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolba import { LanguageModelInfo, SearchScope } from "@/features/chat/types"; import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread"; import { RepositoryQuery, SearchContextQuery } from "@/lib/types"; -import { useState } from "react"; +import { useCallback, useState } from "react"; import { SearchModeSelector, SearchModeSelectorProps } from "./toolbar"; import { useLocalStorage } from "usehooks-ts"; import { DemoExamples } from "@/types"; import { AskSourcebotDemoCards } from "./askSourcebotDemoCards"; +import { AgenticSearchTutorialDialog } from "./agenticSearchTutorialDialog"; +import { setAgenticSearchTutorialDismissedCookie } from "@/actions"; interface AgenticSearchProps { searchModeSelectorProps: SearchModeSelectorProps; @@ -23,6 +25,7 @@ interface AgenticSearchProps { name: string | null; }[]; demoExamples: DemoExamples | undefined; + isTutorialDismissed: boolean; } export const AgenticSearch = ({ @@ -31,11 +34,18 @@ export const AgenticSearch = ({ repos, searchContexts, demoExamples, + isTutorialDismissed, }: AgenticSearchProps) => { const { createNewChatThread, isLoading } = useCreateNewChatThread(); const [selectedSearchScopes, setSelectedSearchScopes] = useLocalStorage("selectedSearchScopes", [], { initializeWithValue: false }); const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false); + const [isTutorialOpen, setIsTutorialOpen] = useState(!isTutorialDismissed); + const onTutorialDismissed = useCallback(() => { + setIsTutorialOpen(false); + setAgenticSearchTutorialDismissedCookie(true); + }, []); + return (
@@ -75,6 +85,12 @@ export const AgenticSearch = ({ demoExamples={demoExamples} /> )} + + {isTutorialOpen && ( + + )}
) } \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/homepage/agenticSearchTutorialDialog.tsx b/packages/web/src/app/[domain]/components/homepage/agenticSearchTutorialDialog.tsx new file mode 100644 index 00000000..412d79fd --- /dev/null +++ b/packages/web/src/app/[domain]/components/homepage/agenticSearchTutorialDialog.tsx @@ -0,0 +1,339 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { Dialog, DialogContent } from "@/components/ui/dialog" +import { ModelProviderLogo } from "@/features/chat/components/chatBox/modelProviderLogo" +import { cn } from "@/lib/utils" +import mentionsDemo from "@/public/ask_sb_tutorial_at_mentions.png" +import citationsDemo from "@/public/ask_sb_tutorial_citations.png" +import searchScopeDemo from "@/public/ask_sb_tutorial_search_scope.png" +import logoDarkSmall from "@/public/sb_logo_dark_small.png" +import { useQuery } from "@tanstack/react-query" +import { + ArrowLeftRightIcon, + AtSignIcon, + BookMarkedIcon, + BookTextIcon, + ChevronLeft, + ChevronRight, + CircleCheckIcon, + FileIcon, + FolderIcon, + GitCommitHorizontalIcon, + LibraryBigIcon, + ScanSearchIcon, + StarIcon, + TicketIcon, +} from "lucide-react" +import Image from "next/image" +import Link from "next/link" +import { useState } from "react" + +interface AgenticSearchTutorialDialogProps { + onClose: () => void +} + + +// Star button component that fetches GitHub star count +const GitHubStarButton = () => { + const { data: starCount, isLoading, isError } = useQuery({ + queryKey: ['github-stars', 'sourcebot-dev/sourcebot'], + queryFn: async () => { + const response = await fetch('https://api.github.com/repos/sourcebot-dev/sourcebot') + if (!response.ok) { + throw new Error('Failed to fetch star count') + } + const data = await response.json() + return data.stargazers_count as number; + }, + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 30 * 60 * 1000, // 30 minutes + retry: 3, + retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000), + }) + + const formatStarCount = (count: number) => { + if (count >= 1000) { + return `${(count / 1000).toFixed(1)}k` + } + return count.toString() + } + + return ( + + ) +} + + +const tutorialSteps = [ + { + leftContent: ( +
+
+

+ Ask Sourcebot. +

+

+ Ask questions about your entire codebase in natural language. + Get back responses grounded in code with inline citations. +

+

+ Ask Sourcebot is an agentic search tool that can answer questions about your codebase by searching, reading files, navigating references, and more. Supports any compatible LLM. +

+
+
+
+ + + + + + + + + +
+
+
+ ), + rightContent: ( +
diff --git a/packages/web/src/components/ui/dialog.tsx b/packages/web/src/components/ui/dialog.tsx index 4d013a60..de2b9d97 100644 --- a/packages/web/src/components/ui/dialog.tsx +++ b/packages/web/src/components/ui/dialog.tsx @@ -31,8 +31,10 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + closeButtonClassName?: string + } +>(({ className, children, closeButtonClassName, ...props }, ref) => ( {children} - + Close diff --git a/packages/web/src/components/atMentionInfoCard.tsx b/packages/web/src/features/chat/components/chatBox/atMentionInfoCard.tsx similarity index 100% rename from packages/web/src/components/atMentionInfoCard.tsx rename to packages/web/src/features/chat/components/chatBox/atMentionInfoCard.tsx diff --git a/packages/web/src/features/chat/components/chatBox/chatBox.tsx b/packages/web/src/features/chat/components/chatBox/chatBox.tsx index f5cfc974..934199f2 100644 --- a/packages/web/src/features/chat/components/chatBox/chatBox.tsx +++ b/packages/web/src/features/chat/components/chatBox/chatBox.tsx @@ -283,7 +283,7 @@ export const ChatBox = ({ > { return ( @@ -10,24 +8,11 @@ export const SearchScopeInfoCard = () => {

Search Scope

- When asking Sourcebot a question, you can select one or more scopes to constrain the search. + When asking Sourcebot a question, you can select one or more scopes to focus the search. There are two different types of search scopes:
- {(() => { - const githubIcon = getCodeHostIcon("github"); - return githubIcon ? ( - GitHub icon - ) : ( - - ); - })()} + Repository: A single repository, indicated by the code host icon.
diff --git a/packages/web/src/lib/constants.ts b/packages/web/src/lib/constants.ts index 4860f473..e74e7d51 100644 --- a/packages/web/src/lib/constants.ts +++ b/packages/web/src/lib/constants.ts @@ -24,6 +24,7 @@ export const TEAM_FEATURES = [ export const MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME = 'sb.mobile-unsupported-splash-screen-dismissed'; export const SEARCH_MODE_COOKIE_NAME = 'sb.search-mode'; +export const AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME = 'sb.agentic-search-tutorial-dismissed'; // NOTE: changing SOURCEBOT_GUEST_USER_ID may break backwards compatibility since this value is used // to detect old guest users in the DB. If you change this value ensure it doesn't break upgrade flows