From 1114ee57f59fd034ad9cb2d8daa9175e420c87f8 Mon Sep 17 00:00:00 2001 From: msukkari Date: Sun, 27 Jul 2025 13:04:07 -0700 Subject: [PATCH] wip demo example path --- .../components/homepage/agenticSearch.tsx | 403 ++++++++---------- .../[domain]/components/homepage/index.tsx | 4 + packages/web/src/app/[domain]/page.tsx | 6 +- packages/web/src/env.mjs | 2 + packages/web/src/types.ts | 27 +- 5 files changed, 226 insertions(+), 216 deletions(-) diff --git a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx index cbd6999b..9374d2b5 100644 --- a/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx +++ b/packages/web/src/app/[domain]/components/homepage/agenticSearch.tsx @@ -1,6 +1,5 @@ 'use client'; -import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { ChatBox } from "@/features/chat/components/chatBox"; import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar"; @@ -10,40 +9,15 @@ import { resetEditor } from "@/features/chat/utils"; import { useDomain } from "@/hooks/useDomain"; import { RepositoryQuery, SearchContextQuery } from "@/lib/types"; import { getDisplayTime } from "@/lib/utils"; -import { BrainIcon, FileIcon, LucideIcon, SearchIcon } from "lucide-react"; -import Link from "next/link"; -import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; -import { ReactEditor, useSlate } from "slate-react"; +import { Code, Database, FileIcon, FileText, Gamepad2, Globe, Layers, LucideIcon, Search, SearchIcon, Smartphone, Zap } 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"; - -// @todo: we should probably rename this to a different type since it sort-of clashes -// with the Suggestion system we have built into the chat box. -type SuggestionType = "understand" | "find" | "summarize"; - -const suggestionTypes: Record = { - understand: { - icon: BrainIcon, - title: "Understand", - description: "Understand the codebase", - }, - find: { - icon: SearchIcon, - title: "Find", - description: "Find the codebase", - }, - summarize: { - icon: FileIcon, - title: "Summarize", - description: "Summarize the codebase", - }, -} - +import { DemoExamples, DemoSearchExample, DemoSearchContextExample } from "@/types"; const Highlight = ({ children }: { children: React.ReactNode }) => { return ( @@ -53,59 +27,6 @@ const Highlight = ({ children }: { children: React.ReactNode }) => { ) } -const suggestions: Record = { - understand: [ - { - queryText: "How does authentication work in this codebase?", - openRepoSelector: true, - }, - { - queryText: "How are API endpoints structured and organized?", - openRepoSelector: true, - }, - { - queryText: "How does the build and deployment process work?", - openRepoSelector: true, - }, - { - queryText: "How is error handling implemented across the application?", - openRepoSelector: true, - }, - ], - find: [ - { - queryText: "Find examples of different logging libraries used throughout the codebase.", - }, - { - queryText: "Find examples of potential security vulnerabilities or authentication issues.", - }, - { - queryText: "Find examples of API endpoints and route handlers.", - } - ], - summarize: [ - { - queryText: "Summarize the purpose of this file @file:", - queryNode: Summarize the purpose of this file @file: - }, - { - queryText: "Summarize the project structure and architecture.", - openRepoSelector: true, - }, - { - queryText: "Provide a quick start guide for ramping up on this codebase.", - openRepoSelector: true, - } - ], -} - -const MAX_RECENT_CHAT_HISTORY_COUNT = 10; - - interface AgenticSearchProps { searchModeSelectorProps: SearchModeSelectorProps; languageModels: LanguageModelInfo[]; @@ -116,49 +37,121 @@ interface AgenticSearchProps { createdAt: Date; name: string | null; }[]; + demoExamples: DemoExamples | undefined; } +const exampleSearches = [ + { + id: "1", + title: "Show me examples of how useMemo is used", + description: "Find React performance optimization patterns", + icon: , + category: "React", + }, + { + id: "2", + title: "How do I implement authentication?", + description: "Explore auth patterns and best practices", + icon: , + category: "Security", + }, + { + id: "3", + title: "Find API route handlers", + description: "Locate and analyze API endpoint implementations", + icon: , + category: "Backend", + }, + { + id: "4", + title: "Show me error handling patterns", + description: "Discover error boundary and exception handling", + icon: , + category: "Best Practices", + }, + { + id: "5", + title: "How are components structured?", + description: "Analyze component architecture and patterns", + icon: , + category: "Architecture", + }, +] + +const searchContextsExample = [ + { + id: "1", + displayName: "Next.js", + name: "nextjs", + description: "React framework for production", + icon: , + color: "bg-black text-white", + }, + { + id: "2", + displayName: "React", + name: "react", + description: "JavaScript library for building UIs", + icon: , + color: "bg-blue-500 text-white", + }, + { + id: "3", + displayName: "TypeScript", + name: "typescript", + description: "Typed JavaScript at scale", + icon: , + color: "bg-blue-600 text-white", + }, + { + id: "4", + displayName: "Tailwind CSS", + name: "tailwindcss", + description: "Utility-first CSS framework", + icon: , + color: "bg-cyan-500 text-white", + }, + { + id: "5", + displayName: "Godot Engine", + name: "godot", + description: "Open source game engine", + icon: , + color: "bg-blue-400 text-white", + }, + { + id: "6", + displayName: "React Native", + name: "react-native", + description: "Build mobile apps with React", + icon: , + color: "bg-purple-500 text-white", + }, +] + export const AgenticSearch = ({ searchModeSelectorProps, languageModels, repos, searchContexts, chatHistory, + demoExamples, }: AgenticSearchProps) => { - const [selectedSuggestionType, _setSelectedSuggestionType] = useState(undefined); const { createNewChatThread, isLoading } = useCreateNewChatThread(); - const dropdownRef = useRef(null); - const editor = useSlate(); const [selectedItems, setSelectedItems] = useLocalStorage("selectedContextItems", [], { initializeWithValue: false }); - const domain = useDomain(); const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false); - const setSelectedSuggestionType = useCallback((type: SuggestionType | undefined) => { - _setSelectedSuggestionType(type); - if (type) { - ReactEditor.focus(editor); - } - }, [editor, _setSelectedSuggestionType]); + const handleExampleClick = (example: DemoSearchExample) => { + console.log(example); + } - // Close dropdown when clicking outside - useEffect(() => { - function handleClickOutside(event: MouseEvent) { - if ( - !dropdownRef.current?.contains(event.target as Node) - ) { - setSelectedSuggestionType(undefined); - } - } - - document.addEventListener("mousedown", handleClickOutside) - return () => document.removeEventListener("mousedown", handleClickOutside) - }, [setSelectedSuggestionType]); + const handleContextClick = (context: DemoSearchContextExample) => { + console.log(context); + } return ( -
-
+
+
{ createNewChatThread(children, selectedItems); @@ -187,111 +180,93 @@ export const AgenticSearch = ({ className="ml-auto" />
- - {selectedSuggestionType && ( -
-

- {suggestionTypes[selectedSuggestionType].title} -

- {suggestions[selectedSuggestionType].map(({ queryText, queryNode, openRepoSelector }, index) => ( -
{ - resetEditor(editor); - editor.insertText(queryText); - setSelectedSuggestionType(undefined); - - if (openRepoSelector) { - setIsContextSelectorOpen(true); - } else { - ReactEditor.focus(editor); - } - }} - > - - {queryNode ?? queryText} -
- ))} -
- )}
-
-
- {Object.entries(suggestionTypes).map(([type, suggestion], index) => ( - { - setSelectedSuggestionType(type as SuggestionType); - }} - /> - ))} -
-
- {chatHistory.length > 0 && ( -
- - Recent conversations -
- {chatHistory - .slice(0, MAX_RECENT_CHAT_HISTORY_COUNT) - .map((chat) => ( - - - {chat.name ?? "Untitled Chat"} - - - {getDisplayTime(chat.createdAt)} - - - ))} + + {demoExamples && ( +
+ {/* Example Searches Column */} +
+
+ +

Example Searches

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

+ {example.title} +

+

{example.description}

+ + {example.category} + +
+
+
+
+ ))}
- {chatHistory.length > MAX_RECENT_CHAT_HISTORY_COUNT && ( - - View all - - )}
+ + {/* Search Contexts Column */} +
+
+ +

Search Contexts

+
+
+ {demoExamples.searchContexts?.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 + +
+
+
+
+ ) + })} +
+
+
)} -
+
) -} - - -interface ExampleButtonProps { - Icon: LucideIcon; - title: string; - onClick: () => void; -} - -const ExampleButton = ({ - Icon, - title, - onClick, -}: ExampleButtonProps) => { - return ( - - ) -} +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/homepage/index.tsx b/packages/web/src/app/[domain]/components/homepage/index.tsx index c4014ac3..47262e0e 100644 --- a/packages/web/src/app/[domain]/components/homepage/index.tsx +++ b/packages/web/src/app/[domain]/components/homepage/index.tsx @@ -10,6 +10,7 @@ import { SearchMode } from "./toolbar"; import { CustomSlateEditor } from "@/features/chat/customSlateEditor"; import { setSearchModeCookie } from "@/actions"; import { useCallback, useState } from "react"; +import { DemoExamples } from "@/types"; interface HomepageProps { initialRepos: RepositoryQuery[]; @@ -21,6 +22,7 @@ interface HomepageProps { name: string | null; }[]; initialSearchMode: SearchMode; + demoExamples: DemoExamples | undefined; } @@ -30,6 +32,7 @@ export const Homepage = ({ languageModels, chatHistory, initialSearchMode, + demoExamples, }: HomepageProps) => { const [searchMode, setSearchMode] = useState(initialSearchMode); const isAgenticSearchEnabled = languageModels.length > 0; @@ -86,6 +89,7 @@ export const Homepage = ({ repos={initialRepos} searchContexts={searchContexts} chatHistory={chatHistory} + demoExamples={demoExamples} /> )} diff --git a/packages/web/src/app/[domain]/page.tsx b/packages/web/src/app/[domain]/page.tsx index a4455f34..b63348c6 100644 --- a/packages/web/src/app/[domain]/page.tsx +++ b/packages/web/src/app/[domain]/page.tsx @@ -2,7 +2,7 @@ import { getRepos, getSearchContexts } from "@/actions"; import { Footer } from "@/app/components/footer"; import { getOrgFromDomain } from "@/data/org"; import { getConfiguredLanguageModelsInfo, getUserChatHistory } from "@/features/chat/actions"; -import { isServiceError } from "@/lib/utils"; +import { isServiceError, loadDemoExamples } from "@/lib/utils"; import { Homepage } from "./components/homepage"; import { NavigationMenu } from "./components/navigationMenu"; import { PageNotFound } from "./components/pageNotFound"; @@ -11,6 +11,7 @@ import { ServiceErrorException } from "@/lib/serviceError"; import { auth } from "@/auth"; import { cookies } from "next/headers"; import { SEARCH_MODE_COOKIE_NAME } from "@/lib/constants"; +import { env } from "@/env.mjs"; export default async function Home({ params: { domain } }: { params: { domain: string } }) { const org = await getOrgFromDomain(domain); @@ -48,6 +49,8 @@ export default async function Home({ params: { domain } }: { params: { domain: s searchModeCookie?.value === "precise" ) ? searchModeCookie.value : models.length > 0 ? "agentic" : "precise"; + const demoExamples = undefined; //await loadDemoExamples(env.SOURCEBOT_DEMO_EXAMPLES_PATH); + return (
diff --git a/packages/web/src/env.mjs b/packages/web/src/env.mjs index 0059b791..6dcfaef0 100644 --- a/packages/web/src/env.mjs +++ b/packages/web/src/env.mjs @@ -129,6 +129,8 @@ export const env = createEnv({ DEBUG_WRITE_CHAT_MESSAGES_TO_FILE: booleanSchema.default('false'), LANGFUSE_SECRET_KEY: z.string().optional(), + + SOURCEBOT_DEMO_EXAMPLES_PATH: z.string().optional(), }, // @NOTE: Please make sure of the following: // - Make sure you destructure all client variables in diff --git a/packages/web/src/types.ts b/packages/web/src/types.ts index 394c3835..a8cf2a38 100644 --- a/packages/web/src/types.ts +++ b/packages/web/src/types.ts @@ -4,4 +4,29 @@ export const orgMetadataSchema = z.object({ anonymousAccessEnabled: z.boolean().optional(), }) -export type OrgMetadata = z.infer; \ No newline at end of file +export const demoSearchExampleSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + icon: z.string(), + category: z.string(), +}) + +export const demoSearchContextExampleSchema = z.object({ + id: z.string(), + displayName: z.string(), + name: z.string(), + description: z.string(), + icon: z.string(), + color: z.string(), +}) + +export const demoExamplesSchema = z.object({ + searchExamples: demoSearchExampleSchema.array(), + searchContexts: demoSearchContextExampleSchema.array(), +}) + +export type OrgMetadata = z.infer; +export type DemoExamples = z.infer; +export type DemoSearchExample = z.infer; +export type DemoSearchContextExample = z.infer; \ No newline at end of file