From 56619aa8d6321a3285a72d877ced5ec5bb7d7e56 Mon Sep 17 00:00:00 2001 From: ggalloro Date: Sat, 13 Dec 2025 22:19:52 +0100 Subject: [PATCH 1/3] Complete implementation of Podcast Generator --- .gitignore | 46 + GEMINI.md | 63 +- IMPLEMENTATION_PLAN.md | 59 + README.md | 128 +- eslint.config.mjs | 18 + next.config.ts | 7 + package-lock.json | 6785 +++++++++++++++++ package.json | 34 + postcss.config.mjs | 7 + .../20251213211231_init/migration.sql | 19 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 26 + public/file.svg | 1 + public/globe.svg | 1 + public/hero.png | Bin 0 -> 721568 bytes public/next.svg | 1 + public/vercel.svg | 1 + public/window.svg | 1 + src/app/api/feeds/route.ts | 69 + src/app/api/generate/route.ts | 90 + src/app/api/podcasts/route.ts | 14 + src/app/favicon.ico | Bin 0 -> 25931 bytes src/app/globals.css | 26 + src/app/layout.tsx | 34 + src/app/page.tsx | 35 + src/components/FeedManager.tsx | 124 + src/components/Hero.tsx | 21 + src/components/PodcastGenerator.tsx | 64 + src/components/PodcastList.tsx | 70 + src/generated/client/browser.ts | 29 + src/generated/client/client.ts | 51 + src/generated/client/commonInputTypes.ts | 272 + src/generated/client/enums.ts | 15 + src/generated/client/internal/class.ts | 200 + .../client/internal/prismaNamespace.ts | 821 ++ .../client/internal/prismaNamespaceBrowser.ts | 106 + src/generated/client/models.ts | 13 + src/generated/client/models/Feed.ts | 1126 +++ src/generated/client/models/Podcast.ts | 1194 +++ src/lib/audio.ts | 40 + src/lib/ffmpeg.ts | 23 + src/lib/gemini.ts | 17 + src/lib/prisma.ts | 7 + tsconfig.json | 34 + 44 files changed, 11557 insertions(+), 138 deletions(-) create mode 100644 .gitignore create mode 100644 IMPLEMENTATION_PLAN.md create mode 100644 eslint.config.mjs create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 prisma/migrations/20251213211231_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 public/file.svg create mode 100644 public/globe.svg create mode 100644 public/hero.png create mode 100644 public/next.svg create mode 100644 public/vercel.svg create mode 100644 public/window.svg create mode 100644 src/app/api/feeds/route.ts create mode 100644 src/app/api/generate/route.ts create mode 100644 src/app/api/podcasts/route.ts create mode 100644 src/app/favicon.ico create mode 100644 src/app/globals.css create mode 100644 src/app/layout.tsx create mode 100644 src/app/page.tsx create mode 100644 src/components/FeedManager.tsx create mode 100644 src/components/Hero.tsx create mode 100644 src/components/PodcastGenerator.tsx create mode 100644 src/components/PodcastList.tsx create mode 100644 src/generated/client/browser.ts create mode 100644 src/generated/client/client.ts create mode 100644 src/generated/client/commonInputTypes.ts create mode 100644 src/generated/client/enums.ts create mode 100644 src/generated/client/internal/class.ts create mode 100644 src/generated/client/internal/prismaNamespace.ts create mode 100644 src/generated/client/internal/prismaNamespaceBrowser.ts create mode 100644 src/generated/client/models.ts create mode 100644 src/generated/client/models/Feed.ts create mode 100644 src/generated/client/models/Podcast.ts create mode 100644 src/lib/audio.ts create mode 100644 src/lib/ffmpeg.ts create mode 100644 src/lib/gemini.ts create mode 100644 src/lib/prisma.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..97b81d6b --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md index 3ea20eba..76ddfc90 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -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 diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..c0d86fea --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -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 `