From a5562d794257e81b7d80d7fef0875cf771da9456 Mon Sep 17 00:00:00 2001
From: msukkari
Date: Mon, 28 Jul 2025 13:42:20 -0700
Subject: [PATCH] new demo card ui
---
.../homepage/askSourcebotDemoCards.tsx | 268 +++++++-----------
.../src/components/searchScopeInfoCard.tsx | 41 +++
.../components/chatBox/chatBoxToolbar.tsx | 5 +-
3 files changed, 140 insertions(+), 174 deletions(-)
create mode 100644 packages/web/src/components/searchScopeInfoCard.tsx
diff --git a/packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx b/packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx
index e8bb264c..743a1fc6 100644
--- a/packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx
+++ b/packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx
@@ -1,32 +1,25 @@
'use client';
+import { useState } from "react";
import Image from "next/image";
-import { Search, LibraryBigIcon, Code, Layers } from "lucide-react";
+import { Search, LibraryBigIcon, Code, Info } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import { CardContent } from "@/components/ui/card";
-import { ContextItem, RepoContextItem, SearchContextItem } from "@/features/chat/components/chatBox/contextSelector";
-import { DemoExamples, DemoSearchExample, DemoSearchContextExample, DemoSearchContext } from "@/types";
+import { DemoExamples, DemoSearchExample, DemoSearchContext } from "@/types";
import { cn, getCodeHostIcon } from "@/lib/utils";
-import { RepositoryQuery, SearchContextQuery } from "@/lib/types";
import useCaptureEvent from "@/hooks/useCaptureEvent";
+import { SearchScopeInfoCard } from "@/components/searchScopeInfoCard";
interface AskSourcebotDemoCardsProps {
demoExamples: DemoExamples;
- selectedItems: ContextItem[];
- setSelectedItems: (items: ContextItem[]) => void;
- searchContexts: SearchContextQuery[];
- repos: RepositoryQuery[];
}
export const AskSourcebotDemoCards = ({
demoExamples,
- selectedItems,
- setSelectedItems,
- searchContexts,
- repos,
}: AskSourcebotDemoCardsProps) => {
const captureEvent = useCaptureEvent();
+ const [selectedFilterContext, setSelectedFilterContext] = useState(null);
const handleExampleClick = (example: DemoSearchExample) => {
captureEvent('wa_demo_search_example_card_pressed', {
@@ -39,87 +32,37 @@ export const AskSourcebotDemoCards = ({
}
}
- const getContextIcon = (context: DemoSearchContext, size: number = 20) => {
+ const getContextIcon = (context: DemoSearchContext, size: number = 20, isSelected: boolean = false) => {
const sizeClass = size === 12 ? "h-3 w-3" : "h-5 w-5";
+ const colorClass = isSelected ? "text-primary-foreground" : "text-muted-foreground";
if (context.type === "set") {
- return ;
+ return ;
}
if (context.codeHostType) {
const codeHostIcon = getCodeHostIcon(context.codeHostType);
if (codeHostIcon) {
+ // When selected, icons need to match the inverted badge colors
+ // In light mode selected: light icon on dark bg (invert)
+ // In dark mode selected: dark icon on light bg (no invert, override dark:invert)
+ const selectedIconClass = isSelected
+ ? "invert dark:invert-0"
+ : codeHostIcon.className;
+
return (
);
}
}
- return ;
- }
-
- const handleContextClick = (demoSearchContexts: DemoSearchContext[], contextExample: DemoSearchContextExample) => {
- const context = demoSearchContexts.find((context) => context.id === contextExample.searchContext)
- if (!context) {
- console.error(`Search context ${contextExample.searchContext} not found on handleContextClick`);
- return;
- }
-
- captureEvent('wa_demo_search_context_card_pressed', {
- contextType: context.type,
- contextName: context.value,
- contextDisplayName: context.displayName,
- });
-
- const isDemoMode = process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === "demo";
- const isSelected = selectedItems.some((item) => item.value === context.value);
- if (isSelected) {
- setSelectedItems(selectedItems.filter((item) => item.value !== context.value));
- return;
- }
-
- const getNewSelectedItem = (): ContextItem | null => {
- if (context.type === "set") {
- const searchContext = searchContexts.find((item) => item.name === context.value);
- if (!searchContext) {
- console.error(`Search context ${context.value} not found on handleContextClick`);
- return null;
- }
-
- return {
- type: 'context',
- value: context.value,
- name: context.displayName,
- repoCount: searchContext.repoNames.length
- } as SearchContextItem;
- } else {
- const repo = repos.find((repo) => repo.repoName === context.value);
- if (!repo) {
- console.error(`Repo ${context.value} not found on handleContextClick`);
- return null;
- }
-
- return {
- type: 'repo',
- value: context.value,
- name: context.displayName,
- codeHostType: repo.codeHostType
- } as RepoContextItem;
- }
- }
-
- const newSelectedItem = getNewSelectedItem();
- if (newSelectedItem) {
- setSelectedItems(isDemoMode ? [newSelectedItem] : [...selectedItems, newSelectedItem]);
- } else {
- console.error(`No new selected item found on handleContextClick`);
- }
+ return ;
}
return (
@@ -139,110 +82,91 @@ export const AskSourcebotDemoCards = ({
)}
-
- {/* Search Context Row */}
-
-
-
-
-
Search Contexts
-
-
Select the context you want to ask questions about
-
-
- {demoExamples.searchContextExamples.map((contextExample) => {
- const context = demoExamples.searchContexts.find((context) => context.id === contextExample.searchContext)
- if (!context) {
- console.error(`Search context ${contextExample.searchContext} not found on handleContextClick`);
- return null;
- }
-
- const isSelected = selectedItems.some(
- (selected) => (selected.type === 'context' && selected.value === context.value) ||
- (selected.type === 'repo' && selected.value === context.value)
- );
-
- const searchContext = searchContexts.find((item) => item.name === context.value);
- const numRepos = searchContext ? searchContext.repoNames.length : undefined;
- return (
-
handleContextClick(demoExamples.searchContexts, contextExample)}
- >
-
-
-
- {getContextIcon(context)}
-
-
-
-
- {context.displayName}
-
- {numRepos && (
-
- {numRepos} repos
-
- )}
-
-
{contextExample.description}
-
-
-
-
- )
- })}
-
-
-
+
{/* Example Searches Row */}
-
-
-
-
Community Ask Results
+
+
+
+
Community Ask Results
-
Check out these featured ask results from the community
+
+ {/* Search Context Filter */}
+
+
+
{
+ setSelectedFilterContext(null);
+ }}
+ >
+ All
+
+ {demoExamples.searchContexts.map((context) => (
+
{
+ setSelectedFilterContext(context.id);
+ }}
+ >
+ {getContextIcon(context, 12, selectedFilterContext === context.id)}
+ {context.displayName}
+
+ ))}
+
+
- {demoExamples.searchExamples.map((example) => {
- const searchContexts = demoExamples.searchContexts.filter((context) => example.searchContext.includes(context.id))
- return (
-
handleExampleClick(example)}
- >
-
-
-
- {searchContexts.map((context) => (
-
- {getContextIcon(context, 12)}
- {context.displayName}
-
- ))}
+ {demoExamples.searchExamples
+ .filter((example) => {
+ if (selectedFilterContext === null) return true;
+ return example.searchContext.includes(selectedFilterContext);
+ })
+ .map((example) => {
+ const searchContexts = demoExamples.searchContexts.filter((context) => example.searchContext.includes(context.id))
+ return (
+
handleExampleClick(example)}
+ >
+
+
+
+ {searchContexts.map((context) => (
+
+ {getContextIcon(context, 12)}
+ {context.displayName}
+
+ ))}
+
+
+
+ {example.title}
+
+
+ {example.description}
+
+
-
-
- {example.title}
-
-
- {example.description}
-
-
-
-
-
- )
- })}
+
+
+ )
+ })}
diff --git a/packages/web/src/components/searchScopeInfoCard.tsx b/packages/web/src/components/searchScopeInfoCard.tsx
new file mode 100644
index 00000000..84083548
--- /dev/null
+++ b/packages/web/src/components/searchScopeInfoCard.tsx
@@ -0,0 +1,41 @@
+import Image from "next/image";
+import { LibraryBigIcon, Code, Layers } from "lucide-react";
+import { cn, getCodeHostIcon } from "@/lib/utils";
+
+export const SearchScopeInfoCard = () => {
+ return (
+
+
+
+
Search Context
+
+
+ When asking Sourcebot a question, you can select one or more search contexts to constrain its scope. There
+ are two different types of search contexts:
+
+
+ {(() => {
+ const githubIcon = getCodeHostIcon("github");
+ return githubIcon ? (
+
+ ) : (
+
+ );
+ })()}
+ Repository: A single repository, indicated by the code host icon.
+
+
+
+ Set: A set of repositories, indicated by the library icon.
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/packages/web/src/features/chat/components/chatBox/chatBoxToolbar.tsx b/packages/web/src/features/chat/components/chatBox/chatBoxToolbar.tsx
index 8744e9ac..e32daaa5 100644
--- a/packages/web/src/features/chat/components/chatBox/chatBoxToolbar.tsx
+++ b/packages/web/src/features/chat/components/chatBox/chatBoxToolbar.tsx
@@ -13,6 +13,7 @@ import { ReactEditor, useSlate } from "slate-react";
import { useSelectedLanguageModel } from "../../useSelectedLanguageModel";
import { LanguageModelSelector } from "./languageModelSelector";
import { ContextSelector, type ContextItem } from "./contextSelector";
+import { SearchScopeInfoCard } from "@/components/searchScopeInfoCard";
export interface ChatBoxToolbarProps {
languageModels: LanguageModelInfo[];
@@ -88,8 +89,8 @@ export const ChatBoxToolbar = ({
onOpenChanged={onContextSelectorOpenChanged}
/>
-
- Search contexts and repositories to scope conversation to.
+
+
{languageModels.length > 0 && (