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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

/src/generated/prisma
/dev.db
/dev.db-journal
/tmp/
63 changes: 2 additions & 61 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,67 +12,8 @@ When executing an implementation plan, after a phase is completed, ask the user

## Technical specifications, tools and architecture

If asked to use icons or pictures in your app (for example hero images or background images) generate them with the Nano Banana extension

## SQLAlchemy Database interactions coding guidelines

When using SQLAlchemy with Python and Flask, all database models and queries must adhere to the modern **SQLAlchemy 2.0** style. The legacy query API from `Flask-SQLAlchemy` (`Model.query`) is forbidden.

### 1. Model Definition

Models must be defined using `sqlalchemy.orm.Mapped` and `sqlalchemy.orm.mapped_column` with type annotations.

**Bad (Legacy Style):**
```python
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(40), unique=True, nullable=False)
```

**Good (Modern SQLAlchemy 2.0 Style):**
```python
import sqlalchemy as sa
import sqlalchemy.orm as so

class User(db.Model):
id: so.Mapped[int] = so.mapped_column(primary_key=True)
email: so.Mapped[str] = so.mapped_column(sa.String(40), unique=True)
```

### 2. Database Queries

All queries must be constructed using the `sqlalchemy.select()` function. Do not use the `Model.query` object.

**Bad (Legacy `Model.query`):**
```python
# Get by primary key
user = User.query.get(1)

# Filter and get first
user = User.query.filter_by(email="test@example.com").first()

# Get all
users = User.query.all()
```

**Good (Modern `select()` construct):**
```python
import sqlalchemy as sa

# Get by primary key
user = db.session.get(User, 1)

# Filter and get first
stmt = sa.select(User).where(User.email == "test@example.com")
user = db.session.scalars(stmt).first()

# Get all
stmt = sa.select(User)
users = db.session.scalars(stmt).all()
```



If asked to use icons or pictures in your app (for example hero images or background images) generate them with the Nano Banana extension

For guide on interacting with Google Gemini API follow the instructions in: @gemini-styleguide.md


59 changes: 59 additions & 0 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Implementation Plan - Podcast Generator

## Phase 0: Git Setup
- [x] Check if the current directory is an initialized git repository.
- [x] If it is, create and checkout a new feature branch named "podcast-generator".

## Phase 1: Project Initialization
- [x] Initialize a new Next.js project with TypeScript and Tailwind CSS using `npx create-next-app@latest`.
- [x] Install Prisma and SQLite dependencies (`npm install prisma @prisma/client`, `npx prisma init`).
- [x] Install backend utilities: `rss-parser`, `fluent-ffmpeg`, `@google/generative-ai`.
- [x] Install UI icons (e.g., `lucide-react` or `heroicons`).
- [x] Configure `next.config.js` if necessary for external images or specific build settings.
- [x] Create a `.env` file for API keys (Gemini API Key) and database URL.

## Phase 2: Database & Backend Basics
- [x] Define the Prisma schema (`Feed` and `Podcast` models) in `prisma/schema.prisma` as per the tech spec.
- [x] Run `npx prisma migrate dev --name init` to create the SQLite database tables.
- [x] Create a Prisma client instance singleton (`lib/prisma.ts`) to avoid connection exhaustion in dev.
- [x] Implement `POST /api/feeds` API route: Validate URL, fetch title with `rss-parser`, save to DB.
- [x] Implement `GET /api/feeds` API route: Retrieve all feeds from DB.
- [x] Implement `DELETE /api/feeds` API route: Remove a feed by ID.

## Phase 3: Core Logic (AI & Audio)
- [x] Create a utility function `lib/gemini.ts` to handle Gemini API interactions (Summarization).
- [x] Create a utility function `lib/audio.ts` to handle Text-to-Speech (using Google Cloud TTS or Gemini Multimodal if available/configured).
- [x] Install FFmpeg locally or ensure it's available in the environment (for `fluent-ffmpeg` to work).
- [x] Create `lib/ffmpeg.ts` to handle audio stitching (Intro + Segments + Outro).
- [x] Implement the `POST /api/generate` logic:
- [x] Fetch feeds from DB.
- [x] Parse latest articles (limit 3-5).
- [x] Loop: Summarize articles using Gemini.
- [x] Loop: Generate audio segments from summaries.
- [x] Stitch segments into a single MP3.
- [x] Save MP3 to `public/podcasts/`.
- [x] Save metadata to `Podcast` table in DB.

## Phase 4: Frontend Implementation
- [x] Generate the Hero Image using Nano Banana (prompt: "robot agent reading the news in a TV studio").
- [x] Create `components/Hero.tsx` to display the hero image and title.
- [x] Create `components/FeedManager.tsx`:
- [x] Input field for RSS URL.
- [x] List of added feeds with delete button.
- [x] Connect to `GET`, `POST`, `DELETE /api/feeds`.
- [x] Create `components/PodcastGenerator.tsx`:
- [x] "Generate Podcast" button.
- [x] Loading state/spinner handling (long-running process).
- [x] Create `components/PodcastList.tsx`:
- [x] Fetch and display list of generated podcasts (`GET /api/podcasts`).
- [x] Embed HTML5 `<audio>` player for playback.
- [x] Download link.
- [x] Assemble the main page (`app/page.tsx`) with all components.

## Phase 5: Completion & Version Control
- [x] Verify application functionality (Add feed, Generate, Play).
- [x] Create a `README.md` file explaining the application functions, architecture, and how to run it.
- [x] Add all changes to the repository (`git add .`).
- [x] Commit the changes (`git commit -m "Complete implementation of Podcast Generator"`).
- [x] Push the feature branch to the remote repository.
- [x] Open a pull request for the feature branch.
128 changes: 51 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,94 +1,68 @@
# Spec-Driven Development w Gemini CLI
# Podcast Generator

This repo has some basic assets to experiment **Spec-Driven Development** using the Gemini CLI. You will act as a developer going from a raw Functional Specification to a deployed Pull Request in a single session.
An AI-powered application that turns your favorite RSS feeds into a personalized news podcast.

## Assets
## Features

* `.gemini/commands/`: Contains configuration files for custom commands (`techspec`, `plan`, `build`).
* `GEMINI.md`: Contains project rules and guidelines.
* `.github/workflows`: Contains CI workflow.
* **No application code**.
- **RSS Feed Management**: Add, view, and remove RSS feeds.
- **AI Summarization**: Automatically fetches and summarizes the latest articles from your feeds using Google Gemini.
- **Audio Generation**: Converts summaries into natural-sounding speech.
- **Podcast Assembly**: Stitches intro, segments, and outro into a single MP3 file.
- **Playback & Download**: Listen to your generated podcasts directly in the app or download them.

## Requirements
## Architecture

The `GEMINI.md` configuration and custom commands require the following extensions:
* **Google Workspace**
* **Nano Banana**
* **GitHub**
- **Frontend**: Next.js (App Router), Tailwind CSS, Lucide Icons.
- **Backend**: Next.js API Routes.
- **Database**: SQLite with Prisma ORM.
- **AI**: Google Gemini API (`gemini-2.5-flash`) for summarization.
- **Audio**: `google-tts-api` (fallback) or configured TTS service, processed with `fluent-ffmpeg`.

---
## Prerequisites

## Step 1: The Architect Phase (/techspec)
- Node.js (v18+)
- FFmpeg installed on your system (required for audio stitching).
- macOS: `brew install ffmpeg`
- Linux: `sudo apt install ffmpeg`
- Google Gemini API Key.

**Goal:** Transform a Functional Spec (Google Doc) into a Technical Spec (Google Doc).
## Setup

1. **Command:**
```
/techspec "Name of your functional specs doc" "Your desired technology stack and requirements"
```
1. **Clone the repository**:
```bash
git clone <repository-url>
cd podcast-generator
```

2. **What Happens:**
* The agent searches your Drive for the doc.
* It reads the requirements.
* It generates a **Technical Specification** including Data Models, API Routes, and Architecture based on your inputs.
* **Output:** It creates a *new* Google Doc titled "Technical Specification - Application name" and gives you the link.
2. **Install dependencies**:
```bash
npm install
```

---
3. **Environment Variables**:
Create a `.env` file in the root directory:
```env
DATABASE_URL="file:./dev.db"
GEMINI_API_KEY="your_gemini_api_key_here"
```

## Step 2: The Planning Phase (/plan)
4. **Database Setup**:
```bash
npx prisma migrate dev --name init
```

**Goal:** Break the Technical Spec down into an atomic Implementation Plan.
5. **Run Development Server**:
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) in your browser.

1. **Command:**
```
/plan "Name of your Tech spec doc"
```
*(Use the exact name of the doc generated in Step 1)*
## Usage

2. **What Happens:**
* The agent reads the Tech Spec.
* It creates a local file `IMPLEMENTATION_PLAN.md`.
* It breaks the project into phases (e.g., Setup, Backend, Frontend, Polish).
* It defines the Git strategy.
1. **Add Feeds**: Enter an RSS URL (e.g., `https://feeds.npr.org/1001/rss.xml`) and click "Add".
2. **Generate**: Click the "Generate Podcast" button. Wait for the AI to process (1-2 mins).
3. **Listen**: Click play on the generated episode or download it.

---
## License

## Step 3: The Build Phase (/build)

**Goal:** Execute the plan and write the code.

1. **Command:**
```
/build IMPLEMENTATION_PLAN.md "Name of your Tech spec doc"
```

2. **What Happens (Iterative):**
* **Execution:** The agent iterates through the plan, initializing the project structure and writing the application code.
* **Visuals:** It generates necessary visual assets (images, icons) as defined in the spec.
* **Progress:** It updates `IMPLEMENTATION_PLAN.md` as tasks are completed.

---

## Step 4: Final Delivery

**Goal:** Push the code and open a Pull Request.

1. **Action:**
The `/build` command's final phase usually covers this, or you can manually instruct the agent to finalize the project.

2. **What Happens:**
* The agent runs final checks (linting/formatting).
* It creates a `README.md` for the new application.
* It commits all changes.
* It pushes the feature branch to GitHub.
* It uses the GitHub extension to **Open a Pull Request**.

---

## Summary of Commands

| Step | Command | Input | Output |
| :--- | :--- | :--- | :--- |
| **1. Spec** | `/techspec` | Functional Doc (Drive) | Tech Spec (Drive) |
| **2. Plan** | `/plan` | Tech Spec (Drive) | `IMPLEMENTATION_PLAN.md` |
| **3. Build** | `/build` | Plan + Tech Spec | Code, Assets, App |
MIT
18 changes: 18 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);

export default eslintConfig;
7 changes: 7 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
};

export default nextConfig;
Loading