load demo example

This commit is contained in:
msukkari 2025-07-27 13:40:11 -07:00
parent 1114ee57f5
commit 463477c85e
5 changed files with 69 additions and 106 deletions

View file

@ -12,6 +12,7 @@ export type {
export {
base64Decode,
loadConfig,
loadJsonFile,
isRemotePath,
} from "./utils.js";
export {

View file

@ -3,6 +3,7 @@ import { indexSchema } from "@sourcebot/schemas/v3/index.schema";
import { readFile } from 'fs/promises';
import stripJsonComments from 'strip-json-comments';
import { Ajv } from "ajv";
import { z } from "zod";
const ajv = new Ajv({
validateFormats: false,
@ -18,6 +19,66 @@ export const isRemotePath = (path: string) => {
return path.startsWith('https://') || path.startsWith('http://');
}
// TODO: Merge this with config loading logic which uses AJV
export const loadJsonFile = async <T>(
filePath: string,
schema: any
): Promise<T> => {
const fileContent = await (async () => {
if (isRemotePath(filePath)) {
const response = await fetch(filePath);
if (!response.ok) {
throw new Error(`Failed to fetch file ${filePath}: ${response.statusText}`);
}
return response.text();
} else {
// Retry logic for handling race conditions with mounted volumes
const maxAttempts = 5;
const retryDelayMs = 2000;
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await readFile(filePath, {
encoding: 'utf-8',
});
} catch (error) {
lastError = error as Error;
// Only retry on ENOENT errors (file not found)
if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {
throw error; // Throw immediately for non-ENOENT errors
}
// Log warning before retry (except on the last attempt)
if (attempt < maxAttempts) {
console.warn(`File not found, retrying in 2s... (Attempt ${attempt}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
}
}
}
// If we've exhausted all retries, throw the last ENOENT error
if (lastError) {
throw lastError;
}
throw new Error('Failed to load file after all retry attempts');
}
})();
const parsedData = JSON.parse(stripJsonComments(fileContent));
try {
return schema.parse(parsedData);
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`File '${filePath}' is invalid: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
}
throw error;
}
}
export const loadConfig = async (configPath: string): Promise<SourcebotConfig> => {
const configContent = await (async () => {
if (isRemotePath(configPath)) {

View file

@ -5,11 +5,8 @@ import { ChatBox } from "@/features/chat/components/chatBox";
import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar";
import { LanguageModelInfo } from "@/features/chat/types";
import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread";
import { resetEditor } from "@/features/chat/utils";
import { useDomain } from "@/hooks/useDomain";
import { RepositoryQuery, SearchContextQuery } from "@/lib/types";
import { getDisplayTime } from "@/lib/utils";
import { Code, Database, FileIcon, FileText, Gamepad2, Globe, Layers, LucideIcon, Search, SearchIcon, Smartphone, Zap } from "lucide-react";
import { Layers, Search } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { useState } from "react";
import { SearchModeSelector, SearchModeSelectorProps } from "./toolbar";
@ -19,14 +16,6 @@ import { useLocalStorage } from "usehooks-ts";
import { ContextItem } from "@/features/chat/components/chatBox/contextSelector";
import { DemoExamples, DemoSearchExample, DemoSearchContextExample } from "@/types";
const Highlight = ({ children }: { children: React.ReactNode }) => {
return (
<span className="text-highlight">
{children}
</span>
)
}
interface AgenticSearchProps {
searchModeSelectorProps: SearchModeSelectorProps;
languageModels: LanguageModelInfo[];
@ -40,101 +29,11 @@ interface AgenticSearchProps {
demoExamples: DemoExamples | undefined;
}
const exampleSearches = [
{
id: "1",
title: "Show me examples of how useMemo is used",
description: "Find React performance optimization patterns",
icon: <Zap className="h-4 w-4" />,
category: "React",
},
{
id: "2",
title: "How do I implement authentication?",
description: "Explore auth patterns and best practices",
icon: <Database className="h-4 w-4" />,
category: "Security",
},
{
id: "3",
title: "Find API route handlers",
description: "Locate and analyze API endpoint implementations",
icon: <Globe className="h-4 w-4" />,
category: "Backend",
},
{
id: "4",
title: "Show me error handling patterns",
description: "Discover error boundary and exception handling",
icon: <FileText className="h-4 w-4" />,
category: "Best Practices",
},
{
id: "5",
title: "How are components structured?",
description: "Analyze component architecture and patterns",
icon: <Layers className="h-4 w-4" />,
category: "Architecture",
},
]
const searchContextsExample = [
{
id: "1",
displayName: "Next.js",
name: "nextjs",
description: "React framework for production",
icon: <Code className="h-5 w-5" />,
color: "bg-black text-white",
},
{
id: "2",
displayName: "React",
name: "react",
description: "JavaScript library for building UIs",
icon: <Code className="h-5 w-5" />,
color: "bg-blue-500 text-white",
},
{
id: "3",
displayName: "TypeScript",
name: "typescript",
description: "Typed JavaScript at scale",
icon: <FileText className="h-5 w-5" />,
color: "bg-blue-600 text-white",
},
{
id: "4",
displayName: "Tailwind CSS",
name: "tailwindcss",
description: "Utility-first CSS framework",
icon: <Layers className="h-5 w-5" />,
color: "bg-cyan-500 text-white",
},
{
id: "5",
displayName: "Godot Engine",
name: "godot",
description: "Open source game engine",
icon: <Gamepad2 className="h-5 w-5" />,
color: "bg-blue-400 text-white",
},
{
id: "6",
displayName: "React Native",
name: "react-native",
description: "Build mobile apps with React",
icon: <Smartphone className="h-5 w-5" />,
color: "bg-purple-500 text-white",
},
]
export const AgenticSearch = ({
searchModeSelectorProps,
languageModels,
repos,
searchContexts,
chatHistory,
demoExamples,
}: AgenticSearchProps) => {
const { createNewChatThread, isLoading } = useCreateNewChatThread();
@ -226,7 +125,7 @@ export const AgenticSearch = ({
<h3 className="text-lg font-semibold">Search Contexts</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 gap-3">
{demoExamples.searchContexts?.map((context) => {
{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)

View file

@ -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, loadDemoExamples } from "@/lib/utils";
import { isServiceError } from "@/lib/utils";
import { Homepage } from "./components/homepage";
import { NavigationMenu } from "./components/navigationMenu";
import { PageNotFound } from "./components/pageNotFound";
@ -12,6 +12,8 @@ import { auth } from "@/auth";
import { cookies } from "next/headers";
import { SEARCH_MODE_COOKIE_NAME } from "@/lib/constants";
import { env } from "@/env.mjs";
import { loadJsonFile } from "@sourcebot/shared";
import { DemoExamples, demoExamplesSchema } from "@/types";
export default async function Home({ params: { domain } }: { params: { domain: string } }) {
const org = await getOrgFromDomain(domain);
@ -49,7 +51,7 @@ 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);
const demoExamples = env.SOURCEBOT_DEMO_EXAMPLES_PATH ? await loadJsonFile<DemoExamples>(env.SOURCEBOT_DEMO_EXAMPLES_PATH, demoExamplesSchema) : undefined;
return (
<div className="flex flex-col items-center overflow-hidden min-h-screen">

View file

@ -23,7 +23,7 @@ export const demoSearchContextExampleSchema = z.object({
export const demoExamplesSchema = z.object({
searchExamples: demoSearchExampleSchema.array(),
searchContexts: demoSearchContextExampleSchema.array(),
searchContextExamples: demoSearchContextExampleSchema.array(),
})
export type OrgMetadata = z.infer<typeof orgMetadataSchema>;