├── .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 |
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 |

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 |

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) => )
13 | Breadcrumb.displayName = "Breadcrumb"
14 |
15 | const BreadcrumbList = React.forwardRef<
16 | HTMLOListElement,
17 | React.ComponentPropsWithoutRef<"ol">
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | BreadcrumbList.displayName = "BreadcrumbList"
29 |
30 | const BreadcrumbItem = React.forwardRef<
31 | HTMLLIElement,
32 | React.ComponentPropsWithoutRef<"li">
33 | >(({ className, ...props }, ref) => (
34 |
39 | ))
40 | BreadcrumbItem.displayName = "BreadcrumbItem"
41 |
42 | const BreadcrumbLink = React.forwardRef<
43 | HTMLAnchorElement,
44 | React.ComponentPropsWithoutRef<"a"> & {
45 | asChild?: boolean
46 | }
47 | >(({ asChild, className, ...props }, ref) => {
48 | const Comp = asChild ? Slot : "a"
49 |
50 | return (
51 |
56 | )
57 | })
58 | BreadcrumbLink.displayName = "BreadcrumbLink"
59 |
60 | const BreadcrumbPage = React.forwardRef<
61 | HTMLSpanElement,
62 | React.ComponentPropsWithoutRef<"span">
63 | >(({ className, ...props }, ref) => (
64 |
72 | ))
73 | BreadcrumbPage.displayName = "BreadcrumbPage"
74 |
75 | const BreadcrumbSeparator = ({
76 | children,
77 | className,
78 | ...props
79 | }: React.ComponentProps<"li">) => (
80 | svg]:w-3.5 [&>svg]:h-3.5", className)}
84 | {...props}
85 | >
86 | {children ?? }
87 |
88 | )
89 | BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90 |
91 | const BreadcrumbEllipsis = ({
92 | className,
93 | ...props
94 | }: React.ComponentProps<"span">) => (
95 |
101 |
102 | More
103 |
104 | )
105 | BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106 |
107 | export {
108 | Breadcrumb,
109 | BreadcrumbList,
110 | BreadcrumbItem,
111 | BreadcrumbLink,
112 | BreadcrumbPage,
113 | BreadcrumbSeparator,
114 | BreadcrumbEllipsis,
115 | }
116 |
--------------------------------------------------------------------------------
/frontend/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/frontend/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ChevronLeft, ChevronRight } from "lucide-react"
5 | import { DayPicker } from "react-day-picker"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { buttonVariants } from "@/components/ui/button"
9 |
10 | export type CalendarProps = React.ComponentProps
11 |
12 | function Calendar({
13 | className,
14 | classNames,
15 | showOutsideDays = true,
16 | ...props
17 | }: CalendarProps) {
18 | return (
19 | ,
58 | IconRight: ({ ...props }) => ,
59 | }}
60 | {...props}
61 | />
62 | )
63 | }
64 | Calendar.displayName = "Calendar"
65 |
66 | export { Calendar }
67 |
--------------------------------------------------------------------------------
/frontend/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLDivElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/frontend/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import useEmblaCarousel, {
5 | type UseEmblaCarouselType,
6 | } from "embla-carousel-react"
7 | import { ArrowLeft, ArrowRight } from "lucide-react"
8 |
9 | import { cn } from "@/lib/utils"
10 | import { Button } from "@/components/ui/button"
11 |
12 | type CarouselApi = UseEmblaCarouselType[1]
13 | type UseCarouselParameters = Parameters
14 | type CarouselOptions = UseCarouselParameters[0]
15 | type CarouselPlugin = UseCarouselParameters[1]
16 |
17 | type CarouselProps = {
18 | opts?: CarouselOptions
19 | plugins?: CarouselPlugin
20 | orientation?: "horizontal" | "vertical"
21 | setApi?: (api: CarouselApi) => void
22 | }
23 |
24 | type CarouselContextProps = {
25 | carouselRef: ReturnType[0]
26 | api: ReturnType[1]
27 | scrollPrev: () => void
28 | scrollNext: () => void
29 | canScrollPrev: boolean
30 | canScrollNext: boolean
31 | } & CarouselProps
32 |
33 | const CarouselContext = React.createContext(null)
34 |
35 | function useCarousel() {
36 | const context = React.useContext(CarouselContext)
37 |
38 | if (!context) {
39 | throw new Error("useCarousel must be used within a ")
40 | }
41 |
42 | return context
43 | }
44 |
45 | const Carousel = React.forwardRef<
46 | HTMLDivElement,
47 | React.HTMLAttributes & CarouselProps
48 | >(
49 | (
50 | {
51 | orientation = "horizontal",
52 | opts,
53 | setApi,
54 | plugins,
55 | className,
56 | children,
57 | ...props
58 | },
59 | ref
60 | ) => {
61 | const [carouselRef, api] = useEmblaCarousel(
62 | {
63 | ...opts,
64 | axis: orientation === "horizontal" ? "x" : "y",
65 | },
66 | plugins
67 | )
68 | const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69 | const [canScrollNext, setCanScrollNext] = React.useState(false)
70 |
71 | const onSelect = React.useCallback((api: CarouselApi) => {
72 | if (!api) {
73 | return
74 | }
75 |
76 | setCanScrollPrev(api.canScrollPrev())
77 | setCanScrollNext(api.canScrollNext())
78 | }, [])
79 |
80 | const scrollPrev = React.useCallback(() => {
81 | api?.scrollPrev()
82 | }, [api])
83 |
84 | const scrollNext = React.useCallback(() => {
85 | api?.scrollNext()
86 | }, [api])
87 |
88 | const handleKeyDown = React.useCallback(
89 | (event: React.KeyboardEvent) => {
90 | if (event.key === "ArrowLeft") {
91 | event.preventDefault()
92 | scrollPrev()
93 | } else if (event.key === "ArrowRight") {
94 | event.preventDefault()
95 | scrollNext()
96 | }
97 | },
98 | [scrollPrev, scrollNext]
99 | )
100 |
101 | React.useEffect(() => {
102 | if (!api || !setApi) {
103 | return
104 | }
105 |
106 | setApi(api)
107 | }, [api, setApi])
108 |
109 | React.useEffect(() => {
110 | if (!api) {
111 | return
112 | }
113 |
114 | onSelect(api)
115 | api.on("reInit", onSelect)
116 | api.on("select", onSelect)
117 |
118 | return () => {
119 | api?.off("select", onSelect)
120 | }
121 | }, [api, onSelect])
122 |
123 | return (
124 |
137 |
145 | {children}
146 |
147 |
148 | )
149 | }
150 | )
151 | Carousel.displayName = "Carousel"
152 |
153 | const CarouselContent = React.forwardRef<
154 | HTMLDivElement,
155 | React.HTMLAttributes
156 | >(({ className, ...props }, ref) => {
157 | const { carouselRef, orientation } = useCarousel()
158 |
159 | return (
160 |
171 | )
172 | })
173 | CarouselContent.displayName = "CarouselContent"
174 |
175 | const CarouselItem = React.forwardRef<
176 | HTMLDivElement,
177 | React.HTMLAttributes
178 | >(({ className, ...props }, ref) => {
179 | const { orientation } = useCarousel()
180 |
181 | return (
182 |
193 | )
194 | })
195 | CarouselItem.displayName = "CarouselItem"
196 |
197 | const CarouselPrevious = React.forwardRef<
198 | HTMLButtonElement,
199 | React.ComponentProps
200 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201 | const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202 |
203 | return (
204 |
222 | )
223 | })
224 | CarouselPrevious.displayName = "CarouselPrevious"
225 |
226 | const CarouselNext = React.forwardRef<
227 | HTMLButtonElement,
228 | React.ComponentProps
229 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230 | const { orientation, scrollNext, canScrollNext } = useCarousel()
231 |
232 | return (
233 |
251 | )
252 | })
253 | CarouselNext.displayName = "CarouselNext"
254 |
255 | export {
256 | type CarouselApi,
257 | Carousel,
258 | CarouselContent,
259 | CarouselItem,
260 | CarouselPrevious,
261 | CarouselNext,
262 | }
263 |
--------------------------------------------------------------------------------
/frontend/components/ui/chart.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RechartsPrimitive from "recharts"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | // Format: { THEME_NAME: CSS_SELECTOR }
9 | const THEMES = { light: "", dark: ".dark" } as const
10 |
11 | export type ChartConfig = {
12 | [k in string]: {
13 | label?: React.ReactNode
14 | icon?: React.ComponentType
15 | } & (
16 | | { color?: string; theme?: never }
17 | | { color?: never; theme: Record }
18 | )
19 | }
20 |
21 | type ChartContextProps = {
22 | config: ChartConfig
23 | }
24 |
25 | const ChartContext = React.createContext(null)
26 |
27 | function useChart() {
28 | const context = React.useContext(ChartContext)
29 |
30 | if (!context) {
31 | throw new Error("useChart must be used within a ")
32 | }
33 |
34 | return context
35 | }
36 |
37 | const ChartContainer = React.forwardRef<
38 | HTMLDivElement,
39 | React.ComponentProps<"div"> & {
40 | config: ChartConfig
41 | children: React.ComponentProps<
42 | typeof RechartsPrimitive.ResponsiveContainer
43 | >["children"]
44 | }
45 | >(({ id, className, children, config, ...props }, ref) => {
46 | const uniqueId = React.useId()
47 | const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
48 |
49 | return (
50 |
51 |
60 |
61 |
62 | {children}
63 |
64 |
65 |
66 | )
67 | })
68 | ChartContainer.displayName = "Chart"
69 |
70 | const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
71 | const colorConfig = Object.entries(config).filter(
72 | ([_, config]) => config.theme || config.color
73 | )
74 |
75 | if (!colorConfig.length) {
76 | return null
77 | }
78 |
79 | return (
80 |