mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-15 22:05:23 +00:00
load demo example
This commit is contained in:
parent
1114ee57f5
commit
463477c85e
5 changed files with 69 additions and 106 deletions
|
|
@ -12,6 +12,7 @@ export type {
|
||||||
export {
|
export {
|
||||||
base64Decode,
|
base64Decode,
|
||||||
loadConfig,
|
loadConfig,
|
||||||
|
loadJsonFile,
|
||||||
isRemotePath,
|
isRemotePath,
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
export {
|
export {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { indexSchema } from "@sourcebot/schemas/v3/index.schema";
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
import { Ajv } from "ajv";
|
import { Ajv } from "ajv";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const ajv = new Ajv({
|
const ajv = new Ajv({
|
||||||
validateFormats: false,
|
validateFormats: false,
|
||||||
|
|
@ -18,6 +19,66 @@ export const isRemotePath = (path: string) => {
|
||||||
return path.startsWith('https://') || path.startsWith('http://');
|
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> => {
|
export const loadConfig = async (configPath: string): Promise<SourcebotConfig> => {
|
||||||
const configContent = await (async () => {
|
const configContent = await (async () => {
|
||||||
if (isRemotePath(configPath)) {
|
if (isRemotePath(configPath)) {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,8 @@ import { ChatBox } from "@/features/chat/components/chatBox";
|
||||||
import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar";
|
import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar";
|
||||||
import { LanguageModelInfo } from "@/features/chat/types";
|
import { LanguageModelInfo } from "@/features/chat/types";
|
||||||
import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread";
|
import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread";
|
||||||
import { resetEditor } from "@/features/chat/utils";
|
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
|
||||||
import { RepositoryQuery, SearchContextQuery } from "@/lib/types";
|
import { RepositoryQuery, SearchContextQuery } from "@/lib/types";
|
||||||
import { getDisplayTime } from "@/lib/utils";
|
import { Layers, Search } from "lucide-react";
|
||||||
import { Code, Database, FileIcon, FileText, Gamepad2, Globe, Layers, LucideIcon, Search, SearchIcon, Smartphone, Zap } from "lucide-react";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SearchModeSelector, SearchModeSelectorProps } from "./toolbar";
|
import { SearchModeSelector, SearchModeSelectorProps } from "./toolbar";
|
||||||
|
|
@ -19,14 +16,6 @@ import { useLocalStorage } from "usehooks-ts";
|
||||||
import { ContextItem } from "@/features/chat/components/chatBox/contextSelector";
|
import { ContextItem } from "@/features/chat/components/chatBox/contextSelector";
|
||||||
import { DemoExamples, DemoSearchExample, DemoSearchContextExample } from "@/types";
|
import { DemoExamples, DemoSearchExample, DemoSearchContextExample } from "@/types";
|
||||||
|
|
||||||
const Highlight = ({ children }: { children: React.ReactNode }) => {
|
|
||||||
return (
|
|
||||||
<span className="text-highlight">
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AgenticSearchProps {
|
interface AgenticSearchProps {
|
||||||
searchModeSelectorProps: SearchModeSelectorProps;
|
searchModeSelectorProps: SearchModeSelectorProps;
|
||||||
languageModels: LanguageModelInfo[];
|
languageModels: LanguageModelInfo[];
|
||||||
|
|
@ -40,101 +29,11 @@ interface AgenticSearchProps {
|
||||||
demoExamples: DemoExamples | undefined;
|
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 = ({
|
export const AgenticSearch = ({
|
||||||
searchModeSelectorProps,
|
searchModeSelectorProps,
|
||||||
languageModels,
|
languageModels,
|
||||||
repos,
|
repos,
|
||||||
searchContexts,
|
searchContexts,
|
||||||
chatHistory,
|
|
||||||
demoExamples,
|
demoExamples,
|
||||||
}: AgenticSearchProps) => {
|
}: AgenticSearchProps) => {
|
||||||
const { createNewChatThread, isLoading } = useCreateNewChatThread();
|
const { createNewChatThread, isLoading } = useCreateNewChatThread();
|
||||||
|
|
@ -226,7 +125,7 @@ export const AgenticSearch = ({
|
||||||
<h3 className="text-lg font-semibold">Search Contexts</h3>
|
<h3 className="text-lg font-semibold">Search Contexts</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 gap-3">
|
<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);
|
const searchContext = searchContexts.find((item) => item.name === context.name);
|
||||||
if (!searchContext) return null;
|
if (!searchContext) return null;
|
||||||
const isSelected = false; //selectedItems.some((item) => item.id === context.id)
|
const isSelected = false; //selectedItems.some((item) => item.id === context.id)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { getRepos, getSearchContexts } from "@/actions";
|
||||||
import { Footer } from "@/app/components/footer";
|
import { Footer } from "@/app/components/footer";
|
||||||
import { getOrgFromDomain } from "@/data/org";
|
import { getOrgFromDomain } from "@/data/org";
|
||||||
import { getConfiguredLanguageModelsInfo, getUserChatHistory } from "@/features/chat/actions";
|
import { getConfiguredLanguageModelsInfo, getUserChatHistory } from "@/features/chat/actions";
|
||||||
import { isServiceError, loadDemoExamples } from "@/lib/utils";
|
import { isServiceError } from "@/lib/utils";
|
||||||
import { Homepage } from "./components/homepage";
|
import { Homepage } from "./components/homepage";
|
||||||
import { NavigationMenu } from "./components/navigationMenu";
|
import { NavigationMenu } from "./components/navigationMenu";
|
||||||
import { PageNotFound } from "./components/pageNotFound";
|
import { PageNotFound } from "./components/pageNotFound";
|
||||||
|
|
@ -12,6 +12,8 @@ import { auth } from "@/auth";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { SEARCH_MODE_COOKIE_NAME } from "@/lib/constants";
|
import { SEARCH_MODE_COOKIE_NAME } from "@/lib/constants";
|
||||||
import { env } from "@/env.mjs";
|
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 } }) {
|
export default async function Home({ params: { domain } }: { params: { domain: string } }) {
|
||||||
const org = await getOrgFromDomain(domain);
|
const org = await getOrgFromDomain(domain);
|
||||||
|
|
@ -49,7 +51,7 @@ export default async function Home({ params: { domain } }: { params: { domain: s
|
||||||
searchModeCookie?.value === "precise"
|
searchModeCookie?.value === "precise"
|
||||||
) ? searchModeCookie.value : models.length > 0 ? "agentic" : "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 (
|
return (
|
||||||
<div className="flex flex-col items-center overflow-hidden min-h-screen">
|
<div className="flex flex-col items-center overflow-hidden min-h-screen">
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export const demoSearchContextExampleSchema = z.object({
|
||||||
|
|
||||||
export const demoExamplesSchema = z.object({
|
export const demoExamplesSchema = z.object({
|
||||||
searchExamples: demoSearchExampleSchema.array(),
|
searchExamples: demoSearchExampleSchema.array(),
|
||||||
searchContexts: demoSearchContextExampleSchema.array(),
|
searchContextExamples: demoSearchContextExampleSchema.array(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type OrgMetadata = z.infer<typeof orgMetadataSchema>;
|
export type OrgMetadata = z.infer<typeof orgMetadataSchema>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue