# Project export: GitCast

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: GitCast turns any GitHub repo into a podcast in your native language — making open-source accessible, inclusive, and easy to learn, no matter where you're from.
- Devpost: https://devpost.com/software/gitcast
- GitHub: https://github.com/sakshat-patil/GitTranslate
- Video: https://www.youtube.com/embed/GsVKTIfjhwo?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — nishan (21 commits), Pranav Reddy Gaddam (8 commits), sakshat-patil (5 commits), aanthoni79 (2 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# GitTranslate

A full-stack web app for translating and localizing content using OpenAI’s APIs.  
GitTranslate lets you:

Devpost: [https://devpost.com/software/gitcast](https://devpost.com/software/gitcast)

- **Fetch and browse** Git repositories (GitHub, GitLab)
- **Translate** code comments, filenames, and documentation into your chosen language via GPT-3.5 Turbo
- **(Bonus)** Upload podcast or other audio files to **transcribe** with Whisper, **translate** the transcript, and **synthesize** new audio in the target language
- **Deploy** easily: frontend on Vercel, backend on any Python-friendly host (Heroku, AWS, etc.)
- **Automate** workflows with the included Orkes FE script

<br />

## Demo

Live at: [git-translate-indol.vercel.app](https://git-translate-indol.vercel.app)

<br />

## Features

- **Frontend**: Next.js + React + TypeScript + Tailwind CSS  
- **Backend**: Python (FastAPI)  
- **Audio**: Whisper for transcription, TTS for speech synthesis  
- **Translation**: OpenAI GPT-3.5 Turbo for text translation  
- **Workflow**: scripts/orkes-fe-script.py for Orkes integration  
- **Configurable**: .env-driven, ready for local or cloud deployment  

<br />

## Table of Contents

1. Getting Started
2. Prerequisites
3. Installation
4. Configuration
5. Running Locally
6. Deployment
7. Project Structure
8. Contributing
9. License

<br />

---

## Getting Started

### Prerequisites

- Node.js v16+
- Python 3.9+
- A valid OpenAI API Key

### Installation

1. Clone this repo  
   ```
   git clone https://github.com/sakshat-patil/GitTranslate.git
   cd GitTranslate
   ```

2. Backend  
   ```
   cd backend
   python -m venv venv
   source venv/bin/activate   # or venv\Scripts\activate on Windows
   pip install -r requirements.txt
   ```

3. Frontend  
   ```
   cd ../frontend
   npm install
   ```

<br />

## Configuration

Copy the example .env files and fill in your keys:

```
# backend/.env
OPENAI_API_KEY=your_openai_api_key
PORT=8000

# frontend/.env.local
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
```

<br />

## Running Locally

1. Start the backend (FastAPI + Uvicorn)  
   ```
   cd backend
   uvicorn app.main:app --reload --host 0.0.0.0 --port ${PORT:-8000}
   ```

2. Start the frontend (Next.js)  
   ```
   cd ../frontend
   npm run dev
   ```

Visit http://localhost:3000 in your browser and you’re live.

<br />

## Deployment

- Frontend: Push to Git, deploy via Vercel (auto-detect Next.js).
- Backend: Containerize or push to any Python host (Heroku, AWS Elastic Beanstalk, etc.).
- Workflows: See scripts/orkes-fe-script.py for setting up Orkes flows.

<br />

## Project Structure

```
/
├── backend/               # FastAPI app
│   ├── app/
│   ├── requirements.txt
│   └── .env
├── frontend/              # Next.js + React UI
│   ├── pages/
│   ├── components/
│   ├── public/
│   └── .env.local
├── scripts/
│   └── orkes-fe-script.py # Orkes workflow helper
├── LICENSE
└── sample.txt             # Example input/output
```

<br />

## Contributing

1. Fork the repo
2. Create a feature branch (git checkout -b feat/my-feature)
3. Commit your changes (git commit -m "feat: add X")
4. Push and open a PR

Please follow the existing code style and include tests where applicable.

<br />

## License

This project is licensed under the MIT License  
See LICENSE for details.


## Detected evidence (automated analysis)

Indexed codebase: 42 recognized source files, 106 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AWS (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (54 of 54)

```
.gitignore
backend/.gitignore
backend/app/__init__.py
backend/app/main.py
backend/app/routes/__init__.py
backend/app/routes/generate.py
backend/app/routes/status.py
backend/app/services/__init__.py
backend/app/services/orkes.py
backend/app/utils/__init__.py
backend/app/utils/config.py
backend/requirements.txt
frontend/.gitignore
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/src/api/index.ts
frontend/src/App.tsx
frontend/src/components/AudioPlayer.tsx
frontend/src/components/ConstellationCanvas.tsx
frontend/src/components/Navbar.tsx
frontend/src/components/RepoInputCard.tsx
frontend/src/components/ui/button.tsx
frontend/src/components/ui/card.tsx
frontend/src/components/ui/input.tsx
frontend/src/components/ui/label.tsx
frontend/src/components/ui/radio-group.tsx
frontend/src/index.css
frontend/src/lib/utils.ts
frontend/src/lib/validation.ts
frontend/src/main.tsx
frontend/src/vite-env.d.ts
frontend/tailwind.config.js
frontend/tsconfig.app.json
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
LICENSE
orkes-fe-script.py
package.json
README.md
requirements.txt
sample.txt
scripts/__init__.py
scripts/fetch_repo_data.py
scripts/generate_podcast.py
scripts/orkes_script.py
scripts/return_audio_url.py
scripts/summarize_repo.py
scripts/text2speech.py
scripts/translate_script.py
scripts/utils.py
```

### Dependencies

- backend/requirements.txt: fastapi@>=0.104.0, pydantic@>=2.0.0, pydantic-settings@>=2.0.0, python-dotenv@>=1.0.0, requests@>=2.31.0, uvicorn[standard]@>=0.24.0
- frontend/package.json: @eslint/js@^9.25.0, @radix-ui/react-radio-group@^1.3.7, @radix-ui/react-slot@^1.2.3, @radix-ui/react-toast@^1.2.14, @types/node@^24.0.3, @types/react@^19.1.2, @types/react-dom@^19.1.2, @vitejs/plugin-react@^4.4.1, autoprefixer@^10.4.21, axios@^1.10.0, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9.25.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.19, globals@^16.0.0, lucide-react@^0.522.0, postcss@^8.5.6, react@^19.1.0, react-dom@^19.1.0, tailwind-merge@^3.3.1, tailwindcss@^3.4.0, typescript@~5.8.3, typescript-eslint@^8.30.1, vite@^6.3.5
- package.json: @types/node@^24.0.3, clsx@^2.1.1, tailwind-merge@^3.3.1, tailwindcss-animate@^1.0.7
- requirements.txt: aiohttp, anthropic@>=0.7.0, boto3, pathspec@>=0.11.0, pydub, PyGithub@>=2.1.1, PyJWT@>=2.8.0, python-dotenv@>=1.0.0, requests@>=2.31.0, uuid@>=1.30

### Recent commits (newest first)

- Update README.md
- Update README.md
- Create README.md
- Merge remote-tracking branch 'origin/main'
- Update API endpoints in App.tsx to use HTTPS
- Update vite.config.ts
- Update vite.config.ts
- Update Navbar.tsx
- Remove Mandarin language option from RepoInputCard
- Remove Mandarin language option from RepoInputCard
- Update generate.py to include language parameter in workflow start message
- Rename repo_url to github_url and update language codes in RepoInputCard
- Rename repo_url to github_url and update language codes in RepoInputCard
- Rename repo_url to github_url and update language codes in RepoInputCard
- Merge branch 'main' of https://github.com/sakshat-patil/GitTranslate
- updated front end code
- Update main.py
- Update generate.py and orkes.py to rename repo_url to github_url and add language parameter
- Merge remote-tracking branch 'origin/main'
- minor

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
requests>=2.31.0
anthropic>=0.7.0
python-dotenv>=1.0.0
uuid>=1.30
PyJWT>=2.8.0
PyGithub>=2.1.1
pathspec>=0.11.0
aiohttp
pydub
boto3
```

### package.json

```
{
  "dependencies": {
    "clsx": "^2.1.1",
    "tailwind-merge": "^3.3.1",
    "tailwindcss-animate": "^1.0.7"
  },
  "devDependencies": {
    "@types/node": "^24.0.3"
  }
}

```

### backend/requirements.txt

```
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
pydantic>=2.0.0
pydantic-settings>=2.0.0
requests>=2.31.0
python-dotenv>=1.0.0

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@radix-ui/react-radio-group": "^1.3.7",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-toast": "^1.2.14",
    "axios": "^1.10.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.522.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "tailwind-merge": "^3.3.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.25.0",
    "@types/node": "^24.0.3",
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.1.2",
    "@vitejs/plugin-react": "^4.4.1",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.25.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "postcss": "^8.5.6",
    "tailwindcss": "^3.4.0",
    "typescript": "~5.8.3",
    "typescript-eslint": "^8.30.1",
    "vite": "^6.3.5"
  }
}

```

### frontend/src/main.tsx

```typescript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### backend/app/main.py

```python
"""GitTranslate FastAPI Gateway
--------------------------------
Thin gateway service that forwards requests to Orkes Conductor.
No business logic, storage, or DB. Simply triggers and polls
Orkes workflows and exposes public endpoints for the Next.js
frontend.
"""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

# Import route blueprints (routers)
from app.routes.generate import router as generate_router  # POST /generate
from app.routes.status import router as status_router      # GET /status/{id}


def create_app() -> FastAPI:
    """Factory function to create the FastAPI application."""
    app = FastAPI(
        title="GitTranslate API",
        version="0.1.0",
        description="Gateway API that triggers and monitors Orkes workflows for GitTranslate."
    )

    # Define allowed origins
    origins = [
        "http://localhost:5174",  # Local React dev server
        "http://localhost:3000",  # Common alternative for local dev
        "https://git-cast.vercel.app", # Your production frontend
        # Add any other frontend URLs here
    ]

    # CORS: allow frontend (e.g., Vercel) to call this API
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],  # TODO: tighten for production
        allow_credentials=False,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    @app.get("/")
    def read_root():
        return {"message": "Hello, world!"}

    # Register route modules
    app.include_router(generate_router, prefix="/api", tags=["Generate"])
    app.include_router(status_router,   prefix="/api", tags=["Status"])

    return app


app = create_app()

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        reload=True,
    )

```

### frontend/src/App.tsx

```typescript
import { useState, useRef, useEffect } from "react";
import "./index.css";
import { RepoInputCard } from "./components/RepoInputCard";
import { AudioPlayer } from "./components/AudioPlayer";
import ConstellationCanvas from "./components/ConstellationCanvas";
import { Navbar } from "./components/Navbar";

function App() {
  const [showAudioPlayer, setShowAudioPlayer] = useState(false);
  const [audioUrl, setAudioUrl] = useState("");
  const [isGenerating, setIsGenerating] = useState(false);
  const playerContainerRef = useRef<HTMLDivElement>(null);
  const [workflowId, setWorkflowId] = useState("");

  const handlePodcastGeneration = async (repoUrl: string, language: string) => {
    setIsGenerating(true);
    setShowAudioPlayer(false);
    setWorkflowId("");

    // Scroll the loading message into view
    setTimeout(() => {
        playerContainerRef.current?.scrollIntoView({
          behavior: 'smooth',
          block: 'center',
        });
      }, 100);

    try {
      const response = await fetch("https://3.95.215.8/api/generate", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          github_url: repoUrl,
          language: language
        })
      });

      if (!response.ok) {
        throw new Error(`API error: ${response.statusText}`);
      }

      const data = await response.json();
      setWorkflowId(data.workflow_id);

    } catch (error) {
      console.error("Failed to start podcast generation:", error);
      alert("Sorry, something went wrong. Please try again later.");
      setIsGenerating(false);
    }
  };
  
  const checkWorkflowStatus = async (id: string) => {
    try {
      const response = await fetch(`https://127.0.0.1/api/status/${id}`);

      if (!response.ok) {
        if (response.status !== 404) {
          throw new Error(`API error: ${response.statusText}`);
        }
        return;
      }

      const data = await response.json();

      if (data && data.result) {
        setAudioUrl(data.result);
        setShowAudioPlayer(true);
        setIsGenerating(false);
        setWorkflowId("");
      } else {
        console.log("Workflow status:", data.status || "In progress");
      }

    } catch (error) {
      console.error("Error checking workflow status:", error);
      alert("There was an error retrieving the podcast. Please try again.");
      setIsGenerating(false);
      setWorkflowId("");
    }
  };

  useEffect(() => {
    if (workflowId) {
      const intervalId = setInterval(() => {
        checkWorkflowStatus(workflowId);
      }, 3000);

      return () => clearInterval(intervalId);
    }
  }, [workflowId]);

  return (
    <main
      className="relative min-h-screen w-full bg-sky-100 flex flex-col items-center antialiased overflow-x-hidden"
    >
      <Navbar />
      <ConstellationCanvas />
      <div className="z-10 flex flex-col items-center justify-center text-center w-full flex-grow px-4 pt-32 pb-12">
        <div className="text-center mb-12">
            <h1 className="text-6xl font-bold text-slate-800 flex overflow-hidden justify-center pb-4">
              <span className="animate-text-slide-in block" style={{ animationDelay: '0s', animationFillMode: 'backwards' }}>
                Repository&nbsp;
              </span>
              <span className="animate-text-slide-in block" style={{ animationDelay: '0.15s', animationFillMode: 'backwards' }}>
                to&nbsp;
              </span>
              <span className="animate-text-slide-in block text-orange-500" style={{ animationDelay: '0.3s', animationFillMode: 'backwards' }}>
                Podcast
              </span>
            </h1>
          <p className="text-2xl font-medium text-gray-700 max-w-2xl mx-auto mt-4 text-balance">
            Now in Your Native Language — Learn Code the Way You Think.
          </p>
        </div>

        <div className="w-full mb-8 max-w-2xl">
          <RepoInputCard 
            onPodcast={handlePodcastGeneration}
            isGenerating={isGenerating}
          />
        </div>

        <div ref={playerContainerRef} className="w-full h-32 flex items-center justify-center px-4">
          {isGenerating ? (
             <div className="flex flex-col items-center gap-4 text-slate-700 animate-fade-in-up">
                <svg className="animate-spin h-8 w-8 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                  <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                  <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                </svg>
                <p className="text-lg font-medium text-center text-balance">
                  Please wait while your tailored podcast is being generated...
                </p>
              </div>
          ) : (
            showAudioPlayer && <AudioPlayer src={audioUrl} />
          )}
        </div>
      </div>
    </main>
  );
}

export default App;

```

### frontend/src/api/index.ts

```typescript
import axios from 'axios';

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
});

export default apiClient; 
```

### orkes-fe-script.py

```python
from time import sleep

import requests
import json



# url = "https://developer.orkescloud.com/api/token"
# headers = {
#     "Content-Type": "application/json"
# }
# data = {
#     "keyId": "47hs2be26735-4ee7-11f0-a795-d685533af8e3",
#     "keySecret": "kosQVUbCtvFarR8AmaG8RWomGLtm67ulTJWMUlLZIxoMkEXk"
# }
#
# response = requests.post(url, headers=headers, data=json.dumps(data))
#
# print(response.status_code)
# token = response.json()['token']

token = 'eyJhbGciOiJIUzUxMiJ9.eyJvcmtlc19rZXkiOiI0N2hzMmJlMjY3MzUtNGVlNy0xMWYwLWE3OTUtZDY4NTUzM2FmOGUzIiwib3JrZXNfY29uZHVjdG9yX3Rva2VuIjp0cnVlLCJzdWIiOiJhcHA6ZjM0ZjYyNmItZDFhZC00YWYxLThjZDEtODQwNDQ0ZmRhNGQwIiwiaWF0IjoxNzUwNTQyMzc1fQ.J7IqR03wDl7SOfd2DSsdxKMNXk0FPMkDiPGUEHLbYJbsbgj3IQIR5pkISO75R3Ixx4R5YuWI76SL803yKSUwtw'

import requests
import json

url = "https://developer.orkescloud.com/api/workflow"
headers = {
    "x-authorization": f"{token}",
    "Content-Type": "application/json"
}
data = {
    "name": "GitTranslate_v3",
    "version": 1,
    "input": {
        "github_url": "https://github.com/streamlit/streamlit",
        "lang": "hindi"
    }
}

response = requests.post(url, headers=headers, data=json.dumps(data))
workflow_id = response.text

url = f"https://developer.orkescloud.com/api/workflow/{workflow_id}?summarize=true"

headers = {
    "x-authorization": f"{token}",
    "Content-Type": "application/json"
}

while True:
    response = requests.get(url, headers=headers)

    # print(response.status_code)
    # print(response.json())
    status = response.json()["status"]
    if status == "COMPLETED":
        print("Workflow completed successfully.")
        print("Output:", response.json()['output']['data'])
        break
    elif status == "FAILED":
        print("Workflow failed.")
        break
    else:
        print(f"Workflow is still running... {workflow_id}")

```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
} 
```

[33 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]