- {demoExamples.searchExamples
- .filter((example) => {
- if (selectedFilterSearchScope === null) return true;
- return example.searchScopes.includes(selectedFilterSearchScope);
- })
- .map((example) => {
- const searchScopes = demoExamples.searchScopes.filter((searchScope) => example.searchScopes.includes(searchScope.id))
- return (
-
handleExampleClick(example)}
- >
-
-
-
- {searchScopes.map((searchScope) => (
-
- {getSearchScopeIcon(searchScope, 12)}
- {searchScope.displayName}
-
- ))}
-
-
-
- {example.title}
-
-
- {example.description}
-
-
-
-
-
- )
- })}
+
+ {/* Example Searches Row */}
+
+
+
+
+
Community Ask Results
+
+ {/* Search Scope Filter */}
+
+
+
{
+ setSelectedFilterSearchScope(null);
+ }}
+ >
+ All
+
+ {demoExamples.searchScopes.map((searchScope) => (
+
{
+ setSelectedFilterSearchScope(searchScope.id);
+ }}
+ >
+ {getSearchScopeIcon(searchScope, 12, selectedFilterSearchScope === searchScope.id)}
+ {searchScope.displayName}
+
+ ))}
+
+
+
+ {demoExamples.searchExamples
+ .filter((example) => {
+ if (selectedFilterSearchScope === null) return true;
+ return example.searchScopes.includes(selectedFilterSearchScope);
+ })
+ .map((example) => {
+ const searchScopes = demoExamples.searchScopes.filter((searchScope) => example.searchScopes.includes(searchScope.id))
+ return (
+
handleExampleClick(example)}
+ >
+
+
+
+ {searchScopes.map((searchScope) => (
+
+ {getSearchScopeIcon(searchScope, 12)}
+ {searchScope.displayName}
+
+ ))}
+
+
+
+ {example.title}
+
+
+ {example.description}
+
+
+
+
+
+ )
+ })}
+
- >
+
);
};
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/components/homepage/repositorySnapshot.tsx b/packages/web/src/app/[domain]/components/homepage/repositorySnapshot.tsx
index c867fdd0..6248a659 100644
--- a/packages/web/src/app/[domain]/components/homepage/repositorySnapshot.tsx
+++ b/packages/web/src/app/[domain]/components/homepage/repositorySnapshot.tsx
@@ -16,6 +16,7 @@ import {
import { RepoIndexingStatus } from "@sourcebot/db";
import { SymbolIcon } from "@radix-ui/react-icons";
import { RepositoryQuery } from "@/lib/types";
+import { captureEvent } from "@/hooks/useCaptureEvent";
interface RepositorySnapshotProps {
repos: RepositoryQuery[];
@@ -68,15 +69,30 @@ export function RepositorySnapshot({
return (
- {`Search ${indexedRepos.length} `}
+ {`${indexedRepos.length} `}
{indexedRepos.length > 1 ? 'repositories' : 'repository'}
+ {` indexed`}
+ {process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === "demo" && (
+
+ Interested in using Sourcebot on your code? Check out our{' '}
+ captureEvent('wa_demo_docs_link_pressed', {})}
+ >
+ docs
+
+
+ )}
)
}
diff --git a/packages/web/src/app/onboard/page.tsx b/packages/web/src/app/onboard/page.tsx
index fbc746d9..5fd4a49b 100644
--- a/packages/web/src/app/onboard/page.tsx
+++ b/packages/web/src/app/onboard/page.tsx
@@ -14,7 +14,7 @@ import { prisma } from "@/prisma";
import { OrgRole } from "@sourcebot/db";
import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch";
import { redirect } from "next/navigation";
-import { BetweenHorizontalStart, GitBranchIcon, LockIcon } from "lucide-react";
+import { BetweenHorizontalStart, Brain, GitBranchIcon, LockIcon } from "lucide-react";
import { hasEntitlement } from "@sourcebot/shared";
import { env } from "@/env.mjs";
import { GcpIapAuth } from "@/app/[domain]/components/gcpIapAuth";
@@ -87,6 +87,13 @@ export default async function Onboarding({ searchParams }: OnboardingProps) {
href: "https://docs.sourcebot.dev/docs/connections/overview",
icon:
,
},
+ {
+ id: "language-models",
+ title: "Language Models",
+ description: "Learn how to configure your language model providers to start using Ask Sourcebot",
+ href: "https://docs.sourcebot.dev/docs/configuration/language-model-providers",
+ icon:
,
+ },
{
id: "authentication-system",
title: "Authentication System",
diff --git a/packages/web/src/features/chat/components/chatBox/chatBox.tsx b/packages/web/src/features/chat/components/chatBox/chatBox.tsx
index 5d539bcf..934199f2 100644
--- a/packages/web/src/features/chat/components/chatBox/chatBox.tsx
+++ b/packages/web/src/features/chat/components/chatBox/chatBox.tsx
@@ -5,10 +5,9 @@ import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { CustomEditor, LanguageModelInfo, MentionElement, RenderElementPropsFor, SearchScope } from "@/features/chat/types";
import { insertMention, slateContentToString } from "@/features/chat/utils";
-import { SearchContextQuery } from "@/lib/types";
import { cn, IS_MAC } from "@/lib/utils";
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
-import { ArrowUp, Loader2, StopCircleIcon } from "lucide-react";
+import { ArrowUp, Loader2, StopCircleIcon, TriangleAlertIcon } from "lucide-react";
import { Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { Descendant, insertText } from "slate";
@@ -18,6 +17,8 @@ import { SuggestionBox } from "./suggestionsBox";
import { Suggestion } from "./types";
import { useSuggestionModeAndQuery } from "./useSuggestionModeAndQuery";
import { useSuggestionsData } from "./useSuggestionsData";
+import { useToast } from "@/components/hooks/use-toast";
+import { SearchContextQuery } from "@/lib/types";
interface ChatBoxProps {
onSubmit: (children: Descendant[], editor: CustomEditor) => void;
@@ -29,6 +30,7 @@ interface ChatBoxProps {
languageModels: LanguageModelInfo[];
selectedSearchScopes: SearchScope[];
searchContexts: SearchContextQuery[];
+ onContextSelectorOpenChanged: (isOpen: boolean) => void;
}
export const ChatBox = ({
@@ -41,6 +43,7 @@ export const ChatBox = ({
languageModels,
selectedSearchScopes,
searchContexts,
+ onContextSelectorOpenChanged,
}: ChatBoxProps) => {
const suggestionsBoxRef = useRef
(null);
const [index, setIndex] = useState(0);
@@ -67,6 +70,7 @@ export const ChatBox = ({
const { selectedLanguageModel } = useSelectedLanguageModel({
initialLanguageModel: languageModels.length > 0 ? languageModels[0] : undefined,
});
+ const { toast } = useToast();
// Reset the index when the suggestion mode changes.
useEffect(() => {
@@ -97,9 +101,9 @@ export const ChatBox = ({
return
}, []);
- const { isSubmitDisabled } = useMemo((): {
+ const { isSubmitDisabled, isSubmitDisabledReason } = useMemo((): {
isSubmitDisabled: true,
- isSubmitDisabledReason: "empty" | "redirecting" | "generating" | "no-language-model-selected"
+ isSubmitDisabledReason: "empty" | "redirecting" | "generating" | "no-repos-selected" | "no-language-model-selected"
} | {
isSubmitDisabled: false,
isSubmitDisabledReason: undefined,
@@ -125,6 +129,13 @@ export const ChatBox = ({
}
}
+ if (selectedSearchScopes.length === 0) {
+ return {
+ isSubmitDisabled: true,
+ isSubmitDisabledReason: "no-repos-selected",
+ }
+ }
+
if (selectedLanguageModel === undefined) {
return {
@@ -138,11 +149,29 @@ export const ChatBox = ({
isSubmitDisabledReason: undefined,
}
- }, [editor.children, isRedirecting, isGenerating, selectedLanguageModel])
+ }, [
+ editor.children,
+ isRedirecting,
+ isGenerating,
+ selectedSearchScopes.length,
+ selectedLanguageModel,
+ ])
const onSubmit = useCallback(() => {
+ if (isSubmitDisabled) {
+ if (isSubmitDisabledReason === "no-repos-selected") {
+ toast({
+ description: "⚠️ You must select at least one search scope",
+ variant: "destructive",
+ });
+ onContextSelectorOpenChanged(true);
+ }
+
+ return;
+ }
+
_onSubmit(editor.children, editor);
- }, [_onSubmit, editor]);
+ }, [_onSubmit, editor, isSubmitDisabled, isSubmitDisabledReason, toast, onContextSelectorOpenChanged]);
const onInsertSuggestion = useCallback((suggestion: Suggestion) => {
switch (suggestion.type) {
@@ -281,15 +310,39 @@ export const ChatBox = ({
Stop
) : (
-
-
-
+
+
+ {
+ // @hack: When submission is disabled, we still want to issue
+ // a warning to the user as to why the submission is disabled.
+ // onSubmit on the Button will not be called because of the
+ // disabled prop, hence the call here.
+ if (isSubmitDisabled) {
+ onSubmit();
+ }
+ }}
+ >
+
+
+
+
+
+ {(isSubmitDisabled && isSubmitDisabledReason === "no-repos-selected") && (
+
+
+
+ You must select at least one search scope
+
+
+ )}
+
)}
{suggestionMode !== "none" && (
diff --git a/packages/web/src/features/chat/components/chatBox/searchScopeSelector.tsx b/packages/web/src/features/chat/components/chatBox/searchScopeSelector.tsx
index a1c8f595..63a44cef 100644
--- a/packages/web/src/features/chat/components/chatBox/searchScopeSelector.tsx
+++ b/packages/web/src/features/chat/components/chatBox/searchScopeSelector.tsx
@@ -57,6 +57,7 @@ export const SearchScopeSelector = React.forwardRef<
) => {
const scrollContainerRef = React.useRef