From 648df845f0fbb7ab7ff573650c22444f8b325003 Mon Sep 17 00:00:00 2001 From: msukkari Date: Sun, 27 Jul 2025 17:16:24 -0700 Subject: [PATCH] refactor demo cards to their own component --- .../components/homepage/agenticSearch.tsx | 104 +-------- .../homepage/askSourcebotDemoCards.tsx | 211 ++++++++++++++++++ packages/web/src/types.ts | 21 +- 3 files changed, 233 insertions(+), 103 deletions(-) create mode 100644 packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx diff --git a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx index 04c2c5e6..c8bd472f 100644 --- a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx +++ b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx @@ -6,15 +6,12 @@ import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolba import { LanguageModelInfo } from "@/features/chat/types"; import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread"; import { RepositoryQuery, SearchContextQuery } from "@/lib/types"; -import { Layers, Search } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; import { useState } from "react"; import { SearchModeSelector, SearchModeSelectorProps } from "./toolbar"; -import { Card } from "@/components/ui/card"; -import { CardContent } from "@/components/ui/card"; import { useLocalStorage } from "usehooks-ts"; import { ContextItem } from "@/features/chat/components/chatBox/contextSelector"; -import { DemoExamples, DemoSearchExample, DemoSearchContextExample } from "@/types"; +import { DemoExamples } from "@/types"; +import { AskSourcebotDemoCards } from "./askSourcebotDemoCards"; interface AgenticSearchProps { searchModeSelectorProps: SearchModeSelectorProps; @@ -40,14 +37,6 @@ export const AgenticSearch = ({ const [selectedItems, setSelectedItems] = useLocalStorage("selectedContextItems", [], { initializeWithValue: false }); const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false); - const handleExampleClick = (example: DemoSearchExample) => { - console.log(example); - } - - const handleContextClick = (context: DemoSearchContextExample) => { - console.log(context); - } - return (
@@ -83,88 +72,13 @@ export const AgenticSearch = ({
{demoExamples && ( -
- {/* Example Searches Column */} -
-
- -

Example Searches

-
-
- {demoExamples.searchExamples.map((example) => ( - handleExampleClick(example)} - > - -
-
- {example.icon} -
-
-

- {example.title} -

-

{example.description}

- - {example.category} - -
-
-
-
- ))} -
-
- - {/* Search Contexts Column */} -
-
- -

Search Contexts

-
-
- {demoExamples.searchContextExamples.map((context) => { - const searchContext = searchContexts.find((item) => item.name === context.name); - if (!searchContext) return null; - const isSelected = false; //selectedItems.some((item) => item.id === context.id) - - const numRepos = searchContext.repoNames.length; - return ( - handleContextClick(context)} - > - -
-
- {context.icon} -
-
-

- {context.displayName} -

-

{context.description}

- - {numRepos} repos - -
-
-
-
- ) - })} -
-
-
+ )}
) diff --git a/packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx b/packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx new file mode 100644 index 00000000..5ebfd6a0 --- /dev/null +++ b/packages/web/src/app/[domain]/components/homepage/askSourcebotDemoCards.tsx @@ -0,0 +1,211 @@ +'use client'; + +import Image from "next/image"; +import { Search, LibraryBigIcon, Code, Layers } 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 { cn, getCodeHostIcon } from "@/lib/utils"; +import { RepositoryQuery, SearchContextQuery } from "@/lib/types"; + +interface AskSourcebotDemoCardsProps { + demoExamples: DemoExamples; + selectedItems: ContextItem[]; + setSelectedItems: (items: ContextItem[]) => void; + searchContexts: SearchContextQuery[]; + repos: RepositoryQuery[]; +} + +export const AskSourcebotDemoCards = ({ + demoExamples, + selectedItems, + setSelectedItems, + searchContexts, + repos, +}: AskSourcebotDemoCardsProps) => { + const handleExampleClick = (example: DemoSearchExample) => { + if (example.url) { + window.open(example.url, '_blank'); + } + } + + const getContextIcon = (context: DemoSearchContext, size: number = 20) => { + const sizeClass = size === 12 ? "h-3 w-3" : "h-5 w-5"; + + if (context.type === "set") { + return ; + } + + if (context.codeHostType) { + const codeHostIcon = getCodeHostIcon(context.codeHostType); + if (codeHostIcon) { + return ( + {`${context.codeHostType} + ); + } + } + + 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; + } + + 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; + } + + const isSelected = selectedItems.some( + (selected) => selected.type === 'context' && selected.value === context.value + ); + const newSelectedItems = isSelected + ? selectedItems.filter( + (selected) => !(selected.type === 'context' && selected.value === context.value) + ) + : [...selectedItems, { type: 'context', value: context.value, name: context.displayName, repoCount: searchContext.repoNames.length } as SearchContextItem]; + + setSelectedItems(newSelectedItems); + } else { + const repo = repos.find((repo) => repo.repoName === context.value); + if (!repo) { + console.error(`Repo ${context.value} not found on handleContextClick`); + return; + } + + const isSelected = selectedItems.some( + (selected) => selected.type === 'repo' && selected.value === context.value + ); + const newSelectedItems = isSelected + ? selectedItems.filter( + (selected) => !(selected.type === 'repo' && selected.value === context.value) + ) + : [...selectedItems, { type: 'repo', value: context.value, name: context.displayName, codeHostType: repo.codeHostType } as RepoContextItem]; + + setSelectedItems(newSelectedItems); + } + } + + return ( +
+ {/* Search Context Row */} +
+
+
+ +

Search Context

+
+

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; + } + + 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

+
+

Check out these featured ask results from the community

+
+
+ {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} + + ))} +
+
+

+ {example.title} +

+

+ {example.description} +

+
+
+
+
+ )})} +
+
+
+ ); +}; \ No newline at end of file diff --git a/packages/web/src/types.ts b/packages/web/src/types.ts index 8f41d89e..5b55b9db 100644 --- a/packages/web/src/types.ts +++ b/packages/web/src/types.ts @@ -4,29 +4,34 @@ export const orgMetadataSchema = z.object({ anonymousAccessEnabled: z.boolean().optional(), }) +export const demoSearchContextSchema = z.object({ + id: z.number(), + displayName: z.string(), + value: z.string(), + type: z.enum(["repo", "set"]), + codeHostType: z.string().optional(), +}) + export const demoSearchExampleSchema = z.object({ - id: z.string(), title: z.string(), description: z.string(), - icon: z.string(), - category: z.string(), + url: z.string(), + searchContext: z.array(z.number()) }) export const demoSearchContextExampleSchema = z.object({ - id: z.string(), - displayName: z.string(), - name: z.string(), + searchContext: z.number(), description: z.string(), - icon: z.string(), - color: z.string(), }) export const demoExamplesSchema = z.object({ + searchContexts: demoSearchContextSchema.array(), searchExamples: demoSearchExampleSchema.array(), searchContextExamples: demoSearchContextExampleSchema.array(), }) export type OrgMetadata = z.infer; export type DemoExamples = z.infer; +export type DemoSearchContext = z.infer; export type DemoSearchExample = z.infer; export type DemoSearchContextExample = z.infer; \ No newline at end of file