Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"class-variance-authority": "^0.7.0",
"client-only": "^0.0.1",
"clsx": "^2.1.1",
"embla-carousel-auto-scroll": "^8.3.0",
"embla-carousel-react": "^8.3.0",
"escape-string-regexp": "^5.0.0",
"http-status-codes": "^2.3.0",
"lucide-react": "^0.435.0",
Expand Down
2 changes: 2 additions & 0 deletions src/app/navigationMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ export const NavigationMenu = () => {
src={logoDark}
className="h-11 w-auto hidden dark:block"
alt={"Sourcebot logo"}
priority={true}
/>
<Image
src={logoLight}
className="h-11 w-auto block dark:hidden"
alt={"Sourcebot logo"}
priority={true}
/>
</div>

Expand Down
155 changes: 152 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,183 @@
import { listRepositories } from "@/lib/server/searchService";
import { isServiceError } from "@/lib/utils";
import Image from "next/image";
import { Suspense } from "react";
import logoDark from "../../public/sb_logo_dark_large.png";
import logoLight from "../../public/sb_logo_light_large.png";
import { NavigationMenu } from "./navigationMenu";
import { RepositoryCarousel } from "./repositoryCarousel";
import { SearchBar } from "./searchBar";
import { Separator } from "@/components/ui/separator";

export default function Home() {

export default async function Home() {
return (
<div className="h-screen flex flex-col items-center">
{/* TopBar */}
<NavigationMenu />
<div className="flex flex-col justify-center items-center p-4 mt-48">

<div className="flex flex-col justify-center items-center mt-8 md:mt-32 max-w-[90%]">
<div className="max-h-44 w-auto">
<Image
src={logoDark}
className="w-full h-full hidden dark:block"
alt={"Sourcebot logo"}
priority={true}
/>
<Image
src={logoLight}
className="w-full h-full block dark:hidden"
alt={"Sourcebot logo"}
priority={true}
/>
</div>
<div className="w-full flex flex-row mt-4">
<SearchBar
autoFocus={true}
/>
</div>
<div className="mt-8">
<Suspense fallback={<div>...</div>}>
<RepositoryList />
</Suspense>
</div>
<Separator className="mt-5 mb-8" />
<div className="flex flex-col items-center w-fit gap-6">
<span className="font-semibold">How to search</span>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<HowToSection
title="Search in files or paths"
>
<QueryExample>
<Query query="test todo">test todo</Query> <QueryExplanation>(both test and todo)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="test or todo">test <Highlight>or</Highlight> todo</Query> <QueryExplanation>(either test or todo)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query={`"exit boot"`}>{`"exit boot"`}</Query> <QueryExplanation>(exact match)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="TODO case:yes">TODO <Highlight>case:</Highlight>yes</Query> <QueryExplanation>(case sensitive)</QueryExplanation>
</QueryExample>
</HowToSection>
<HowToSection
title="Filter results"
>
<QueryExample>
<Query query="file:README setup"><Highlight>file:</Highlight>README setup</Query> <QueryExplanation>(by filename)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="repo:torvalds/linux test"><Highlight>repo:</Highlight>torvalds/linux test</Query> <QueryExplanation>(by repo)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="lang:typescript"><Highlight>lang:</Highlight>typescript</Query> <QueryExplanation>(by language)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="branch:HEAD"><Highlight>branch:</Highlight>HEAD</Query> <QueryExplanation>(by branch)</QueryExplanation>
</QueryExample>
</HowToSection>
<HowToSection
title="Advanced"
>
<QueryExample>
<Query query="file:\.py$"><Highlight>file:</Highlight>{`\\.py$`}</Query> <QueryExplanation>{`(files that end in ".py")`}</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="sym:main"><Highlight>sym:</Highlight>main</Query> <QueryExplanation>{`(symbols named "main")`}</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="todo -lang:c">todo <Highlight>-lang:c</Highlight></Query> <QueryExplanation>(negate filter)</QueryExplanation>
</QueryExample>
<QueryExample>
<Query query="content:README"><Highlight>content:</Highlight>README</Query> <QueryExplanation>(search content only)</QueryExplanation>
</QueryExample>
</HowToSection>
</div>
</div>
</div>
</div>
)
}

const RepositoryList = async () => {
const _repos = await listRepositories();

if (isServiceError(_repos)) {
return null;
}

const repos = _repos.List.Repos.map((repo) => repo.Repository);

if (repos.length === 0) {
return <span>
Get started
<a
href="https://github.com/TaqlaAI/sourcebot/blob/main/README.md"
className="text-blue-500"
>
{` configuring Sourcebot.`}
</a>
</span>;
}

return (
<div className="flex flex-col items-center gap-3">
<span className="text-sm">
{`Search ${repos.length} `}
<a
href="/repos"
className="text-blue-500"
>
{repos.length > 1 ? 'repositories' : 'repository'}
</a>
</span>
<RepositoryCarousel repos={repos} />
</div>
)
}

const HowToSection = ({ title, children }: { title: string, children: React.ReactNode }) => {
return (
<div className="flex flex-col gap-1">
<span className="dark:text-gray-300 text-sm mb-2 underline">{title}</span>
{children}
</div>
)

}

const Highlight = ({ children }: { children: React.ReactNode }) => {
return (
<span className="text-blue-700 dark:text-blue-500">
{children}
</span>
)
}

const QueryExample = ({ children }: { children: React.ReactNode }) => {
return (
<span className="text-sm font-mono">
{children}
</span>
)
}

const QueryExplanation = ({ children }: { children: React.ReactNode }) => {
return (
<span className="text-gray-500 dark:text-gray-400 ml-3">
{children}
</span>
)
}

const Query = ({ query, children }: { query: string, children: React.ReactNode }) => {
return (
<a
href={`/search?query=${query}`}
className="cursor-pointer hover:underline"
>
{children}
</a>
)
}
21 changes: 19 additions & 2 deletions src/app/repos/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use client';

import { Button } from "@/components/ui/button";
import { getRepoCodeHostInfo } from "@/lib/utils";
import { Column, ColumnDef } from "@tanstack/react-table"
import { ArrowUpDown } from "lucide-react"



export type RepositoryColumnInfo = {
name: string;
branches: {
Expand All @@ -25,6 +24,24 @@ export const columns: ColumnDef<RepositoryColumnInfo>[] = [
{
accessorKey: "name",
header: "Name",
cell: ({ row }) => {
const repo = row.original;
const info = getRepoCodeHostInfo(repo.name);
return (
<div className="flex flex-row items-center gap-2">
<span
className={info?.repoLink ? "cursor-pointer text-blue-500 hover:underline": ""}
onClick={() => {
if (info?.repoLink) {
window.open(info.repoLink, "_blank");
}
}}
>
{repo.name}
</span>
</div>
);
}
},
{
accessorKey: "branches",
Expand Down
98 changes: 98 additions & 0 deletions src/app/repositoryCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use client';

import { Repository } from "@/lib/schemas";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import Autoscroll from "embla-carousel-auto-scroll";
import { getRepoCodeHostInfo } from "@/lib/utils";
import Image from "next/image";
import { FileIcon } from "@radix-ui/react-icons";
import clsx from "clsx";

interface RepositoryCarouselProps {
repos: Repository[];
}

export const RepositoryCarousel = ({
repos,
}: RepositoryCarouselProps) => {
return (
<Carousel
opts={{
align: "start",
loop: true,
}}
className="w-full max-w-lg"
plugins={[
Autoscroll({
startDelay: 0,
speed: 1,
stopOnMouseEnter: true,
stopOnInteraction: false,
}),
]}
>
<CarouselContent>
{repos.map((repo, index) => (
<CarouselItem key={index} className="basis-auto">
<RepositoryBadge
key={index}
repo={repo}
/>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
)
};

interface RepositoryBadgeProps {
repo: Repository;
}

const RepositoryBadge = ({
repo
}: RepositoryBadgeProps) => {
const { repoIcon, repoName, repoLink } = (() => {
const info = getRepoCodeHostInfo(repo.Name);

if (info) {
return {
repoIcon: <Image
src={info.icon}
alt={info.costHostName}
className="w-4 h-4 dark:invert"
/>,
repoName: info.repoName,
repoLink: info.repoLink,
}
}

return {
repoIcon: <FileIcon className="w-4 h-4" />,
repoName: repo.Name,
repoLink: undefined,
}
})();

return (
<div
onClick={() => {
if (repoLink !== undefined) {
window.open(repoLink, "_blank");
}
}}
className={clsx("flex flex-row items-center gap-2 border rounded-md p-2 text-clip", {
"cursor-pointer": repoLink !== undefined,
})}
>
{repoIcon}
<span className="text-sm font-mono">
{repoName}
</span>
</div>
)
}
Loading