diff --git a/next.config.mjs b/next.config.mjs index 4678774..6519f7a 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + transpilePackages: ['next-mdx-remote'], +} -export default nextConfig; +export default nextConfig diff --git a/package.json b/package.json index bb7e7ec..8848ef9 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,17 @@ "@google/generative-ai": "^0.21.0", "@langchain/core": "^0.3.26", "axios": "^1.7.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "dotenv": "^16.4.7", "langchain": "^0.3.7", + "lucide-react": "^0.469.0", "next": "15.1.0", + "next-mdx-remote": "^5.0.0", "pg": "^8.13.1", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "tailwind-merge": "^2.6.0" }, "devDependencies": { "@babel/cli": "^7.26.4", diff --git a/src/agent/structured-output/schema-factory.ts b/src/agent/structured-output/schema-factory.ts index 84040f3..c86d93c 100644 --- a/src/agent/structured-output/schema-factory.ts +++ b/src/agent/structured-output/schema-factory.ts @@ -49,36 +49,10 @@ File Schema Example Output: summary: z .string() .describe( - 'Summary of the file, its main purpose, and its role in the project. Include Markdown links to important code blocks within this file using the format `[{Description of Code Block}]({Full github url of the file including the start line with optional ending line}#L{startLine}-L{endLine})` where applicable.' - ), - relevantCodeBlocks: z - .array( - z.object({ - name: z - .string() - .describe( - 'Name or identifier of the code block (e.g., function name, class name, key variable).' - ), - description: z - .string() - .describe( - 'Description of the code block and its significance within the file.' - ), - startLine: z - .number() - .describe( - 'Starting line number of the code block.' - ), - endLine: z - .number() - .describe( - 'Ending line number of the code block.' - ), - }) - ) - .optional() - .describe( - 'List of important code blocks (functions, classes, key sections) within the file, with line numbers.' + 'Summary of the file talking about its main purpose, and its role in the project.\n' + + 'Include Markdown links to important code blocks within this file using the format\n`' + + '[{Description of Code Block}]({Full github url of the file including the start line with optional ending line}#L{startLine}-L{endLine})` where applicable.\n' + + 'Also you should not return more than 2-3 paragraphs of summary.' ), }) diff --git a/src/app/[ownerSlug]/[repoSlug]/loading.tsx b/src/app/[ownerSlug]/[repoSlug]/loading.tsx new file mode 100644 index 0000000..06820fe --- /dev/null +++ b/src/app/[ownerSlug]/[repoSlug]/loading.tsx @@ -0,0 +1,16 @@ +import { Skeleton } from '@/components/ui/skeleton' + +export default function Loading() { + return ( +
+ + + +
+ + + +
+
+ ) +} diff --git a/src/app/[ownerSlug]/[repoSlug]/page.tsx b/src/app/[ownerSlug]/[repoSlug]/page.tsx new file mode 100644 index 0000000..509604c --- /dev/null +++ b/src/app/[ownerSlug]/[repoSlug]/page.tsx @@ -0,0 +1,60 @@ +import { JSX, Suspense } from 'react' +import { RepoCard } from '@/components/RepoCard' +import Loading from '@/app/[ownerSlug]/[repoSlug]/loading' +import { FetchRepoService, FullRepository } from '@/db/get-db' +import { MarkdownContent } from '@/components/MarkdownContent' +import { notFound } from 'next/navigation' + +interface PageProps { + params: { + ownerSlug: string + repoSlug: string + } +} + +interface RepoPageProps { + ownerSlug: string + repoSlug: string +} + +const placeholder = ` +# parser.cpp\n\n + +- Reference \`parser/parser.cpp\`\n + +This file defines the \`Parser\` class, responsible for transforming a stream of tokens into an Abstract Syntax Tree (AST). The parser utilizes a queue of \`TokenPtr\` objects (\`tok_queue_\`) and provides methods for consuming tokens (\`Eat\`), peeking at the next token (\`Peek\`), and expecting specific token types (\`ExpectedTokenType\`). The core functionality resides in \`ProduceAST\` which drives the parsing process by repeatedly calling \`ParseStatement\` until an end-of-line token is encountered. Different parsing methods are present to handle various expressions such as \`ParsePrimaryExpression\`, \`ParseAdditionExpression\`, \`ParseMultiplicationExpression\`, and \`ParseComparisonExpression\`. It supports variable declarations and assignments, and also handles whitespace using \`ParseWhitespaceExpression\`. The parser uses recursive descent parsing strategy with helper functions for each type of expression. It throws \`UnexpectedTokenParsedException\` when unexpected tokens are encountered. The \`Parser\` class depends on the \`TokenPtr\` and the \`ast.hpp\` module for the AST node definitions. Key methods include: [\`Eat\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L11-L15) for consuming tokens, [\`Peek\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L17-L17) for peeking at tokens, [\`ExpectedTokenType\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L19-L39) for validating token types, [\`ProduceAST\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L41-L53) for generating the AST, [\`ParseStatement\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L55-L62) for parsing statements, and [\`ParseExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L64-L66) for parsing expressions. The file also handles different types of expressions using separate parsing methods like [\`ParsePrimaryExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L68-L145), [\`ParseAdditionExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L147-L165), [\`ParseMultiplicationExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L167-L185), [\`ParseWhitespaceExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L187-L192), [\`ParseIdentifierDeclarationExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L194-L217), [\`ParseIdentifierAssignmentExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L219-L243), and [\`ParseComparisonExpression\`](https://github.com/daeisbae/AParser/blob/9bbea84efa9f8eeed5576f53e4a65ca87a7f023c/parser/parser.cpp#L245-L263). +` + +async function RepoPage({ + ownerSlug, + repoSlug, +}: RepoPageProps): Promise { + const fetchRepoService = new FetchRepoService() + const repoDetails: FullRepository | null = + await fetchRepoService.getFullRepositoryTree(ownerSlug, repoSlug) + + if (!repoDetails) { + notFound() + } + + return ( +
+
+ +
+
+ +
+
+ ) +} + +export default function DocumentationPage({ params }: PageProps) { + const { ownerSlug, repoSlug } = params + + return ( + }> + + + ) +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..755899c --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,49 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer base { + :root { + --radius: 0.5rem; + } +} + +h1 { + font-size: 48px; + font-weight: bold; +} + +h2 { + font-size: 40px; + font-weight: bold; +} + +h3 { + font-size: 32px; + font-weight: bold; +} + +h4 { + font-size: 24px; + font-weight: bold; +} + +h5 { + font-size: 20px; + font-weight: bold; +} + +ul > li { + padding: 20px 0px; +} + +a > code { + background-color: lightblue; + color:blue; + padding: 2px 4px; + border-radius: 4px; +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..0130f4a --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,22 @@ +import './globals.css' +import Navbar from '@/components/NavBar' + +export const metadata = { + title: 'OpenRepoWiki', + description: 'A Wikipedia of Github Repositories of how it is made.', +} + +interface RootLayoutProps { + children: React.ReactNode +} + +export default function RootLayout({ children } : RootLayoutProps) { + return ( + + + +
{children}
+ + + ) +} diff --git a/src/components/MarkdownContent.tsx b/src/components/MarkdownContent.tsx new file mode 100644 index 0000000..9ab2543 --- /dev/null +++ b/src/components/MarkdownContent.tsx @@ -0,0 +1,33 @@ +'use client' + +import { MDXRemote } from 'next-mdx-remote' +import { serialize } from 'next-mdx-remote/serialize' +import { useState, useEffect } from 'react' +import React from 'react' +import { Skeleton } from '@/components/ui/skeleton' + +interface MarkdownContentProps { + content: string +} + +export function MarkdownContent({ content }: MarkdownContentProps) { + const [serializedContent, setSerializedContent] = useState(null) + + useEffect(() => { + const serializeMarkdown = async () => { + const serialized = await serialize(content) + setSerializedContent(serialized) + } + serializeMarkdown() + }, [content]) + + if (!serializedContent) { + return + } + + return ( +
+ +
+ ) +} diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx new file mode 100644 index 0000000..b1cd8f3 --- /dev/null +++ b/src/components/NavBar.tsx @@ -0,0 +1,64 @@ +import * as React from 'react' +import Link from 'next/link' + +import { cn } from '@/lib/utils' + +import { + NavigationMenu, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + navigationMenuTriggerStyle, +} from '@/components/ui/navigation-menu' + +export default function Navbar() { + return ( +
+
+ + OpenRepoWiki + + + + + + + Repo + + + + + + + Blogs + + + + + + + About + + + + + +
+
+ ) +} diff --git a/src/components/RepoCard.tsx b/src/components/RepoCard.tsx new file mode 100644 index 0000000..867cd0a --- /dev/null +++ b/src/components/RepoCard.tsx @@ -0,0 +1,66 @@ +import { Card, CardHeader, CardContent, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { Star, GitFork } from 'lucide-react' +import React from 'react' + +export interface RepoDetails { + owner: string + repo: string + descriptions: string + stars: number + forks: number + url: string + default_branch: string + topics: string[] +} + +export interface RepoCardProps { + repoInfo: RepoDetails +} + +export function RepoCard({ repoInfo }: RepoCardProps) { + return ( + + + + {repoInfo.owner}/{repoInfo.repo} + + + +

+ {repoInfo.descriptions} +

+
+
+ + {repoInfo.stars} +
+
+ + {repoInfo.forks} +
+
+ + View on GitHub + + +
+

Topics

+
+ {repoInfo.topics.map((topic, index) => ( + + {topic} + + ))} +
+
+
+
+ ) +} diff --git a/src/components/ui/badge.jsx b/src/components/ui/badge.jsx new file mode 100644 index 0000000..7a559fe --- /dev/null +++ b/src/components/ui/badge.jsx @@ -0,0 +1,34 @@ +import * as React from "react" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border border-neutral-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 dark:border-neutral-800 dark:focus:ring-neutral-300", + { + variants: { + variant: { + default: + "border-transparent bg-neutral-900 text-neutral-50 shadow hover:bg-neutral-900/80 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/80", + secondary: + "border-transparent bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", + destructive: + "border-transparent bg-red-500 text-neutral-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/80", + outline: "text-neutral-950 dark:text-neutral-50", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + ...props +}) { + return (
); +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/card.jsx b/src/components/ui/card.jsx new file mode 100644 index 0000000..0cbde16 --- /dev/null +++ b/src/components/ui/card.jsx @@ -0,0 +1,53 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/components/ui/navigation-menu.jsx b/src/components/ui/navigation-menu.jsx new file mode 100644 index 0000000..807c2b5 --- /dev/null +++ b/src/components/ui/navigation-menu.jsx @@ -0,0 +1,104 @@ +import * as React from "react" +import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" +import { cva } from "class-variance-authority" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const NavigationMenu = React.forwardRef(({ className, children, ...props }, ref) => ( + + {children} + + +)) +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName + +const NavigationMenuList = React.forwardRef(({ className, ...props }, ref) => ( + +)) +NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName + +const NavigationMenuItem = NavigationMenuPrimitive.Item + +const navigationMenuTriggerStyle = cva( + "group inline-flex h-9 w-max items-center justify-center rounded-md bg-white px-4 py-2 text-sm font-medium transition-colors hover:bg-neutral-100 hover:text-neutral-900 focus:bg-neutral-100 focus:text-neutral-900 focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-neutral-100/50 data-[state=open]:bg-neutral-100/50 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[active]:bg-neutral-800/50 dark:data-[state=open]:bg-neutral-800/50" +) + +const NavigationMenuTrigger = React.forwardRef(({ className, children, ...props }, ref) => ( + + {children}{""} + +)) +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName + +const NavigationMenuContent = React.forwardRef(({ className, ...props }, ref) => ( + +)) +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName + +const NavigationMenuLink = NavigationMenuPrimitive.Link + +const NavigationMenuViewport = React.forwardRef(({ className, ...props }, ref) => ( +
+ +
+)) +NavigationMenuViewport.displayName = + NavigationMenuPrimitive.Viewport.displayName + +const NavigationMenuIndicator = React.forwardRef(({ className, ...props }, ref) => ( + +
+ +)) +NavigationMenuIndicator.displayName = + NavigationMenuPrimitive.Indicator.displayName + +export { + navigationMenuTriggerStyle, + NavigationMenu, + NavigationMenuList, + NavigationMenuItem, + NavigationMenuContent, + NavigationMenuTrigger, + NavigationMenuLink, + NavigationMenuIndicator, + NavigationMenuViewport, +} diff --git a/src/components/ui/separator.jsx b/src/components/ui/separator.jsx new file mode 100644 index 0000000..1c5bf64 --- /dev/null +++ b/src/components/ui/separator.jsx @@ -0,0 +1,25 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef(( + { className, orientation = "horizontal", decorative = true, ...props }, + ref +) => ( + +)) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/src/components/ui/skeleton.jsx b/src/components/ui/skeleton.jsx new file mode 100644 index 0000000..5bf6563 --- /dev/null +++ b/src/components/ui/skeleton.jsx @@ -0,0 +1,17 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}) { + return ( + (
) + ); +} + +export { Skeleton } diff --git a/src/db/get-db.ts b/src/db/get-db.ts new file mode 100644 index 0000000..c8d999f --- /dev/null +++ b/src/db/get-db.ts @@ -0,0 +1,73 @@ +import { Repository, RepositoryData } from '@/db/models/repository' +import { Branch, BranchData } from '@/db/models/branch' +import { Folder, FolderData } from '@/db/models/folder' +import { File, FileData } from '@/db/models/file' + +export interface FullFolder extends FolderData { + files: FileData[] + subfolders: FullFolder[] +} + +export interface FullRepository { + repository: RepositoryData + branch: BranchData | null + folders: FullFolder[] +} + +export class FetchRepoService { + private repository = new Repository() + private branch = new Branch() + private folder = new Folder() + private file = new File() + + async getFullRepositoryTree( + owner: string, + repo: string + ): Promise { + const repositoryData = await this.repository.select(owner, repo) + + if (!repositoryData) return null + + const branchData = await this.branch.select(repositoryData.url) + const folders: FullFolder[] = branchData + ? await this.getFoldersRecursively(branchData.branch_id, null) + : [] + + // if (!branchData) { + // throw new Error('No branch found for the repository') + // } + + return { + repository: repositoryData, + branch: branchData, + folders, + } + } + + private async getFoldersRecursively( + branchId: number, + parentFolderId: number | null + ): Promise { + const allFolders = await this.folder.select(branchId) + if (!allFolders) throw new Error('No folders found for the repository') + + const currentLevel = allFolders.filter( + (f) => f.parent_folder_id === parentFolderId + ) + + const foldersWithChildren: FullFolder[] = [] + for (const folder of currentLevel) { + const subfolders = await this.getFoldersRecursively( + branchId, + folder.folder_id + ) + const files = await this.file.select(folder.folder_id) + foldersWithChildren.push({ + ...folder, + files, + subfolders, + }) + } + return foldersWithChildren + } +} diff --git a/src/db/models/repository.ts b/src/db/models/repository.ts index f7afaa7..0264ea9 100644 --- a/src/db/models/repository.ts +++ b/src/db/models/repository.ts @@ -13,7 +13,7 @@ export interface RepositoryData { } export class Repository { - async select(owner: string, repo: string): Promise { + async select(owner: string, repo: string): Promise { const queryRepo = 'SELECT * FROM Repository WHERE owner = $1 AND repo = $2' const queryTopics = @@ -21,6 +21,9 @@ export class Repository { const repoResult = (await dbConn.query(queryRepo, [owner, repo])) .rows[0] + if (!repoResult) { + return null + } const topicsResult = await dbConn.query(queryTopics, [repoResult.url]) return { diff --git a/src/lib/utils.js b/src/lib/utils.js new file mode 100644 index 0000000..0601ee4 --- /dev/null +++ b/src/lib/utils.js @@ -0,0 +1,6 @@ +import { clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs) { + return twMerge(clsx(inputs)) +}