-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Nevermined Tutorials showcase website #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
dbe2e47
feat: add Nevermined Tutorials showcase website
aaitor 536c49f
ci: build & push showcase image to Artifact Registry
aaitor 2a1cbec
fix(ci): PR verify job, drop moving latest tag, harden sandbox docs
aaitor 8d8c076
fix(showcase): per-slug sandbox state, WebVTT subtitles, honest live-…
aaitor 083d086
fix(showcase): anchor SRT→VTT comma→dot to cue-timing lines only
aaitor 1846af6
perf(ci): kill npm ci network stalls + cancel superseded PR runs
aaitor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # Docker build context is this repo root (docker build -f showcase/Dockerfile .). | ||
| # Keep it small: drop dependencies, build output, VCS, secrets, logs. | ||
| **/node_modules | ||
| **/.next | ||
| **/out | ||
| **/.git | ||
| **/.env | ||
| **/.env.* | ||
| **/*.log | ||
| **/.DS_Store | ||
| **/coverage | ||
| # Python tutorials leave these locally (Poetry venvs, bytecode) — never needed in context. | ||
| **/.venv | ||
| **/__pycache__ | ||
| **/*.pyc | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| name: Showcase image | ||
|
|
||
| # Builds the tutorials showcase (showcase/Dockerfile) and pushes it to Google | ||
| # Artifact Registry so ArgoCD can deploy it. Keyless auth via Workload Identity | ||
| # Federation (same SA/provider the other Nevermined repos use — no secrets). | ||
| # | ||
| # pull_request → verify only (build + sandbox self-check); no credentials, no push. | ||
| # push to main → verify, then build & push. | ||
| # dispatch → verify, then build & push (optional extra semver tag). | ||
| # | ||
| # Versioning (immutable-first — the AR repo has immutableTags=true, so a moving | ||
| # `latest` tag would be rejected on the second build; we don't publish one): | ||
| # - sha-<short> every build — the immutable tag ArgoCD should pin in production | ||
| # - <version> optional, when run manually with a version input (e.g. 1.0.0) | ||
|
|
||
| on: | ||
|
aaitor marked this conversation as resolved.
|
||
| pull_request: | ||
| paths: | ||
| - "showcase/**" | ||
| - ".github/workflows/showcase-image.yml" | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - "showcase/**" | ||
| - "catalog/**/*.mp4" | ||
| - ".github/workflows/showcase-image.yml" | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: "Optional explicit semver tag to also publish (e.g. 1.0.0)" | ||
| required: false | ||
| type: string | ||
|
|
||
| # Superseded PR pushes shouldn't keep burning a runner. Cancel only in-flight PR | ||
| # runs (grouped per ref, so distinct PRs don't cancel each other); never cancel a | ||
| # push/dispatch run — those publish the immutable image and must finish. | ||
| concurrency: | ||
| group: showcase-image-${{ github.ref }} | ||
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | ||
|
|
||
| env: | ||
| IMAGE: europe-west3-docker.pkg.dev/nevermined-eu-dev/nevermined-io/tutorials-showcase | ||
| AR_LOCATION: europe-west3 | ||
| PROJECT_ID: nevermined-eu-dev | ||
| SERVICE_ACCOUNT: github-actions-service-account@nevermined-eu-dev.iam.gserviceaccount.com | ||
| WORKLOAD_IDENTITY_PROVIDER: projects/112425687177/locations/global/workloadIdentityPools/github/providers/github-actions | ||
|
|
||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
|
|
||
| jobs: | ||
| # Runs on every PR and every push. `next build` type-checks the 1,000+ line | ||
| # content array, and the sandbox self-check covers the x402 handshake paths — | ||
| # neither needs credentials, so a broken PR fails here instead of on main. | ||
| verify: | ||
| name: Verify (build + sandbox self-check) | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: showcase | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Node | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "20" | ||
| cache: npm | ||
| cache-dependency-path: showcase/package-lock.json | ||
|
|
||
| - name: Install | ||
| # setup-node already restores ~/.npm, so every tarball is on disk. Stop | ||
| # npm ci from hitting the network for data it has: --prefer-offline skips | ||
| # registry revalidation, --no-audit/--no-fund skip the metadata round-trips | ||
| # that were stalling install (measured 22s ↔ 421s variance on identical | ||
| # inputs — the slow runs hung on the audit/fund calls, not on downloads). | ||
| run: npm ci --prefer-offline --no-audit --no-fund | ||
|
|
||
| - name: Sandbox agent self-check | ||
| run: node lib/demo-agent.mjs | ||
|
|
||
| - name: Build (type-checks content/tutorials.ts) | ||
| run: npm run build | ||
|
|
||
| # Only publishes on main / dispatch — never on a pull request. | ||
| build-push: | ||
| name: Build & push showcase image | ||
| runs-on: ubuntu-latest | ||
| needs: verify | ||
| if: github.event_name != 'pull_request' | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Docker metadata (tags + labels) | ||
| id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: ${{ env.IMAGE }} | ||
| tags: | | ||
| type=sha,prefix=sha-,format=short | ||
| type=raw,value=${{ inputs.version }},enable=${{ inputs.version != '' }} | ||
|
|
||
| - name: Authenticate to Google Cloud | ||
| id: auth | ||
| uses: google-github-actions/auth@v2 | ||
| with: | ||
| token_format: access_token | ||
| project_id: ${{ env.PROJECT_ID }} | ||
| service_account: ${{ env.SERVICE_ACCOUNT }} | ||
| workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }} | ||
|
|
||
| - name: Log in to Artifact Registry | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ${{ env.AR_LOCATION }}-docker.pkg.dev | ||
| username: oauth2accesstoken | ||
| password: ${{ steps.auth.outputs.access_token }} | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Build & push | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| # context is the repo root so the Dockerfile can pull the catalog demo media | ||
| context: . | ||
| file: ./showcase/Dockerfile | ||
| push: true | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| provenance: false | ||
| sbom: false | ||
|
|
||
| - name: Summary | ||
| env: | ||
| TAGS: ${{ steps.meta.outputs.tags }} | ||
| run: | | ||
| { | ||
| echo "### Pushed tutorials-showcase image" | ||
| echo '```' | ||
| echo "$TAGS" | ||
| echo '```' | ||
| echo "Pin the immutable sha-* tag in ArgoCD for production." | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # deps / build | ||
| node_modules | ||
| .next | ||
| out | ||
| *.tsbuildinfo | ||
| next-env.d.ts | ||
|
|
||
| # env | ||
| .env | ||
| .env*.local | ||
|
|
||
| # large demo video — pull with `npm run sync:media` (kept out of git) | ||
| public/media/**/*.mp4 | ||
|
|
||
| # internal design-review snapshots (Impeccable) | ||
| .impeccable/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Nevermined Tutorials showcase — production image (Next.js standalone). | ||
| # | ||
| # Build from the REPO ROOT so the catalog demo media (committed under catalog/) | ||
| # is in the build context: | ||
| # | ||
| # docker build -f showcase/Dockerfile -t nvm-tutorials-showcase . | ||
| # docker run -p 3000:3000 nvm-tutorials-showcase | ||
| # | ||
| # The container listens on $PORT (default 3000) on 0.0.0.0 — ready for a k8s | ||
| # Service / ArgoCD Deployment. | ||
|
|
||
| # ---- dependencies ---- | ||
| FROM node:20-alpine AS deps | ||
| WORKDIR /app | ||
| COPY showcase/package.json showcase/package-lock.json ./ | ||
| RUN npm ci | ||
|
|
||
| # ---- build ---- | ||
| FROM node:20-alpine AS build | ||
| WORKDIR /app | ||
| COPY --from=deps /app/node_modules ./node_modules | ||
| COPY showcase/ ./ | ||
| # The recap videos are gitignored under showcase/public — pull them from the | ||
| # committed catalog/ demos so the recap panels play in the image. | ||
| COPY catalog/song-from-the-headlines/song-from-the-headlines.mp4 ./public/media/song-from-the-headlines/ | ||
| COPY catalog/diligence-in-a-box/diligence-in-a-box.mp4 ./public/media/diligence-in-a-box/ | ||
| RUN npm run build | ||
|
|
||
| # ---- runner ---- | ||
| FROM node:20-alpine AS runner | ||
| WORKDIR /app | ||
| ENV NODE_ENV=production | ||
| ENV PORT=3000 | ||
| ENV HOSTNAME=0.0.0.0 | ||
| RUN addgroup -S nodejs && adduser -S nextjs -G nodejs | ||
|
|
||
| COPY --from=build /app/public ./public | ||
| COPY --from=build /app/.next/standalone ./ | ||
| COPY --from=build /app/.next/static ./.next/static | ||
|
|
||
| USER nextjs | ||
| EXPOSE 3000 | ||
| CMD ["node", "server.js"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| # Nevermined Tutorials — Showcase | ||
|
|
||
| A visual showcase of the Nevermined Payments tutorials. Every paid-agent and catalog | ||
| tutorial in this repo is presented in one normalized shape — **what you'll learn · how it | ||
| works · under the hood · see it run** — behind a persistent left sidebar, with an | ||
| individual page for each. (`langchain-chat-ui-nvm` is the browser *buyer* front-end for the | ||
| LangChain agents rather than a paid agent itself, so it's referenced inside those pages | ||
| instead of getting its own entry.) | ||
|
|
||
| Built with Next.js (App Router) + TypeScript. Clean, light, docs-style UI on a white | ||
| ground with a restrained Nevermined-teal accent. Intended to become the canonical | ||
| replacement for `examples.nevermined.app`. | ||
|
|
||
| ## Quick start | ||
|
|
||
| ```bash | ||
| cd showcase | ||
| npm install | ||
| npm run sync:media # copy catalog demo video/audio into public/ (recap panels) | ||
| npm run dev # http://localhost:3000 | ||
| ``` | ||
|
|
||
| Build the production bundle: | ||
|
|
||
| ```bash | ||
| npm run build && npm start | ||
| ``` | ||
|
|
||
| ## Docker (for ArgoCD / k8s) | ||
|
|
||
| The app builds to a Next.js **standalone** server. Build the image from the **repo | ||
| root** (so the committed `catalog/` demo videos are in context): | ||
|
|
||
| ```bash | ||
| docker build -f showcase/Dockerfile -t nvm-tutorials-showcase . | ||
| docker run -p 3000:3000 nvm-tutorials-showcase | ||
| ``` | ||
|
|
||
| The container listens on `$PORT` (default `3000`) on `0.0.0.0`, runs as a non-root | ||
| user, and needs no build args or secrets — point a k8s Service / ArgoCD Deployment | ||
| at it. | ||
|
|
||
| ### CI → Artifact Registry | ||
|
|
||
| `.github/workflows/showcase-image.yml` has two jobs: | ||
|
|
||
| - **`verify`** runs on every **pull request** (and every push): `npm ci`, the sandbox | ||
| self-check (`node lib/demo-agent.mjs`), and `npm run build` — which type-checks | ||
| `content/tutorials.ts`. It needs no credentials, so a broken change fails the PR | ||
| instead of landing on `main` and breaking the image build. | ||
| - **`build-push`** runs only on **push to `main`** touching `showcase/**` (and on manual | ||
| dispatch), never on a PR. It builds the image and pushes it to | ||
| `europe-west3-docker.pkg.dev/nevermined-eu-dev/nevermined-io/tutorials-showcase` | ||
| (keyless, via Workload Identity Federation). | ||
|
|
||
| Tags: an immutable `sha-<short>` per build (**pin this in ArgoCD for production**) and an | ||
| optional semver when dispatched with a `version` input. There is no moving `latest` tag — | ||
| the registry has `immutableTags=true`, which would reject a second `latest` push. | ||
|
|
||
| ## How content works | ||
|
|
||
| All tutorial content lives in one typed array: [`content/tutorials.ts`](./content/tutorials.ts), | ||
| shaped by [`lib/types.ts`](./lib/types.ts). Each entry is sourced from that tutorial's own README. | ||
| Adding or editing a tutorial is a data change — no new components. Pages are generated statically | ||
| from the array (`generateStaticParams`). | ||
|
|
||
| Each tutorial declares a **tier**: | ||
|
|
||
| - **`live`** — the `See it run` panel is **functional in the browser**: it does real fetch | ||
| round-trips through `/api/agent` and runs the actual x402 handshake — `402 → authorize → | ||
| 200 + settlement` — with a real per-session credit balance that decrements per call and | ||
| responses that react to what you type. It talks to a **local sandbox agent** | ||
| (`lib/demo-agent.mjs`), so it spends no real money and needs no credentials or backend. | ||
| - **`recap`** — the two `catalog/` demos spend real crypto autonomously across chains, so they are | ||
| **watch-only**: embedded video, playable outputs, the on-chain receipt, and a "run it locally" | ||
| note. | ||
|
|
||
| ## The sandbox agent, and going fully real | ||
|
|
||
| `/api/agent` (backed by `lib/demo-agent.mjs`) is a local sandbox: it speaks the real x402 shape | ||
| and tracks a real per-session balance in an httpOnly cookie, but calls no external service. That | ||
| makes every "see it run" panel functional out of the box, with no credentials. | ||
|
|
||
| To make a tutorial run against the **real** Nevermined flow (visitor pays via a card delegation, | ||
| real agent responds): | ||
|
|
||
| 1. Deploy its agent backend and note the URL + plan id. | ||
| 2. In `app/api/agent/route.ts`, proxy that tutorial's requests to the backend instead of the | ||
| sandbox, porting the four x402 proxy routes from `../langchain-chat-ui-nvm/src/app/api/` | ||
| (session → token → init → passthrough) — they inject the buyer's x402 token server-side, so | ||
| `NVM_API_KEY` never reaches the browser. | ||
|
|
||
| The panel code doesn't change — only what `/api/agent` talks to. | ||
|
|
||
| Unit test for the handshake logic: `node lib/demo-agent.mjs`. | ||
|
|
||
| ## Media | ||
|
|
||
| `npm run sync:media` copies the catalog `.mp4/.mp3/.jpg` into `public/media/`. The large `.mp4`s are | ||
| gitignored; the cover art and song are small enough to commit so a fresh clone still shows them. | ||
|
|
||
| ## Structure | ||
|
|
||
| ``` | ||
| showcase/ | ||
| ├── app/ | ||
| │ ├── layout.tsx # builds the sidebar groups + wraps every page in AppShell | ||
| │ ├── page.tsx # overview — intro + grouped index of all tutorials | ||
| │ ├── t/[slug]/page.tsx # tutorial page — the 4 normalized sections | ||
| │ ├── api/agent/route.ts # the "see it run" endpoint (cookie state → sandbox agent) | ||
| │ └── globals.css # the light, docs-style design system (tokens) | ||
| ├── components/ | ||
| │ ├── AppShell.tsx # persistent left sidebar + mobile drawer + active state | ||
| │ ├── LiveRunPanel.tsx # the interactive "see it run" panel (real fetches → /api/agent) | ||
| │ └── RecapPanel.tsx # video + outputs + receipt (recap tier) | ||
| ├── content/tutorials.ts # ← all tutorial content + sidebar grouping live here | ||
| └── lib/ | ||
| ├── types.ts # the normalized content model | ||
| └── demo-agent.mjs # sandbox agent logic (x402 handshake) + `node` self-test | ||
| ``` |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.