├── .gitignore ├── README.md ├── backend └── api.py └── frontend ├── app ├── api │ └── clean-log │ │ └── route.ts ├── favicon.ico ├── globals.css ├── layout.tsx └── page.tsx ├── components.json ├── components ├── repo-chat-dashboard.tsx ├── theme-provider.tsx └── ui │ ├── accordion.tsx │ ├── alert-dialog.tsx │ ├── alert.tsx │ ├── aspect-ratio.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── breadcrumb.tsx │ ├── button.tsx │ ├── calendar.tsx │ ├── card.tsx │ ├── carousel.tsx │ ├── chart.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── command.tsx │ ├── context-menu.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── hover-card.tsx │ ├── input-otp.tsx │ ├── input.tsx │ ├── label.tsx │ ├── menubar.tsx │ ├── navigation-menu.tsx │ ├── pagination.tsx │ ├── popover.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── resizable.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── sidebar.tsx │ ├── skeleton.tsx │ ├── slider.tsx │ ├── sonner.tsx │ ├── switch.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── toggle-group.tsx │ ├── toggle.tsx │ ├── tooltip.tsx │ ├── use-mobile.tsx │ └── use-toast.ts ├── hooks ├── use-mobile.tsx └── use-toast.ts ├── lib └── utils.ts ├── next-env.d.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── cg.png ├── codegen.png ├── placeholder-logo.png ├── placeholder-logo.svg ├── placeholder-user.jpg ├── placeholder.jpg └── placeholder.svg ├── styles └── globals.css ├── tailwind.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | frontend/node_modules 2 | frontend/.next 3 | backend/.venv 4 | backend/__pycache__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codegen Deep Research 2 | 3 | A code research tool that enables users to understand codebases through agentic AI analysis. The project combines a Modal-based FastAPI backend with a Next.js frontend to provide intelligent code exploration capabilities. 4 | 5 | Users submit a GitHub repository and research query through the frontend. The Modal API processes the request using an AI agent equipped with specialized code analysis tools. The agent explores the codebase using various tools (search, symbol analysis, etc.) and results are returned to the frontend for display. 6 | 7 | ## How it Works 8 | 9 | ### Backend (Modal API) 10 | 11 | The backend is built using [Modal](https://modal.com/) and [FastAPI](https://fastapi.tiangolo.com/), providing a serverless API endpoint for code research. 12 | 13 | There is a main API endpoint that handles code research requests. It uses the `codegen` library for codebase analysis. 14 | 15 | The agent investigates the codebase through various research tools: 16 | - `ViewFileTool`: Read file contents 17 | - `ListDirectoryTool`: Explore directory structures 18 | - `SearchTool`: Text-based code search 19 | - `SemanticSearchTool`: AI-powered semantic code search 20 | - `RevealSymbolTool`: Analyze code symbols and relationships 21 | 22 | ```python 23 | tools = [ 24 | ViewFileTool(codebase), 25 | ListDirectoryTool(codebase), 26 | SearchTool(codebase), 27 | SemanticSearchTool(codebase), 28 | RevealSymbolTool(codebase) 29 | ] 30 | 31 | # Initialize agent with research tools 32 | agent = create_agent_with_tools( 33 | codebase=codebase, 34 | tools=tools, 35 | chat_history=[SystemMessage(content=RESEARCH_AGENT_PROMPT)], 36 | verbose=True 37 | ) 38 | ``` 39 | 40 | ### Frontend (Next.js) 41 | 42 | The frontend provides an interface for users to submit a GitHub repository and research query. The components come from the [shadcn/ui](https://ui.shadcn.com/) library. This triggers the Modal API to perform the code research and returns the results to the frontend. 43 | 44 | ## Getting Started 45 | 46 | 1. Set up environment variables in an `.env` file: 47 | ``` 48 | OPENAI_API_KEY=your_key_here 49 | ``` 50 | 51 | 2. Deploy or serve the Modal API: 52 | ```bash 53 | modal serve backend/api.py 54 | ``` 55 | `modal serve` runs the API locally for development, creating a temporary endpoint that's active only while the command is running. 56 | ```bash 57 | modal deploy backend/api.py 58 | ``` 59 | `modal deploy` creates a persistent Modal app and deploys the FastAPI app to it, generating a permanent API endpoint. 60 | 61 | After deployment, you'll need to update the API endpoint in the frontend configuration to point to your deployed Modal app URL. 62 | 63 | 3. Run the Next.js frontend: 64 | ```bash 65 | cd frontend 66 | npm install 67 | npm run dev 68 | ``` 69 | 70 | ## Learn More 71 | 72 | More information about the `codegen` library can be found [here](https://codegen.com/). 73 | 74 | For details on the agent implementation, check out [Deep Code Research with AI](https://docs.codegen.com/tutorials/deep-code-research) from the Codegen docs. This tutorial provides an in-depth guide on how the research agent is created. -------------------------------------------------------------------------------- /backend/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | import modal 4 | from codegen import Codebase 5 | from codegen.extensions.langchain.agent import create_agent_with_tools 6 | from codegen.extensions.langchain.tools import ( 7 | ListDirectoryTool, 8 | RevealSymbolTool, 9 | SearchTool, 10 | SemanticSearchTool, 11 | ViewFileTool, 12 | ) 13 | from langchain_core.messages import SystemMessage 14 | from fastapi.middleware.cors import CORSMiddleware 15 | from codegen.extensions.index.file_index import FileIndex 16 | import os 17 | from typing import List 18 | from fastapi.responses import StreamingResponse 19 | import json 20 | 21 | image = ( 22 | modal.Image.debian_slim() 23 | .apt_install("git") 24 | .pip_install( 25 | "codegen==0.22.1", 26 | "fastapi", 27 | "uvicorn", 28 | "langchain", 29 | "langchain-core", 30 | "pydantic", 31 | ) 32 | ) 33 | 34 | app = modal.App( 35 | name="code-research-app", 36 | image=image, 37 | secrets=[modal.Secret.from_name("agent-secret")], 38 | ) 39 | 40 | fastapi_app = FastAPI() 41 | 42 | fastapi_app.add_middleware( 43 | CORSMiddleware, 44 | allow_origins=["*"], 45 | allow_credentials=True, 46 | allow_methods=["*"], 47 | allow_headers=["*"], 48 | ) 49 | 50 | # Research agent prompt 51 | RESEARCH_AGENT_PROMPT = """You are a code research expert. Your goal is to help users understand codebases by: 52 | 1. Finding relevant code through semantic and text search 53 | 2. Analyzing symbol relationships and dependencies 54 | 3. Exploring directory structures 55 | 4. Reading and explaining code 56 | 57 | Always explain your findings in detail and provide context about how different parts of the code relate to each other. 58 | When analyzing code, consider: 59 | - The purpose and functionality of each component 60 | - How different parts interact 61 | - Key patterns and design decisions 62 | - Potential areas for improvement 63 | 64 | Break down complex concepts into understandable pieces and use examples when helpful.""" 65 | 66 | current_status = "Intializing process..." 67 | 68 | 69 | def update_status(new_status: str): 70 | global current_status 71 | current_status = new_status 72 | return {"type": "status", "content": new_status} 73 | 74 | 75 | class ResearchRequest(BaseModel): 76 | repo_name: str 77 | query: str 78 | 79 | 80 | class ResearchResponse(BaseModel): 81 | response: str 82 | 83 | 84 | class FilesResponse(BaseModel): 85 | files: List[str] 86 | 87 | 88 | class StatusResponse(BaseModel): 89 | status: str 90 | 91 | 92 | # @fastapi_app.post("/files", response_model=ResearchResponse) 93 | # async def files(request: ResearchRequest) -> ResearchResponse: 94 | # codebase = Codebase.from_repo(request.repo_name) 95 | 96 | # file_index = FileIndex(codebase) 97 | # file_index.create() 98 | 99 | # similar_files = file_index.similarity_search(request.query, k=5) 100 | 101 | # similar_file_names = [file.filepath for file, score in similar_files] 102 | # return FilesResponse(files=similar_file_names) 103 | 104 | 105 | @fastapi_app.post("/research", response_model=ResearchResponse) 106 | async def research(request: ResearchRequest) -> ResearchResponse: 107 | """ 108 | Endpoint to perform code research on a GitHub repository. 109 | """ 110 | try: 111 | update_status("Initializing codebase...") 112 | codebase = Codebase.from_repo(request.repo_name) 113 | 114 | update_status("Creating research tools...") 115 | tools = [ 116 | ViewFileTool(codebase), 117 | ListDirectoryTool(codebase), 118 | SearchTool(codebase), 119 | SemanticSearchTool(codebase), 120 | RevealSymbolTool(codebase), 121 | ] 122 | 123 | update_status("Initializing research agent...") 124 | agent = create_agent_with_tools( 125 | codebase=codebase, 126 | tools=tools, 127 | chat_history=[SystemMessage(content=RESEARCH_AGENT_PROMPT)], 128 | verbose=True, 129 | ) 130 | 131 | update_status("Running analysis...") 132 | result = agent.invoke( 133 | {"input": request.query}, 134 | config={"configurable": {"session_id": "research"}}, 135 | ) 136 | 137 | update_status("Complete") 138 | return ResearchResponse(response=result["output"]) 139 | 140 | except Exception as e: 141 | update_status("Error occurred") 142 | return ResearchResponse(response=f"Error during research: {str(e)}") 143 | 144 | 145 | @fastapi_app.post("/similar-files", response_model=FilesResponse) 146 | async def similar_files(request: ResearchRequest) -> FilesResponse: 147 | """ 148 | Endpoint to find similar files in a GitHub repository based on a query. 149 | """ 150 | try: 151 | codebase = Codebase.from_repo(request.repo_name) 152 | file_index = FileIndex(codebase) 153 | file_index.create() 154 | similar_files = file_index.similarity_search(request.query, k=5) 155 | similar_file_names = [file.filepath for file, score in similar_files] 156 | return FilesResponse(files=similar_file_names) 157 | 158 | except Exception as e: 159 | update_status("Error occurred") 160 | return FilesResponse(files=[f"Error finding similar files: {str(e)}"]) 161 | 162 | 163 | @app.function() 164 | async def get_similar_files(repo_name: str, query: str) -> List[str]: 165 | """ 166 | Separate Modal function to find similar files 167 | """ 168 | codebase = Codebase.from_repo(repo_name) 169 | file_index = FileIndex(codebase) 170 | file_index.create() 171 | similar_files = file_index.similarity_search(query, k=6) 172 | return [file.filepath for file, score in similar_files if score > 0.2] 173 | 174 | 175 | @fastapi_app.post("/research/stream") 176 | async def research_stream(request: ResearchRequest): 177 | """ 178 | Streaming endpoint to perform code research on a GitHub repository. 179 | """ 180 | try: 181 | 182 | async def event_generator(): 183 | final_response = "" 184 | 185 | similar_files_future = get_similar_files.remote.aio( 186 | request.repo_name, request.query 187 | ) 188 | 189 | codebase = Codebase.from_repo(request.repo_name) 190 | tools = [ 191 | ViewFileTool(codebase), 192 | ListDirectoryTool(codebase), 193 | SearchTool(codebase), 194 | SemanticSearchTool(codebase), 195 | RevealSymbolTool(codebase), 196 | ] 197 | 198 | agent = create_agent_with_tools( 199 | codebase=codebase, 200 | tools=tools, 201 | chat_history=[SystemMessage(content=RESEARCH_AGENT_PROMPT)], 202 | verbose=True, 203 | ) 204 | 205 | research_task = agent.astream_events( 206 | {"input": request.query}, 207 | version="v1", 208 | config={"configurable": {"session_id": "research"}}, 209 | ) 210 | 211 | similar_files = await similar_files_future 212 | yield f"data: {json.dumps({'type': 'similar_files', 'content': similar_files})}\n\n" 213 | 214 | async for event in research_task: 215 | kind = event["event"] 216 | if kind == "on_chat_model_stream": 217 | content = event["data"]["chunk"].content 218 | if content: 219 | final_response += content 220 | yield f"data: {json.dumps({'type': 'content', 'content': content})}\n\n" 221 | elif kind in ["on_tool_start", "on_tool_end"]: 222 | yield f"data: {json.dumps({'type': kind, 'data': event['data']})}\n\n" 223 | 224 | yield f"data: {json.dumps({'type': 'complete', 'content': final_response})}\n\n" 225 | 226 | return StreamingResponse( 227 | event_generator(), 228 | media_type="text/event-stream", 229 | ) 230 | 231 | except Exception as e: 232 | error_status = update_status("Error occurred") 233 | return StreamingResponse( 234 | iter( 235 | [ 236 | f"data: {json.dumps(error_status)}\n\n", 237 | f"data: {json.dumps({'type': 'error', 'content': str(e)})}\n\n", 238 | ] 239 | ), 240 | media_type="text/event-stream", 241 | ) 242 | 243 | 244 | @app.function(image=image, secrets=[modal.Secret.from_name("agent-secret")]) 245 | @modal.asgi_app() 246 | def fastapi_modal_app(): 247 | return fastapi_app 248 | 249 | 250 | if __name__ == "__main__": 251 | app.deploy("code-research-app") 252 | -------------------------------------------------------------------------------- /frontend/app/api/clean-log/route.ts: -------------------------------------------------------------------------------- 1 | import OpenAI from 'openai'; 2 | import { NextResponse } from 'next/server'; 3 | 4 | const client = new OpenAI({ 5 | apiKey: process.env.OPENAI_API_KEY, 6 | }); 7 | 8 | export async function POST(request: Request) { 9 | try { 10 | const { logData } = await request.json(); 11 | 12 | const chatCompletion = await client.chat.completions.create({ 13 | messages: [ 14 | { 15 | role: 'system', 16 | content: 'Make this a one line description of what is happening or guess what is happening with this software agent. Be confident and concise, phrasing it like "Running a process to do X" or "Investigating X to do Y". No parenthesis in output.' 17 | }, 18 | { role: 'user', content: logData } 19 | ], 20 | model: 'gpt-4o-mini', 21 | }); 22 | 23 | return NextResponse.json({ 24 | content: chatCompletion.choices[0].message.content 25 | }); 26 | } catch (error) { 27 | console.error('Error in clean-log API route:', error); 28 | return NextResponse.json( 29 | { error: 'Failed to process log' }, 30 | { status: 500 } 31 | ); 32 | } 33 | } -------------------------------------------------------------------------------- /frontend/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codegen-sh/deep-research/c966d5e01849432d6b3b650752e69c1c3023e13b/frontend/app/favicon.ico -------------------------------------------------------------------------------- /frontend/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: 'Inter', sans-serif; 7 | } 8 | 9 | @layer utilities { 10 | .text-balance { 11 | text-wrap: balance; 12 | } 13 | } 14 | 15 | @layer base { 16 | :root { 17 | --background: 0 0% 100%; 18 | --foreground: 0 0% 3.9%; 19 | --card: 0 0% 100%; 20 | --card-foreground: 0 0% 3.9%; 21 | --popover: 0 0% 100%; 22 | --popover-foreground: 0 0% 3.9%; 23 | --primary: 0 0% 9%; 24 | --primary-foreground: 0 0% 98%; 25 | --secondary: 0 0% 96.1%; 26 | --secondary-foreground: 0 0% 9%; 27 | --muted: 0 0% 96.1%; 28 | --muted-foreground: 0 0% 45.1%; 29 | --accent: 0 0% 96.1%; 30 | --accent-foreground: 0 0% 9%; 31 | --destructive: 0 84.2% 60.2%; 32 | --destructive-foreground: 0 0% 98%; 33 | --border: 0 0% 89.8%; 34 | --input: 0 0% 89.8%; 35 | --ring: 0 0% 3.9%; 36 | --chart-1: 12 76% 61%; 37 | --chart-2: 173 58% 39%; 38 | --chart-3: 197 37% 24%; 39 | --chart-4: 43 74% 66%; 40 | --chart-5: 27 87% 67%; 41 | --radius: 0.5rem; 42 | --sidebar-background: 0 0% 98%; 43 | --sidebar-foreground: 240 5.3% 26.1%; 44 | --sidebar-primary: 240 5.9% 10%; 45 | --sidebar-primary-foreground: 0 0% 98%; 46 | --sidebar-accent: 240 4.8% 95.9%; 47 | --sidebar-accent-foreground: 240 5.9% 10%; 48 | --sidebar-border: 220 13% 91%; 49 | --sidebar-ring: 217.2 91.2% 59.8%; 50 | } 51 | .dark { 52 | --background: 0 0% 3.9%; 53 | --foreground: 0 0% 98%; 54 | --card: 0 0% 3.9%; 55 | --card-foreground: 0 0% 98%; 56 | --popover: 0 0% 3.9%; 57 | --popover-foreground: 0 0% 98%; 58 | --primary: 0 0% 98%; 59 | --primary-foreground: 0 0% 9%; 60 | --secondary: 0 0% 14.9%; 61 | --secondary-foreground: 0 0% 98%; 62 | --muted: 0 0% 14.9%; 63 | --muted-foreground: 0 0% 63.9%; 64 | --accent: 0 0% 14.9%; 65 | --accent-foreground: 0 0% 98%; 66 | --destructive: 0 62.8% 30.6%; 67 | --destructive-foreground: 0 0% 98%; 68 | --border: 0 0% 14.9%; 69 | --input: 0 0% 14.9%; 70 | --ring: 0 0% 83.1%; 71 | --chart-1: 220 70% 50%; 72 | --chart-2: 160 60% 45%; 73 | --chart-3: 30 80% 55%; 74 | --chart-4: 280 65% 60%; 75 | --chart-5: 340 75% 55%; 76 | --sidebar-background: 240 5.9% 10%; 77 | --sidebar-foreground: 240 4.8% 95.9%; 78 | --sidebar-primary: 224.3 76.3% 48%; 79 | --sidebar-primary-foreground: 0 0% 100%; 80 | --sidebar-accent: 240 3.7% 15.9%; 81 | --sidebar-accent-foreground: 240 4.8% 95.9%; 82 | --sidebar-border: 240 3.7% 15.9%; 83 | --sidebar-ring: 217.2 91.2% 59.8%; 84 | } 85 | } 86 | 87 | @layer base { 88 | * { 89 | @apply border-border; 90 | } 91 | body { 92 | @apply bg-background text-foreground; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /frontend/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css" 2 | import type { Metadata } from "next" 3 | import type React from "react" // Import React 4 | 5 | import { ThemeProvider } from "@/components/theme-provider" 6 | 7 | export const metadata: Metadata = { 8 | title: "Codebase Analytics Dashboard", 9 | description: "Analytics dashboard for public GitHub repositories", 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | 20 | 21 | {children} 22 | 23 | 24 | 25 | ) 26 | } 27 | 28 | 29 | 30 | import './globals.css' -------------------------------------------------------------------------------- /frontend/app/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next" 2 | import RepoChatDashboard from "@/components/repo-chat-dashboard" 3 | 4 | export const metadata: Metadata = { 5 | title: "Deep Research", 6 | description: "Chat with your codebase" 7 | } 8 | 9 | export default function Page() { 10 | return 11 | } 12 | 13 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /frontend/components/repo-chat-dashboard.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState, useEffect} from "react" 4 | import { Github, ArrowRight, FileText } from "lucide-react" 5 | import { Button } from "@/components/ui/button" 6 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" 7 | import { Input } from "@/components/ui/input" 8 | import ReactMarkdown from 'react-markdown' 9 | 10 | async function cleanLogWithGPT4Mini(logData: string): Promise { 11 | try { 12 | const response = await fetch('/api/clean-log', { 13 | method: 'POST', 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | }, 17 | body: JSON.stringify({ logData }), 18 | }); 19 | 20 | if (!response.ok) { 21 | throw new Error('Failed to clean log'); 22 | } 23 | 24 | const data = await response.json(); 25 | return data.content || logData; 26 | } catch (error) { 27 | console.error('Error cleaning log:', error); 28 | return logData; 29 | } 30 | } 31 | 32 | export default function RepoChatDashboard() { 33 | const [repoUrl, setRepoUrl] = useState("") 34 | const [question, setQuestion] = useState("") 35 | const [isLoading, setIsLoading] = useState(false) 36 | const [isLandingPage, setIsLandingPage] = useState(true) 37 | const [researchResult, setResearchResult] = useState("") 38 | const [showQueryInput, setShowQueryInput] = useState(false) 39 | const [isTransitioning, setIsTransitioning] = useState(false) 40 | const [logs, setLogs] = useState([]) 41 | const [similarFiles, setSimilarFiles] = useState([]) 42 | 43 | useEffect(() => { 44 | if (repoUrl) { 45 | setShowQueryInput(true) 46 | } else { 47 | setShowQueryInput(false) 48 | } 49 | }, [repoUrl]) 50 | 51 | 52 | const parseRepoUrl = (input: string): string => { 53 | if (input.includes('github.com')) { 54 | const url = new URL(input) 55 | const pathParts = url.pathname.split('/').filter(Boolean) 56 | if (pathParts.length >= 2) { 57 | return `${pathParts[0]}/${pathParts[1]}` 58 | } 59 | } 60 | return input 61 | } 62 | 63 | const handleSubmit = async () => { 64 | if (!repoUrl) { 65 | alert('Please enter a repository URL'); 66 | return; 67 | } 68 | setIsLoading(true); 69 | setIsLandingPage(false); 70 | setResearchResult(""); 71 | setLogs([]); 72 | setSimilarFiles([]); 73 | 74 | setLogs(["Fetching codebase"]); 75 | await new Promise(resolve => setTimeout(resolve, 2000)); 76 | 77 | setLogs(prev => [...prev, "Initializing research tools for agent"]); 78 | await new Promise(resolve => setTimeout(resolve, 1500)); 79 | 80 | try { 81 | const parsedRepoUrl = parseRepoUrl(repoUrl); 82 | 83 | if (question) { 84 | setLogs(prev => [...prev, "Looking through files"]); 85 | 86 | const response = await fetch('https://codegen-sh--code-research-app-fastapi-modal-app.modal.run/research/stream', { 87 | method: 'POST', 88 | headers: { 89 | 'Content-Type': 'application/json', 90 | }, 91 | body: JSON.stringify({ 92 | repo_name: parsedRepoUrl, 93 | query: question 94 | }) 95 | }); 96 | 97 | if (!response.ok) { 98 | throw new Error('Failed to fetch research results'); 99 | } 100 | 101 | const reader = response.body?.getReader(); 102 | const decoder = new TextDecoder(); 103 | let partialLine = ''; 104 | 105 | if (!reader) { 106 | throw new Error('Failed to get response reader'); 107 | } 108 | 109 | try { 110 | while (true) { 111 | const { value, done } = await reader.read(); 112 | if (done) break; 113 | 114 | const chunk = partialLine + decoder.decode(value, { stream: true }); 115 | const lines = chunk.split('\n'); 116 | 117 | partialLine = lines[lines.length - 1]; 118 | 119 | for (let i = 0; i < lines.length - 1; i++) { 120 | const line = lines[i].trim(); 121 | if (line.startsWith('data: ')) { 122 | try { 123 | const eventData = JSON.parse(line.slice(6)); 124 | if (eventData.type === 'similar_files') { 125 | setSimilarFiles(eventData.content); 126 | setLogs(prev => [...prev, "Starting agent run"]); 127 | } else if (eventData.type === 'content') { 128 | setResearchResult(prev => prev + eventData.content); 129 | } else if (eventData.type === 'error') { 130 | setResearchResult(`Error: ${eventData.content}`); 131 | setIsLoading(false); 132 | return; 133 | } else if (eventData.type === 'complete') { 134 | setResearchResult(eventData.content); 135 | setIsLoading(false); 136 | setLogs(prev => [...prev, "Analysis complete"]); 137 | return; 138 | } else if (['on_tool_start', 'on_tool_end'].includes(eventData.type)) { 139 | const cleanedLog = await cleanLogWithGPT4Mini(JSON.stringify(eventData.data)); 140 | setLogs(prev => [...prev, cleanedLog]); 141 | } 142 | } catch (e) { 143 | console.error('Error parsing event:', e, line); 144 | } 145 | } 146 | } 147 | } 148 | } finally { 149 | reader.releaseLock(); 150 | } 151 | } 152 | } catch (error) { 153 | console.error('Error:', error); 154 | setResearchResult("Error: Failed to process request. Please try again."); 155 | } finally { 156 | setIsLoading(false); 157 | } 158 | }; 159 | 160 | const handleKeyPress = (e: React.KeyboardEvent) => { 161 | if (e.key === 'Enter') { 162 | handleSubmit(); 163 | } 164 | } 165 | 166 | return ( 167 |
168 |
172 |
175 |
176 |

177 | CG Logo 178 | Deep Research 179 |

180 |

181 | Unlock the power of Codegen in codebase exploration. 182 |

183 |
184 |
185 | setRepoUrl(e.target.value)} 190 | className="flex-1 h-25 text-lg px-4 mb-2 bg-[#050505] text-muted-foreground" 191 | title="Format: https://github.com/owner/repo or owner/repo" 192 | /> 193 |
198 | setQuestion(e.target.value)} 203 | onKeyPress={handleKeyPress} 204 | className="flex-1 h-25 text-lg px-4 mb-2 bg-[#050505] text-muted-foreground" 205 | /> 206 |
207 |
208 | 217 |
218 |
219 |
220 |
221 |
225 |
228 |
229 |
setIsLandingPage(true)} 232 | > 233 | CG Logo 234 |

Deep Research

235 |
236 | 239 |
240 |

241 |
242 | 243 | 244 | {question || "No query provided"} 245 | 256 | 257 | 258 |
259 | {researchResult && ( 260 |
261 | 262 | 263 | {researchResult} 264 | 265 | 266 |
267 | )} 268 | 269 | {researchResult && ( 270 |
271 |

Relevant Files

272 |
273 | {isLoading && !similarFiles.length ? ( 274 | Array(3).fill(0).map((_, i) => ( 275 | 279 |

Loading...

280 |
281 | )) 282 | ) : similarFiles.length > 0 ? ( 283 | similarFiles.map((file, i) => { 284 | const fileName = file.split('/').pop() || file; 285 | const filePath = file.split('/').slice(0, -1).join('/'); 286 | return ( 287 | window.open(`https://github.com/${parseRepoUrl(repoUrl)}/blob/main/${file}`, '_blank')} 292 | > 293 |
294 |
295 | 296 |
297 |

{fileName}

298 | {filePath && ( 299 |

{filePath}

300 | )} 301 |
302 |
303 |
304 |
305 | ); 306 | }) 307 | ) : ( 308 | Array(6).fill(0).map((_, i) => ( 309 | 313 |
314 |

Example file {i + 1}

315 |
316 |
317 | )) 318 | )} 319 |
320 |
321 | )} 322 | 323 |
324 |

Agent Logs

325 |
326 | {logs.map((log, index) => ( 327 |
332 | {index === logs.length - 1 && isLoading ? ( 333 | CG Logo 339 | ) : ( 340 |
341 | 342 |
343 | )} 344 | {log} 345 |
346 | ))} 347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 | ) 357 | } 358 | -------------------------------------------------------------------------------- /frontend/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { ThemeProvider as NextThemesProvider } from "next-themes" 3 | import type { ThemeProviderProps } from "next-themes" 4 | 5 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 6 | return {children} 7 | } 8 | 9 | -------------------------------------------------------------------------------- /frontend/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )) 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 59 | -------------------------------------------------------------------------------- /frontend/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { buttonVariants } from "@/components/ui/button" 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )) 28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName 29 | 30 | const AlertDialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, ...props }, ref) => ( 34 | 35 | 36 | 44 | 45 | )) 46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName 47 | 48 | const AlertDialogHeader = ({ 49 | className, 50 | ...props 51 | }: React.HTMLAttributes) => ( 52 |
59 | ) 60 | AlertDialogHeader.displayName = "AlertDialogHeader" 61 | 62 | const AlertDialogFooter = ({ 63 | className, 64 | ...props 65 | }: React.HTMLAttributes) => ( 66 |
73 | ) 74 | AlertDialogFooter.displayName = "AlertDialogFooter" 75 | 76 | const AlertDialogTitle = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef 79 | >(({ className, ...props }, ref) => ( 80 | 85 | )) 86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName 87 | 88 | const AlertDialogDescription = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 97 | )) 98 | AlertDialogDescription.displayName = 99 | AlertDialogPrimitive.Description.displayName 100 | 101 | const AlertDialogAction = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName 112 | 113 | const AlertDialogCancel = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 126 | )) 127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName 128 | 129 | export { 130 | AlertDialog, 131 | AlertDialogPortal, 132 | AlertDialogOverlay, 133 | AlertDialogTrigger, 134 | AlertDialogContent, 135 | AlertDialogHeader, 136 | AlertDialogFooter, 137 | AlertDialogTitle, 138 | AlertDialogDescription, 139 | AlertDialogAction, 140 | AlertDialogCancel, 141 | } 142 | -------------------------------------------------------------------------------- /frontend/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /frontend/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /frontend/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /frontend/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /frontend/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { ChevronRight, MoreHorizontal } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Breadcrumb = React.forwardRef< 8 | HTMLElement, 9 | React.ComponentPropsWithoutRef<"nav"> & { 10 | separator?: React.ReactNode 11 | } 12 | >(({ ...props }, ref) =>