From 43e17b4584759160aa6ded8db6b95b9afd53edde Mon Sep 17 00:00:00 2001 From: ggalloro Date: Sun, 14 Dec 2025 13:24:18 +0100 Subject: [PATCH 1/5] Complete implementation of Podcast Generator --- GEMINI.md | 61 +- IMPLEMENTATION_PLAN.md | 58 + README.md | 94 - gemini-styleguide.md | 107 + podcast-generator/.gitignore | 41 + podcast-generator/README.md | 47 + podcast-generator/app/api/feeds/route.ts | 65 + .../app/api/podcasts/[id]/route.ts | 16 + podcast-generator/app/api/podcasts/route.ts | 42 + podcast-generator/app/favicon.ico | Bin 0 -> 25931 bytes podcast-generator/app/globals.css | 26 + podcast-generator/app/layout.tsx | 54 + podcast-generator/app/page.tsx | 33 + podcast-generator/components/FeedManager.tsx | 132 + .../components/PodcastGenerator.tsx | 74 + podcast-generator/components/PodcastList.tsx | 57 + podcast-generator/data/db.json | 1 + podcast-generator/eslint.config.mjs | 18 + podcast-generator/lib/generator.ts | 118 + podcast-generator/lib/store.ts | 73 + podcast-generator/next.config.ts | 7 + podcast-generator/package-lock.json | 6618 +++++++++++++++++ podcast-generator/package.json | 30 + podcast-generator/postcss.config.mjs | 7 + podcast-generator/public/file.svg | 1 + podcast-generator/public/globe.svg | 1 + podcast-generator/public/hero-image.png | Bin 0 -> 735204 bytes podcast-generator/public/next.svg | 1 + podcast-generator/public/vercel.svg | 1 + podcast-generator/public/window.svg | 1 + podcast-generator/tsconfig.json | 34 + 31 files changed, 7665 insertions(+), 153 deletions(-) create mode 100644 IMPLEMENTATION_PLAN.md delete mode 100644 README.md create mode 100644 gemini-styleguide.md create mode 100644 podcast-generator/.gitignore create mode 100644 podcast-generator/README.md create mode 100644 podcast-generator/app/api/feeds/route.ts create mode 100644 podcast-generator/app/api/podcasts/[id]/route.ts create mode 100644 podcast-generator/app/api/podcasts/route.ts create mode 100644 podcast-generator/app/favicon.ico create mode 100644 podcast-generator/app/globals.css create mode 100644 podcast-generator/app/layout.tsx create mode 100644 podcast-generator/app/page.tsx create mode 100644 podcast-generator/components/FeedManager.tsx create mode 100644 podcast-generator/components/PodcastGenerator.tsx create mode 100644 podcast-generator/components/PodcastList.tsx create mode 100644 podcast-generator/data/db.json create mode 100644 podcast-generator/eslint.config.mjs create mode 100644 podcast-generator/lib/generator.ts create mode 100644 podcast-generator/lib/store.ts create mode 100644 podcast-generator/next.config.ts create mode 100644 podcast-generator/package-lock.json create mode 100644 podcast-generator/package.json create mode 100644 podcast-generator/postcss.config.mjs create mode 100644 podcast-generator/public/file.svg create mode 100644 podcast-generator/public/globe.svg create mode 100644 podcast-generator/public/hero-image.png create mode 100644 podcast-generator/public/next.svg create mode 100644 podcast-generator/public/vercel.svg create mode 100644 podcast-generator/public/window.svg create mode 100644 podcast-generator/tsconfig.json diff --git a/GEMINI.md b/GEMINI.md index 3ea20eba..628cb1c8 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -14,65 +14,8 @@ When executing an implementation plan, after a phase is completed, ask the user 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() -``` - - - +For guide on interacting with Gemini API follow the instructions in: +@gemini-styleguide.md diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..0acfeacf --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -0,0 +1,58 @@ +# 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 & Configuration +- [x] Initialize a new Next.js project with Tailwind CSS (`npx create-next-app@latest podcast-generator --typescript --tailwind --eslint`). +- [x] Install project dependencies: `npm install google-genai rss-parser uuid`. +- [x] Create a `.env.local` file for environment variables (`GOOGLE_API_KEY`, `PROJECT_ID`). +- [x] Generate a Hero Image ("Robot Agent in TV Studio") using the Nano Banana extension and save it to `public/hero-image.png`. +- [x] Verify the application runs locally (`npm run dev`). + +## Phase 2: Data Layer & API - Feeds +- [x] Create a utility module `lib/store.ts` to handle reading/writing to a local JSON file (`data/db.json`) for data persistence. Define the `Feed` and `Podcast` interfaces here. +- [x] Implement the API route `GET /api/feeds` to retrieve the list of stored feeds. +- [x] Implement the API route `POST /api/feeds` to add a new feed. This must use `rss-parser` to validate the URL and fetch the feed title before saving. +- [x] Implement the API route `DELETE /api/feeds` to remove a feed by ID. +- [x] Verify API endpoints using `curl` or a tool like Postman. + +## Phase 3: Frontend - Layout & Feed Manager +- [x] Update `app/layout.tsx` and `app/page.tsx` to implement the basic two-column layout. Include the Hero Image at the top. +- [x] Create a `components/FeedManager.tsx` component. This should include: + - An input form to add a new RSS feed URL. + - A list displaying currently added feeds with a "Remove" button. +- [x] Connect `FeedManager` to the `/api/feeds` endpoints to fetch, add, and delete feeds dynamically. +- [x] Verify the Feed Manager UI works correctly (adding valid/invalid URLs, removing feeds). + +## Phase 4: API - Podcast Generation Logic +- [x] Create the `POST /api/podcasts/generate` endpoint. This should: + - Accept a request to start generation. + - Create a new "PENDING" podcast record in the DB. + - Trigger the generation process asynchronously (or simulate async if Vercel limits apply, but for local use, normal async is fine). + - Return the `podcastId`. +- [x] Implement the "Fetch & Filter" logic: Fetch articles from all stored feeds and filter for those published in the last 24 hours. +- [x] Implement the "Summarization" service: Use `google-genai` (`gemini-2.5-flash`) to summarize each article into a short script segment. +- [x] Implement the "Script Compilation" logic: specific Intro + Summaries + Outro. +- [x] Implement the "Audio Synthesis" service: Use `google-genai` (or specific TTS endpoint if `google-genai` audio generation is distinct) to convert the full script to speech. Save the resulting audio buffer to `public/podcasts/[id].mp3`. +- [x] Update the podcast record in `data/db.json` to "COMPLETED" with the path and duration. +- [x] Implement `GET /api/podcasts` to list all generated podcasts. +- [x] Implement `GET /api/podcasts/[id]` to return the status of a specific generation job. + +## Phase 5: Frontend - Podcast Player & History +- [x] Create a `components/PodcastGenerator.tsx` component with a "Generate Podcast" button. + - It should call `/api/podcasts/generate`. + - It should display a loading state (spinner) while polling `/api/podcasts/[id]` until status is "COMPLETED". +- [x] Create a `components/PodcastList.tsx` component to display the history of generated podcasts. +- [x] Create/Integrate an `AudioPlayer` component (standard HTML5 `