From ac20202fb60e95c5789209ff7aad2e177daeae92 Mon Sep 17 00:00:00 2001 From: KaKituken <838242595@qq.com> Date: Thu, 25 Jun 2026 13:21:49 +0900 Subject: [PATCH 1/2] Add NVIDIA mirror site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New WebHarbor mirror of nvidia.com on port 40015 (16th site). - sites/nvidia/: Flask + SQLAlchemy app — GPU/hardware catalog (Product with full spec sheet, Article, Driver, Review, CartItem, Order, WishlistItem, NewsletterSubscriber). Routes: browse/filter, product detail with specs, spec-comparison tool, driver-download finder, news, scored token-overlap search (all listings + global), auth, account, cart/checkout, wishlist, reviews, newsletter. 22 Jinja2 templates, NVIDIA black + green design. - 25 real products across 5 categories (GeForce RTX 50/40, RTX PRO / Ada workstation, data-center H200/H100/A100/B200/GH200/L40S, Jetson, SHIELD) with real published specs; 8 real NVIDIA Newsroom articles; 9 drivers. - Idempotent seeding (function-level gates) with the 4 canonical benchmark users (alice.j@test.com et al.); byte-identical reset verified. - 20 WebVoyager tasks in sites/nvidia/tasks.jsonl. - Registered in websyn_start.sh, control_server.py, Dockerfile (EXPOSE ...40015). - Re-applies the .gitignore fix: scraped_data/ and instance/ patterns had inline comments that gitignore treats as part of the pattern. Heavy assets (instance_seed/nvidia.db, static/images/) ship via the ChilleD/WebHarbor HF dataset, not this repo. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 6 +- Dockerfile | 2 +- control_server.py | 2 +- sites/nvidia/_health.py | 3 + sites/nvidia/app.py | 809 ++++++++++++++++++ sites/nvidia/requirements.txt | 10 + sites/nvidia/seed_data.py | 379 ++++++++ sites/nvidia/static/css/.gitkeep | 0 sites/nvidia/static/css/main.css | 160 ++++ sites/nvidia/static/icons/.gitkeep | 0 sites/nvidia/static/icons/favicon.svg | 1 + .../nvidia/static/icons/nvidia-logo-black.svg | 26 + .../nvidia/static/icons/nvidia-logo-white.svg | 1 + sites/nvidia/static/js/.gitkeep | 0 sites/nvidia/static/js/main.js | 7 + sites/nvidia/tasks.jsonl | 20 + sites/nvidia/templates/.gitkeep | 0 sites/nvidia/templates/404.html | 14 + sites/nvidia/templates/_product_card.html | 16 + sites/nvidia/templates/account.html | 45 + sites/nvidia/templates/account_edit.html | 37 + sites/nvidia/templates/account_password.html | 33 + sites/nvidia/templates/base.html | 84 ++ sites/nvidia/templates/cart.html | 64 ++ sites/nvidia/templates/checkout.html | 65 ++ sites/nvidia/templates/compare.html | 76 ++ sites/nvidia/templates/driver_detail.html | 38 + sites/nvidia/templates/drivers.html | 76 ++ sites/nvidia/templates/index.html | 77 ++ sites/nvidia/templates/login.html | 30 + sites/nvidia/templates/news.html | 43 + sites/nvidia/templates/news_detail.html | 45 + sites/nvidia/templates/order_detail.html | 35 + sites/nvidia/templates/product_detail.html | 130 +++ sites/nvidia/templates/products.html | 51 ++ sites/nvidia/templates/register.html | 44 + sites/nvidia/templates/search.html | 52 ++ sites/nvidia/templates/wishlist.html | 40 + websyn_start.sh | 2 +- 39 files changed, 2518 insertions(+), 5 deletions(-) create mode 100644 sites/nvidia/_health.py create mode 100644 sites/nvidia/app.py create mode 100644 sites/nvidia/requirements.txt create mode 100644 sites/nvidia/seed_data.py create mode 100644 sites/nvidia/static/css/.gitkeep create mode 100644 sites/nvidia/static/css/main.css create mode 100644 sites/nvidia/static/icons/.gitkeep create mode 100644 sites/nvidia/static/icons/favicon.svg create mode 100644 sites/nvidia/static/icons/nvidia-logo-black.svg create mode 100644 sites/nvidia/static/icons/nvidia-logo-white.svg create mode 100644 sites/nvidia/static/js/.gitkeep create mode 100644 sites/nvidia/static/js/main.js create mode 100644 sites/nvidia/tasks.jsonl create mode 100644 sites/nvidia/templates/.gitkeep create mode 100644 sites/nvidia/templates/404.html create mode 100644 sites/nvidia/templates/_product_card.html create mode 100644 sites/nvidia/templates/account.html create mode 100644 sites/nvidia/templates/account_edit.html create mode 100644 sites/nvidia/templates/account_password.html create mode 100644 sites/nvidia/templates/base.html create mode 100644 sites/nvidia/templates/cart.html create mode 100644 sites/nvidia/templates/checkout.html create mode 100644 sites/nvidia/templates/compare.html create mode 100644 sites/nvidia/templates/driver_detail.html create mode 100644 sites/nvidia/templates/drivers.html create mode 100644 sites/nvidia/templates/index.html create mode 100644 sites/nvidia/templates/login.html create mode 100644 sites/nvidia/templates/news.html create mode 100644 sites/nvidia/templates/news_detail.html create mode 100644 sites/nvidia/templates/order_detail.html create mode 100644 sites/nvidia/templates/product_detail.html create mode 100644 sites/nvidia/templates/products.html create mode 100644 sites/nvidia/templates/register.html create mode 100644 sites/nvidia/templates/search.html create mode 100644 sites/nvidia/templates/wishlist.html diff --git a/.gitignore b/.gitignore index c2efc04c..24ce1529 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,10 @@ sites/*/static/external_cache/ # ============================================================= # Intermediate / volatile — never committed anywhere. # ============================================================= -sites/*/scraped_data/ # scrape pipeline intermediate; runtime data lives in instance_seed/*.db -sites/*/instance/ # rebuilt at every container boot from instance_seed/ +# scrape pipeline intermediate; runtime data lives in instance_seed/*.db +sites/*/scraped_data/ +# rebuilt at every container boot from instance_seed/ +sites/*/instance/ sites/*/venv/ # HF download metadata produced by `hf download`. diff --git a/Dockerfile b/Dockerfile index 991e5ab6..f653515e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,6 @@ COPY control_server.py /opt/control_server.py COPY site_runner.py /opt/site_runner.py RUN chmod +x /opt/websyn_start.sh -EXPOSE 8101 40000-40014 +EXPOSE 8101 40000-40015 CMD ["/opt/websyn_start.sh"] diff --git a/control_server.py b/control_server.py index c255253c..0acbc1f4 100644 --- a/control_server.py +++ b/control_server.py @@ -26,7 +26,7 @@ 'allrecipes', 'amazon', 'apple', 'arxiv', 'bbc_news', 'booking', 'github', 'google_flights', 'google_map', 'google_search', 'huggingface', 'wolfram_alpha', 'cambridge_dictionary', - 'coursera', 'espn', + 'coursera', 'espn', 'nvidia', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/sites/nvidia/_health.py b/sites/nvidia/_health.py new file mode 100644 index 00000000..83237fc3 --- /dev/null +++ b/sites/nvidia/_health.py @@ -0,0 +1,3 @@ +"""Per-site health probe (optional, called by control_server).""" +def health(): + return {"ok": True, "site": "nvidia"} diff --git a/sites/nvidia/app.py b/sites/nvidia/app.py new file mode 100644 index 00000000..ad43582c --- /dev/null +++ b/sites/nvidia/app.py @@ -0,0 +1,809 @@ +#!/usr/bin/env python3 +"""NVIDIA.com mirror — Flask app. + +Models a faithful slice of nvidia.com for the WebHarbor benchmark: a product +catalog of GPUs/hardware (GeForce gaming, Studio/Professional, Data Center, +Embedded, Consumer Devices) with full spec sheets, a spec-comparison tool, a +driver-download finder, a news/blog section, search, accounts, cart/checkout, +wishlist, and product reviews. +""" +import os +import re +from datetime import datetime, date + +from flask import (Flask, render_template, request, redirect, url_for, + flash, jsonify, abort) +from flask_sqlalchemy import SQLAlchemy +from flask_login import (LoginManager, UserMixin, login_user, logout_user, + login_required, current_user) +from flask_wtf import FlaskForm +from flask_wtf.csrf import CSRFProtect +from flask_bcrypt import Bcrypt +from wtforms import (StringField, PasswordField, TextAreaField, IntegerField, + SelectField, BooleanField) +from wtforms.validators import (DataRequired, Email, Length, EqualTo, Optional, + NumberRange) +from sqlalchemy import or_ + +from seed_data import (PRODUCTS, ARTICLES, DRIVERS, BENCHMARK_USERS, + NOTABLE_REVIEWS, BENCHMARK_ACTIVITY) + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'nvidia-mirror-dev-secret-key' +app.config['SQLALCHEMY_DATABASE_URI'] = \ + f"sqlite:///{os.path.join(BASE_DIR, 'instance', 'nvidia.db')}" +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['WTF_CSRF_TIME_LIMIT'] = None + +os.makedirs(os.path.join(BASE_DIR, 'instance'), exist_ok=True) + +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +login_manager = LoginManager(app) +login_manager.login_view = 'login' +login_manager.login_message = 'Please log in to continue.' +login_manager.login_message_category = 'info' +csrf = CSRFProtect(app) + +CATEGORIES = [ + ("GeForce Gaming", "geforce-gaming"), + ("Studio / Professional", "studio-professional"), + ("Data Center", "data-center"), + ("Embedded", "embedded"), + ("Consumer Devices", "consumer-devices"), +] +CAT_BY_SLUG = {s: n for n, s in CATEGORIES} +CAT_SLUG = {n: s for n, s in CATEGORIES} + + +# -------------------------------------------------------------------------- +# Models +# -------------------------------------------------------------------------- +class User(db.Model, UserMixin): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False, index=True) + password_hash = db.Column(db.String(255), nullable=False) + name = db.Column(db.String(120), nullable=False) + company = db.Column(db.String(120), default='') + country = db.Column(db.String(60), default='United States') + newsletter_opt_in = db.Column(db.Boolean, default=False) + created = db.Column(db.DateTime, default=datetime(2026, 1, 1)) + + cart_items = db.relationship('CartItem', backref='user', lazy=True, + cascade='all, delete-orphan') + orders = db.relationship('Order', backref='user', lazy=True, + cascade='all, delete-orphan') + wishlist = db.relationship('WishlistItem', backref='user', lazy=True, + cascade='all, delete-orphan') + reviews = db.relationship('Review', backref='user', lazy=True, + cascade='all, delete-orphan') + + def set_password(self, pw): + self.password_hash = bcrypt.generate_password_hash(pw).decode('utf-8') + + def check_password(self, pw): + return bcrypt.check_password_hash(self.password_hash, pw) + + +class Product(db.Model): + __tablename__ = 'products' + id = db.Column(db.Integer, primary_key=True) + slug = db.Column(db.String(120), unique=True, nullable=False, index=True) + name = db.Column(db.String(160), nullable=False) + category = db.Column(db.String(60), nullable=False, index=True) + series = db.Column(db.String(80), default='') + tagline = db.Column(db.String(240), default='') + description = db.Column(db.Text, default='') + price_usd = db.Column(db.Integer, nullable=True) # null => "Contact sales" + image = db.Column(db.String(240), default='') + release_year = db.Column(db.Integer, nullable=True) + in_stock = db.Column(db.Boolean, default=True) + is_featured = db.Column(db.Boolean, default=False) + # spec sheet + architecture = db.Column(db.String(60), default='') + cuda_cores = db.Column(db.Integer, nullable=True) + tensor_cores = db.Column(db.Integer, nullable=True) + rt_cores = db.Column(db.Integer, nullable=True) + memory_gb = db.Column(db.Integer, nullable=True) + memory_type = db.Column(db.String(40), default='') + memory_bandwidth = db.Column(db.String(40), default='') + boost_clock_ghz = db.Column(db.Float, nullable=True) + tdp_watts = db.Column(db.Integer, nullable=True) + interface = db.Column(db.String(40), default='') + recommended_psu_watts = db.Column(db.Integer, nullable=True) + + reviews = db.relationship('Review', backref='product', lazy=True, + cascade='all, delete-orphan') + + @property + def category_slug(self): + return CAT_SLUG.get(self.category, '') + + @property + def avg_rating(self): + rs = [r.rating for r in self.reviews] + return round(sum(rs) / len(rs), 1) if rs else None + + @property + def review_count(self): + return len(self.reviews) + + @property + def price_display(self): + return f"${self.price_usd:,}" if self.price_usd else "Contact Sales" + + +class Review(db.Model): + __tablename__ = 'reviews' + id = db.Column(db.Integer, primary_key=True) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + rating = db.Column(db.Integer, nullable=False) + title = db.Column(db.String(160), default='') + body = db.Column(db.Text, default='') + created = db.Column(db.DateTime, default=datetime(2026, 3, 1)) + + +class Article(db.Model): + __tablename__ = 'articles' + id = db.Column(db.Integer, primary_key=True) + slug = db.Column(db.String(160), unique=True, nullable=False, index=True) + title = db.Column(db.String(240), nullable=False) + category = db.Column(db.String(60), default='') + author = db.Column(db.String(120), default='NVIDIA Newsroom') + published = db.Column(db.Date, default=date(2026, 1, 1)) + excerpt = db.Column(db.Text, default='') + body = db.Column(db.Text, default='') + image = db.Column(db.String(240), default='') + read_minutes = db.Column(db.Integer, default=4) + + +class Driver(db.Model): + __tablename__ = 'drivers' + id = db.Column(db.Integer, primary_key=True) + product_series = db.Column(db.String(80), nullable=False, index=True) + branch = db.Column(db.String(40), default='Game Ready') # Game Ready / Studio + version = db.Column(db.String(40), nullable=False) + os = db.Column(db.String(60), default='Windows 11') + released = db.Column(db.Date, default=date(2026, 1, 1)) + size_mb = db.Column(db.Integer, default=600) + highlights = db.Column(db.Text, default='') + download_count = db.Column(db.Integer, default=0) + + +class CartItem(db.Model): + __tablename__ = 'cart_items' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + quantity = db.Column(db.Integer, default=1) + product = db.relationship('Product') + + +class Order(db.Model): + __tablename__ = 'orders' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + created = db.Column(db.DateTime, default=datetime(2026, 4, 1)) + status = db.Column(db.String(40), default='Processing') + total_usd = db.Column(db.Integer, default=0) + items = db.relationship('OrderItem', backref='order', lazy=True, + cascade='all, delete-orphan') + + +class OrderItem(db.Model): + __tablename__ = 'order_items' + id = db.Column(db.Integer, primary_key=True) + order_id = db.Column(db.Integer, db.ForeignKey('orders.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + name = db.Column(db.String(160)) + price_usd = db.Column(db.Integer) + quantity = db.Column(db.Integer, default=1) + product = db.relationship('Product') + + +class WishlistItem(db.Model): + __tablename__ = 'wishlist_items' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + product = db.relationship('Product') + + +class NewsletterSubscriber(db.Model): + __tablename__ = 'newsletter' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False) + topic = db.Column(db.String(60), default='GeForce') + + +@login_manager.user_loader +def load_user(uid): + return db.session.get(User, int(uid)) + + +# -------------------------------------------------------------------------- +# Forms +# -------------------------------------------------------------------------- +class LoginForm(FlaskForm): + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired()]) + + +class RegisterForm(FlaskForm): + name = StringField('Full name', validators=[DataRequired(), Length(max=120)]) + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired(), Length(min=6)]) + confirm = PasswordField('Confirm password', + validators=[DataRequired(), EqualTo('password')]) + newsletter_opt_in = BooleanField('Email me NVIDIA news and offers') + + +class AccountForm(FlaskForm): + name = StringField('Full name', validators=[DataRequired(), Length(max=120)]) + company = StringField('Company', validators=[Optional(), Length(max=120)]) + country = StringField('Country', validators=[Optional(), Length(max=60)]) + newsletter_opt_in = BooleanField('Subscribe to the NVIDIA newsletter') + + +class PasswordForm(FlaskForm): + current = PasswordField('Current password', validators=[DataRequired()]) + new = PasswordField('New password', validators=[DataRequired(), Length(min=6)]) + confirm = PasswordField('Confirm new password', + validators=[DataRequired(), EqualTo('new')]) + + +class ReviewForm(FlaskForm): + rating = SelectField('Rating', choices=[(str(i), f'{i} stars') for i in range(5, 0, -1)], + validators=[DataRequired()]) + title = StringField('Title', validators=[DataRequired(), Length(max=160)]) + body = TextAreaField('Your review', validators=[DataRequired(), Length(max=2000)]) + + +class CheckoutForm(FlaskForm): + full_name = StringField('Full name', validators=[DataRequired()]) + address = StringField('Shipping address', validators=[DataRequired()]) + city = StringField('City', validators=[DataRequired()]) + zip_code = StringField('ZIP / Postal code', validators=[DataRequired()]) + card = StringField('Card number', validators=[DataRequired(), Length(min=12, max=19)]) + + +class SimpleForm(FlaskForm): + """CSRF-only form for POST actions (add to cart, wishlist, download, etc.).""" + + +# -------------------------------------------------------------------------- +# Search scoring — token overlap (NOT strict AND) +# -------------------------------------------------------------------------- +def _tokenize(q): + return [t for t in re.split(r'[^a-z0-9]+', (q or '').lower()) if t] + + +def _score(haystack, tokens): + if not tokens: + return 0 + h = haystack.lower() + return sum(1 for t in tokens if t in h) + + +def _product_hay(p): + return ' '.join(str(x) for x in (p.name, p.category, p.series, p.tagline, + p.description, p.architecture, p.memory_type)) + + +def _article_hay(a): + return ' '.join(str(x) for x in (a.title, a.category, a.author, a.excerpt, a.body)) + + +# -------------------------------------------------------------------------- +# Context +# -------------------------------------------------------------------------- +@app.context_processor +def inject_globals(): + cart_count = 0 + if current_user.is_authenticated: + cart_count = sum(c.quantity for c in current_user.cart_items) + return {'CATEGORIES': CATEGORIES, 'cart_count': cart_count, + 'current_year': 2026, 'simple_form': SimpleForm()} + + +# -------------------------------------------------------------------------- +# Routes — browse +# -------------------------------------------------------------------------- +@app.route('/') +def index(): + featured = Product.query.filter_by(is_featured=True).order_by( + Product.price_usd.desc().nullslast()).limit(6).all() + if not featured: + featured = Product.query.limit(6).all() + latest_news = Article.query.order_by(Article.published.desc()).limit(3).all() + flagship = Product.query.filter_by(slug='geforce-rtx-5090').first() + return render_template('index.html', featured=featured, latest_news=latest_news, + flagship=flagship) + + +@app.route('/products') +def products(): + cat = request.args.get('category', '').strip() + series = request.args.get('series', '').strip() + sort = request.args.get('sort', 'featured') + q = request.args.get('q', '').strip() + + query = Product.query + cat_name = CAT_BY_SLUG.get(cat) + if cat_name: + query = query.filter(Product.category == cat_name) + if series: + query = query.filter(Product.series == series) + items = query.all() + + tokens = _tokenize(q) + if tokens: + scored = [(s, p) for p in items if (s := _score(_product_hay(p), tokens)) > 0] + scored.sort(key=lambda sp: (-sp[0], -(sp[1].price_usd or 0))) + items = [p for _, p in scored] + elif sort == 'price-low': + items.sort(key=lambda p: (p.price_usd or 10**9)) + elif sort == 'price-high': + items.sort(key=lambda p: -(p.price_usd or 0)) + elif sort == 'newest': + items.sort(key=lambda p: -(p.release_year or 0)) + else: # featured + items.sort(key=lambda p: (not p.is_featured, -(p.price_usd or 0))) + + series_list = sorted({p.series for p in Product.query.all() if p.series}) + return render_template('products.html', items=items, cat=cat, cat_name=cat_name, + series=series, series_list=series_list, sort=sort, q=q) + + +@app.route('/products/') +def product_detail(slug): + p = Product.query.filter_by(slug=slug).first_or_404() + related = Product.query.filter(Product.category == p.category, + Product.id != p.id).limit(4).all() + in_wishlist = False + if current_user.is_authenticated: + in_wishlist = WishlistItem.query.filter_by( + user_id=current_user.id, product_id=p.id).first() is not None + reviews = Review.query.filter_by(product_id=p.id).order_by(Review.created.desc()).all() + return render_template('product_detail.html', p=p, related=related, + in_wishlist=in_wishlist, reviews=reviews, + review_form=ReviewForm()) + + +@app.route('/compare') +def compare(): + ids = [s for s in request.args.get('ids', '').split(',') if s.strip()] + items = [] + for s in ids: + p = Product.query.filter_by(slug=s.strip()).first() + if p: + items.append(p) + all_products = Product.query.order_by(Product.name).all() + return render_template('compare.html', items=items, all_products=all_products) + + +@app.route('/drivers') +def drivers(): + series = request.args.get('series', '').strip() + branch = request.args.get('branch', '').strip() + os_filter = request.args.get('os', '').strip() + query = Driver.query + if series: + query = query.filter(Driver.product_series == series) + if branch: + query = query.filter(Driver.branch == branch) + if os_filter: + query = query.filter(Driver.os == os_filter) + searched = bool(series or branch or os_filter) + results = query.order_by(Driver.released.desc()).all() if searched else [] + series_list = sorted({d.product_series for d in Driver.query.all()}) + os_list = sorted({d.os for d in Driver.query.all()}) + return render_template('drivers.html', results=results, series=series, + branch=branch, os_filter=os_filter, + series_list=series_list, os_list=os_list, searched=searched) + + +@app.route('/drivers/') +def driver_detail(driver_id): + d = db.session.get(Driver, driver_id) or abort(404) + return render_template('driver_detail.html', d=d) + + +@app.route('/drivers//download', methods=['POST']) +def driver_download(driver_id): + d = db.session.get(Driver, driver_id) or abort(404) + d.download_count += 1 + db.session.commit() + flash(f'Download started: {d.product_series} driver {d.version} ({d.branch}).', 'success') + return redirect(url_for('driver_detail', driver_id=driver_id)) + + +@app.route('/news') +def news(): + cat = request.args.get('category', '').strip() + query = Article.query + if cat: + query = query.filter(Article.category == cat) + items = query.order_by(Article.published.desc()).all() + cats = sorted({a.category for a in Article.query.all() if a.category}) + return render_template('news.html', items=items, cats=cats, cat=cat) + + +@app.route('/news/') +def news_detail(slug): + a = Article.query.filter_by(slug=slug).first_or_404() + more = Article.query.filter(Article.id != a.id).order_by( + Article.published.desc()).limit(3).all() + return render_template('news_detail.html', a=a, more=more) + + +@app.route('/search') +def search(): + q = request.args.get('q', '').strip() + tokens = _tokenize(q) + products_found, articles_found = [], [] + if tokens: + ps = [(s, p) for p in Product.query.all() + if (s := _score(_product_hay(p), tokens)) > 0] + ps.sort(key=lambda sp: (-sp[0], -(sp[1].price_usd or 0))) + products_found = [p for _, p in ps] + asc = [(s, a) for a in Article.query.all() + if (s := _score(_article_hay(a), tokens)) > 0] + asc.sort(key=lambda sa: -sa[0]) + articles_found = [a for _, a in asc] + return render_template('search.html', q=q, products=products_found, + articles=articles_found) + + +# -------------------------------------------------------------------------- +# Auth +# -------------------------------------------------------------------------- +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('account')) + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(email=form.email.data.lower().strip()).first() + if user and user.check_password(form.password.data): + login_user(user) + nxt = request.args.get('next') + return redirect(nxt or url_for('account')) + flash('Invalid email or password.', 'error') + return render_template('login.html', form=form) + + +@app.route('/register', methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('account')) + form = RegisterForm() + if form.validate_on_submit(): + email = form.email.data.lower().strip() + if User.query.filter_by(email=email).first(): + flash('An account with that email already exists.', 'error') + else: + u = User(email=email, name=form.name.data.strip(), + newsletter_opt_in=form.newsletter_opt_in.data) + u.set_password(form.password.data) + db.session.add(u) + db.session.commit() + login_user(u) + flash('Welcome to NVIDIA. Your account has been created.', 'success') + return redirect(url_for('account')) + return render_template('register.html', form=form) + + +@app.route('/logout') +@login_required +def logout(): + logout_user() + flash('You have been signed out.', 'info') + return redirect(url_for('index')) + + +# -------------------------------------------------------------------------- +# Account +# -------------------------------------------------------------------------- +@app.route('/account') +@login_required +def account(): + orders = Order.query.filter_by(user_id=current_user.id).order_by( + Order.created.desc()).all() + return render_template('account.html', orders=orders) + + +@app.route('/account/edit', methods=['GET', 'POST']) +@login_required +def account_edit(): + form = AccountForm(obj=current_user) + if form.validate_on_submit(): + current_user.name = form.name.data.strip() + current_user.company = form.company.data.strip() + current_user.country = form.country.data.strip() + current_user.newsletter_opt_in = form.newsletter_opt_in.data + db.session.commit() + flash('Your profile has been updated.', 'success') + return redirect(url_for('account')) + return render_template('account_edit.html', form=form) + + +@app.route('/account/password', methods=['GET', 'POST']) +@login_required +def account_password(): + form = PasswordForm() + if form.validate_on_submit(): + if not current_user.check_password(form.current.data): + flash('Current password is incorrect.', 'error') + else: + current_user.set_password(form.new.data) + db.session.commit() + flash('Your password has been changed.', 'success') + return redirect(url_for('account')) + return render_template('account_password.html', form=form) + + +@app.route('/account/wishlist') +@login_required +def wishlist(): + items = WishlistItem.query.filter_by(user_id=current_user.id).all() + return render_template('wishlist.html', items=items) + + +# -------------------------------------------------------------------------- +# Cart + checkout +# -------------------------------------------------------------------------- +@app.route('/cart') +@login_required +def cart(): + items = CartItem.query.filter_by(user_id=current_user.id).all() + subtotal = sum((c.product.price_usd or 0) * c.quantity for c in items) + return render_template('cart.html', items=items, subtotal=subtotal) + + +@app.route('/cart/add/', methods=['POST']) +@login_required +def cart_add(product_id): + p = db.session.get(Product, product_id) or abort(404) + if p.price_usd is None: + flash(f'{p.name} is sold through NVIDIA sales — contact sales for a quote.', 'info') + return redirect(url_for('product_detail', slug=p.slug)) + item = CartItem.query.filter_by(user_id=current_user.id, product_id=p.id).first() + if item: + item.quantity += 1 + else: + db.session.add(CartItem(user_id=current_user.id, product_id=p.id, quantity=1)) + db.session.commit() + flash(f'Added {p.name} to your cart.', 'success') + return redirect(request.referrer or url_for('cart')) + + +@app.route('/cart/update/', methods=['POST']) +@login_required +def cart_update(item_id): + item = db.session.get(CartItem, item_id) or abort(404) + if item.user_id != current_user.id: + abort(403) + qty = request.form.get('quantity', type=int) or 1 + item.quantity = max(1, qty) + db.session.commit() + return redirect(url_for('cart')) + + +@app.route('/cart/remove/', methods=['POST']) +@login_required +def cart_remove(item_id): + item = db.session.get(CartItem, item_id) or abort(404) + if item.user_id != current_user.id: + abort(403) + db.session.delete(item) + db.session.commit() + flash('Item removed from cart.', 'info') + return redirect(url_for('cart')) + + +@app.route('/checkout', methods=['GET', 'POST']) +@login_required +def checkout(): + items = CartItem.query.filter_by(user_id=current_user.id).all() + if not items: + flash('Your cart is empty.', 'info') + return redirect(url_for('cart')) + subtotal = sum((c.product.price_usd or 0) * c.quantity for c in items) + tax = round(subtotal * 0.0875) + total = subtotal + tax + form = CheckoutForm() + if request.method == 'GET': + form.full_name.data = current_user.name + if form.validate_on_submit(): + order = Order(user_id=current_user.id, status='Processing', total_usd=total) + db.session.add(order) + db.session.flush() + for c in items: + db.session.add(OrderItem(order_id=order.id, product_id=c.product_id, + name=c.product.name, price_usd=c.product.price_usd, + quantity=c.quantity)) + db.session.delete(c) + db.session.commit() + flash('Order placed! A confirmation has been emailed to you.', 'success') + return redirect(url_for('order_detail', order_id=order.id)) + return render_template('checkout.html', items=items, subtotal=subtotal, + tax=tax, total=total, form=form) + + +@app.route('/order/') +@login_required +def order_detail(order_id): + order = db.session.get(Order, order_id) or abort(404) + if order.user_id != current_user.id: + abort(403) + return render_template('order_detail.html', order=order) + + +# -------------------------------------------------------------------------- +# Wishlist + reviews + newsletter (POST actions) +# -------------------------------------------------------------------------- +@app.route('/wishlist/toggle/', methods=['POST']) +@login_required +def wishlist_toggle(product_id): + p = db.session.get(Product, product_id) or abort(404) + item = WishlistItem.query.filter_by(user_id=current_user.id, product_id=p.id).first() + if item: + db.session.delete(item) + db.session.commit() + flash(f'Removed {p.name} from your wishlist.', 'info') + else: + db.session.add(WishlistItem(user_id=current_user.id, product_id=p.id)) + db.session.commit() + flash(f'Saved {p.name} to your wishlist.', 'success') + return redirect(request.referrer or url_for('product_detail', slug=p.slug)) + + +@app.route('/products//review', methods=['POST']) +@login_required +def add_review(slug): + p = Product.query.filter_by(slug=slug).first_or_404() + form = ReviewForm() + if form.validate_on_submit(): + existing = Review.query.filter_by(product_id=p.id, user_id=current_user.id).first() + if existing: + flash('You have already reviewed this product.', 'info') + else: + db.session.add(Review(product_id=p.id, user_id=current_user.id, + rating=int(form.rating.data), + title=form.title.data.strip(), + body=form.body.data.strip(), + created=datetime(2026, 6, 1))) + db.session.commit() + flash('Thank you — your review has been posted.', 'success') + else: + flash('Please complete all review fields.', 'error') + return redirect(url_for('product_detail', slug=slug)) + + +@app.route('/newsletter', methods=['POST']) +def newsletter(): + email = (request.form.get('email') or '').lower().strip() + topic = (request.form.get('topic') or 'GeForce').strip() + if not email or '@' not in email: + flash('Please enter a valid email address.', 'error') + elif NewsletterSubscriber.query.filter_by(email=email).first(): + flash('You are already subscribed.', 'info') + else: + db.session.add(NewsletterSubscriber(email=email, topic=topic)) + db.session.commit() + flash(f'Subscribed to {topic} updates. Welcome aboard!', 'success') + return redirect(request.referrer or url_for('index')) + + +@app.errorhandler(404) +def not_found(e): + return render_template('404.html'), 404 + + +# -------------------------------------------------------------------------- +# Seeding — each function gated as a whole for byte-identical reset +# -------------------------------------------------------------------------- +def seed_catalog(): + if Product.query.count() > 0: + return + for p in PRODUCTS: + db.session.add(Product(**p)) + db.session.commit() + + +def seed_articles(): + if Article.query.count() > 0: + return + for a in ARTICLES: + db.session.add(Article(**a)) + db.session.commit() + + +def seed_drivers(): + if Driver.query.count() > 0: + return + for d in DRIVERS: + db.session.add(Driver(**d)) + db.session.commit() + + +def seed_benchmark_users(): + if User.query.filter_by(email='alice.j@test.com').first(): + return + for u in BENCHMARK_USERS: + user = User(email=u['email'], name=u['name'], company=u.get('company', ''), + country=u.get('country', 'United States'), + newsletter_opt_in=u.get('newsletter_opt_in', False), + created=datetime(2026, 1, 1)) + user.set_password(u['password']) + db.session.add(user) + db.session.commit() + + +def seed_reviews(): + if Review.query.count() > 0: + return + for r in NOTABLE_REVIEWS: + user = User.query.filter_by(email=r['user_email']).first() + prod = Product.query.filter_by(slug=r['product_slug']).first() + if user and prod: + db.session.add(Review(product_id=prod.id, user_id=user.id, + rating=r['rating'], title=r['title'], + body=r['body'], created=datetime(2026, 3, 1))) + db.session.commit() + + +def seed_benchmark_activity(): + """Pre-populate benchmark users' wishlists / orders so their account pages + feel lived-in. Gated as a whole. Deliberately avoids tasks.jsonl targets.""" + if Order.query.count() > 0 or WishlistItem.query.count() > 0: + return + for act in BENCHMARK_ACTIVITY: + user = User.query.filter_by(email=act['user_email']).first() + if not user: + continue + for slug in act.get('wishlist', []): + p = Product.query.filter_by(slug=slug).first() + if p: + db.session.add(WishlistItem(user_id=user.id, product_id=p.id)) + for o in act.get('orders', []): + prods = [Product.query.filter_by(slug=s).first() for s in o['products']] + prods = [p for p in prods if p] + if not prods: + continue + total = sum(p.price_usd or 0 for p in prods) + order = Order(user_id=user.id, status=o.get('status', 'Delivered'), + created=datetime(2026, 4, 1), total_usd=total) + db.session.add(order) + db.session.flush() + for p in prods: + db.session.add(OrderItem(order_id=order.id, product_id=p.id, + name=p.name, price_usd=p.price_usd, quantity=1)) + db.session.commit() + + +with app.app_context(): + db.create_all() + seed_catalog() + seed_articles() + seed_drivers() + seed_benchmark_users() + seed_reviews() + seed_benchmark_activity() + + +@app.route('/_health') +def health(): + return {'ok': True, 'site': 'nvidia', + 'products': Product.query.count(), + 'articles': Article.query.count()} + + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 5000)) + app.run(host='0.0.0.0', port=port, debug=False) diff --git a/sites/nvidia/requirements.txt b/sites/nvidia/requirements.txt new file mode 100644 index 00000000..4d37a226 --- /dev/null +++ b/sites/nvidia/requirements.txt @@ -0,0 +1,10 @@ +# Runtime deps are pinned in the repo Dockerfile (the image is the source of +# truth). Listed here for local dev parity. +Flask==3.1.0 +Flask-SQLAlchemy==3.1.1 +Flask-Login==0.6.3 +Flask-WTF==1.2.2 +Flask-Bcrypt==1.0.1 +SQLAlchemy==2.0.36 +WTForms==3.2.1 +email-validator==2.2.0 diff --git a/sites/nvidia/seed_data.py b/sites/nvidia/seed_data.py new file mode 100644 index 00000000..851cc2e2 --- /dev/null +++ b/sites/nvidia/seed_data.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 +"""Build-time seed data for the NVIDIA mirror. + +All consumed by the gated seed_*() functions in app.py. Specs reflect real +NVIDIA published figures. Image paths are relative to static/images/. +""" +from datetime import date + +# -------------------------------------------------------------------------- +# Products — GPUs / hardware across five categories +# -------------------------------------------------------------------------- +def _p(slug, name, category, series, price, tagline, description, image=None, + featured=False, year=2025, in_stock=True, **specs): + return dict( + slug=slug, name=name, category=category, series=series, price_usd=price, + tagline=tagline, description=description, + image=image or f"products/{slug}.png", is_featured=featured, + release_year=year, in_stock=in_stock, + architecture=specs.get('architecture', ''), + cuda_cores=specs.get('cuda_cores'), + tensor_cores=specs.get('tensor_cores'), + rt_cores=specs.get('rt_cores'), + memory_gb=specs.get('memory_gb'), + memory_type=specs.get('memory_type', ''), + memory_bandwidth=specs.get('memory_bandwidth', ''), + boost_clock_ghz=specs.get('boost_clock_ghz'), + tdp_watts=specs.get('tdp_watts'), + interface=specs.get('interface', ''), + recommended_psu_watts=specs.get('recommended_psu_watts'), + ) + + +PRODUCTS = [ + # ---- GeForce Gaming — RTX 50 Series (Blackwell) ---- + _p("geforce-rtx-5090", "GeForce RTX 5090", "GeForce Gaming", "RTX 50 Series", + 1999, "The most powerful GeForce GPU ever built.", + "Powered by the NVIDIA Blackwell architecture and DLSS 4, the GeForce RTX 5090 " + "delivers unprecedented gaming and creator performance with 32 GB of GDDR7 memory.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=21760, + tensor_cores=680, rt_cores=170, memory_gb=32, memory_type="GDDR7", + memory_bandwidth="1792 GB/s", boost_clock_ghz=2.41, tdp_watts=575, + interface="PCIe 5.0", recommended_psu_watts=1000), + _p("geforce-rtx-5080", "GeForce RTX 5080", "GeForce Gaming", "RTX 50 Series", + 999, "Game-changing performance with DLSS 4.", + "The GeForce RTX 5080 brings Blackwell efficiency, GDDR7 memory, and full ray " + "tracing to high-refresh 4K gaming.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=10752, + tensor_cores=336, rt_cores=84, memory_gb=16, memory_type="GDDR7", + memory_bandwidth="960 GB/s", boost_clock_ghz=2.62, tdp_watts=360, + interface="PCIe 5.0", recommended_psu_watts=850), + _p("geforce-rtx-5070-ti", "GeForce RTX 5070 Ti", "GeForce Gaming", "RTX 50 Series", + 749, "Elevated 1440p and 4K play.", + "16 GB of GDDR7 and Blackwell tensor cores make the RTX 5070 Ti a sweet spot for " + "high-frame-rate gaming with DLSS 4 Multi Frame Generation.", + year=2025, architecture="Blackwell", cuda_cores=8960, tensor_cores=280, + rt_cores=70, memory_gb=16, memory_type="GDDR7", memory_bandwidth="896 GB/s", + boost_clock_ghz=2.45, tdp_watts=300, interface="PCIe 5.0", + recommended_psu_watts=750), + _p("geforce-rtx-5070", "GeForce RTX 5070", "GeForce Gaming", "RTX 50 Series", + 549, "RTX 4090 performance at a fraction of the power.", + "The RTX 5070 pairs 12 GB of GDDR7 with DLSS 4 to deliver flagship-class frames " + "in today's most demanding titles.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=6144, + tensor_cores=192, rt_cores=48, memory_gb=12, memory_type="GDDR7", + memory_bandwidth="672 GB/s", boost_clock_ghz=2.51, tdp_watts=250, + interface="PCIe 5.0", recommended_psu_watts=650), + _p("geforce-rtx-5060-ti", "GeForce RTX 5060 Ti", "GeForce Gaming", "RTX 50 Series", + 429, "Mainstream gaming, supercharged.", + "With 16 GB of GDDR7, the RTX 5060 Ti makes high-fidelity 1440p gaming and local " + "AI workloads accessible.", + year=2025, architecture="Blackwell", cuda_cores=4608, tensor_cores=144, + rt_cores=36, memory_gb=16, memory_type="GDDR7", memory_bandwidth="448 GB/s", + boost_clock_ghz=2.57, tdp_watts=180, interface="PCIe 5.0", + recommended_psu_watts=550), + _p("geforce-rtx-5060", "GeForce RTX 5060", "GeForce Gaming", "RTX 50 Series", + 299, "DLSS 4 for every gamer.", + "The RTX 5060 brings Blackwell and DLSS 4 to the most popular price point in PC gaming.", + year=2025, architecture="Blackwell", cuda_cores=3840, tensor_cores=120, + rt_cores=30, memory_gb=8, memory_type="GDDR7", memory_bandwidth="448 GB/s", + boost_clock_ghz=2.50, tdp_watts=145, interface="PCIe 5.0", + recommended_psu_watts=450), + # ---- GeForce Gaming — RTX 40 Series (Ada Lovelace) ---- + _p("geforce-rtx-4090", "GeForce RTX 4090", "GeForce Gaming", "RTX 40 Series", + 1599, "Beyond fast. The Ada Lovelace flagship.", + "24 GB of GDDR6X and 16,384 CUDA cores made the RTX 4090 the definitive 4K and " + "creator GPU of the Ada generation.", + year=2022, architecture="Ada Lovelace", cuda_cores=16384, tensor_cores=512, + rt_cores=128, memory_gb=24, memory_type="GDDR6X", memory_bandwidth="1008 GB/s", + boost_clock_ghz=2.52, tdp_watts=450, interface="PCIe 4.0", + recommended_psu_watts=850), + _p("geforce-rtx-4080-super", "GeForce RTX 4080 SUPER", "GeForce Gaming", "RTX 40 Series", + 999, "Enthusiast 4K with Ada efficiency.", + "The RTX 4080 SUPER delivers high-refresh 4K gaming and creator acceleration with " + "16 GB of fast GDDR6X.", + year=2024, architecture="Ada Lovelace", cuda_cores=10240, tensor_cores=320, + rt_cores=80, memory_gb=16, memory_type="GDDR6X", memory_bandwidth="736 GB/s", + boost_clock_ghz=2.55, tdp_watts=320, interface="PCIe 4.0", + recommended_psu_watts=750), + _p("geforce-rtx-4070-super", "GeForce RTX 4070 SUPER", "GeForce Gaming", "RTX 40 Series", + 599, "Seriously fast 1440p.", + "More cores than the original RTX 4070 plus DLSS 3 Frame Generation make the 4070 " + "SUPER an outstanding 1440p performer.", + year=2024, architecture="Ada Lovelace", cuda_cores=7168, tensor_cores=224, + rt_cores=56, memory_gb=12, memory_type="GDDR6X", memory_bandwidth="504 GB/s", + boost_clock_ghz=2.48, tdp_watts=220, interface="PCIe 4.0", + recommended_psu_watts=650), + _p("geforce-rtx-4060", "GeForce RTX 4060", "GeForce Gaming", "RTX 40 Series", + 299, "The most popular way into RTX.", + "Efficient Ada Lovelace gaming with DLSS 3 at 1080p, sipping just 115 W.", + year=2023, architecture="Ada Lovelace", cuda_cores=3072, tensor_cores=96, + rt_cores=24, memory_gb=8, memory_type="GDDR6", memory_bandwidth="272 GB/s", + boost_clock_ghz=2.46, tdp_watts=115, interface="PCIe 4.0", + recommended_psu_watts=550), + + # ---- Studio / Professional (RTX workstation) ---- + _p("rtx-pro-6000-blackwell", "RTX PRO 6000 Blackwell", "Studio / Professional", + "RTX PRO Blackwell", 8565, "The ultimate workstation GPU.", + "96 GB of GDDR7 and the Blackwell architecture power the most demanding AI, " + "rendering, and simulation workloads in professional workstations.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=24064, + memory_gb=96, memory_type="GDDR7 ECC", memory_bandwidth="1792 GB/s", + tdp_watts=600, interface="PCIe 5.0"), + _p("rtx-6000-ada", "RTX 6000 Ada Generation", "Studio / Professional", + "RTX Ada", 6800, "Workstation performance for rendering and AI.", + "48 GB of ECC GDDR6 and Ada Lovelace cores deliver massive throughput for " + "visualization, simulation, and AI development.", + year=2022, architecture="Ada Lovelace", cuda_cores=18176, memory_gb=48, + memory_type="GDDR6 ECC", memory_bandwidth="960 GB/s", tdp_watts=300, + interface="PCIe 4.0"), + _p("rtx-5000-ada", "RTX 5000 Ada Generation", "Studio / Professional", + "RTX Ada", 4000, "Pro-grade power in a 250 W envelope.", + "32 GB of ECC memory for large scenes, multi-app workflows, and AI inference at " + "the desk.", + year=2023, architecture="Ada Lovelace", cuda_cores=12800, memory_gb=32, + memory_type="GDDR6 ECC", memory_bandwidth="576 GB/s", tdp_watts=250, + interface="PCIe 4.0"), + _p("rtx-4000-ada", "RTX 4000 Ada Generation", "Studio / Professional", + "RTX Ada", 1250, "Compact, single-slot pro acceleration.", + "20 GB of memory in a single-slot, 130 W card for space-constrained workstations.", + year=2023, architecture="Ada Lovelace", cuda_cores=6144, memory_gb=20, + memory_type="GDDR6 ECC", memory_bandwidth="360 GB/s", tdp_watts=130, + interface="PCIe 4.0"), + + # ---- Data Center / AI (price = contact sales) ---- + _p("dgx-b200", "NVIDIA Blackwell B200", "Data Center", "Blackwell", + None, "The engine of the AI factory.", + "The B200 Tensor Core GPU delivers a generational leap for trillion-parameter " + "training and inference with 192 GB of HBM3e.", + featured=True, year=2025, architecture="Blackwell", memory_gb=192, + memory_type="HBM3e", memory_bandwidth="8 TB/s", tdp_watts=1000, + interface="SXM"), + _p("h200-tensor-core", "NVIDIA H200 Tensor Core GPU", "Data Center", "Hopper", + None, "Supercharged generative AI and HPC.", + "The H200 is the first GPU with 141 GB of HBM3e, nearly doubling capacity and " + "boosting bandwidth to 4.8 TB/s for LLM inference.", + featured=True, year=2024, architecture="Hopper", memory_gb=141, + memory_type="HBM3e", memory_bandwidth="4.8 TB/s", tdp_watts=700, + interface="SXM"), + _p("h100-tensor-core", "NVIDIA H100 Tensor Core GPU", "Data Center", "Hopper", + None, "The proven workhorse of modern AI.", + "With the Transformer Engine and 80 GB of HBM3, the H100 accelerates LLM training " + "and inference at data-center scale.", + year=2022, architecture="Hopper", memory_gb=80, memory_type="HBM3", + memory_bandwidth="3.35 TB/s", tdp_watts=700, interface="SXM"), + _p("a100-tensor-core", "NVIDIA A100 Tensor Core GPU", "Data Center", "Ampere", + None, "The data-center GPU that defined AI scale.", + "80 GB of HBM2e and Multi-Instance GPU make the A100 a versatile platform for " + "training, inference, and HPC.", + year=2020, architecture="Ampere", memory_gb=80, memory_type="HBM2e", + memory_bandwidth="2039 GB/s", tdp_watts=400, interface="SXM"), + _p("l40s", "NVIDIA L40S", "Data Center", "Ada", + None, "Universal GPU for AI, graphics, and video.", + "48 GB of GDDR6 and Ada Lovelace cores make the L40S a versatile data-center GPU " + "for inference, fine-tuning, and rendering.", + year=2023, architecture="Ada Lovelace", cuda_cores=18176, memory_gb=48, + memory_type="GDDR6", memory_bandwidth="864 GB/s", tdp_watts=350, + interface="PCIe 4.0"), + _p("gh200-grace-hopper", "NVIDIA GH200 Grace Hopper Superchip", "Data Center", + "Grace Hopper", None, "CPU and GPU, coherently fused.", + "The GH200 connects a Grace CPU and Hopper GPU over NVLink-C2C for giant-model AI " + "and HPC with up to 624 GB of fast memory.", + year=2024, architecture="Grace Hopper", memory_gb=144, memory_type="HBM3e", + memory_bandwidth="4.9 TB/s", tdp_watts=1000, interface="NVLink-C2C"), + + # ---- Embedded / Edge (Jetson) ---- + _p("jetson-orin-nano-super", "Jetson Orin Nano Super Developer Kit", "Embedded", + "Jetson Orin", 249, "The world's most affordable generative AI computer.", + "67 TOPS of AI performance in a tiny module — the Jetson Orin Nano Super powers " + "edge robotics, vision, and on-device LLMs.", + featured=True, year=2024, architecture="Ampere", cuda_cores=1024, + memory_gb=8, memory_type="LPDDR5", memory_bandwidth="102 GB/s", tdp_watts=25, + interface="—"), + _p("jetson-agx-orin", "Jetson AGX Orin 64GB", "Embedded", "Jetson Orin", + 1999, "Server-class AI at the edge.", + "Up to 275 TOPS and 64 GB of memory bring advanced robotics and autonomous machine " + "workloads to a 60 W module.", + year=2022, architecture="Ampere", cuda_cores=2048, memory_gb=64, + memory_type="LPDDR5", memory_bandwidth="204 GB/s", tdp_watts=60, interface="—"), + _p("jetson-orin-nx", "Jetson Orin NX 16GB", "Embedded", "Jetson Orin", + 699, "Compact power for autonomous machines.", + "100 TOPS in a small module for drones, robots, and smart cameras.", + year=2023, architecture="Ampere", cuda_cores=1024, memory_gb=16, + memory_type="LPDDR5", memory_bandwidth="102 GB/s", tdp_watts=25, interface="—"), + + # ---- Consumer Devices (SHIELD) ---- + _p("shield-tv-pro", "SHIELD TV Pro", "Consumer Devices", "SHIELD", + 199, "The ultimate 4K HDR streaming media player.", + "AI-enhanced 4K upscaling, Dolby Vision and Atmos, and Google TV — powered by the " + "NVIDIA Tegra X1+ processor.", + featured=True, year=2019, architecture="Tegra X1+", memory_gb=3, + memory_type="LPDDR4", tdp_watts=40, interface="HDMI 2.0b"), + _p("shield-tv", "SHIELD TV", "Consumer Devices", "SHIELD", + 149, "Compact 4K HDR streaming, supercharged by AI.", + "A tube-shaped 4K HDR streamer with AI upscaling and the Tegra X1+ processor.", + year=2019, architecture="Tegra X1+", memory_gb=2, memory_type="LPDDR4", + tdp_watts=40, interface="HDMI 2.0b"), +] + +# -------------------------------------------------------------------------- +# News / blog articles +# -------------------------------------------------------------------------- +ARTICLES = [ + dict(slug="top500-green500-supercomputers-isc-2026", + title="NVIDIA Powers Over 400 of the World's 500 Fastest Supercomputers", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 23), + image="news/top500-green500-supercomputers-isc-2026.png", read_minutes=5, + excerpt="NVIDIA technologies power more than 400 of the world's 500 fastest " + "supercomputers — 81% of the TOP500.", + body="At ISC 2026, the latest TOP500 list confirmed that NVIDIA technologies now " + "power more than 400 of the world's 500 fastest supercomputers — 81% of the " + "list. NVIDIA also dominates the Green500 ranking of the most energy-efficient " + "systems, underscoring how accelerated computing delivers both performance and " + "efficiency for the world's most demanding scientific workloads."), + dict(slug="jupiter-exascale-supercomputing-science", + title="At ISC, JUPITER Shows What Exascale Science Looks Like", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/jupiter-exascale-supercomputing-science.png", read_minutes=6, + excerpt="Europe's first exascale supercomputer — running on NVIDIA Grace Hopper " + "Superchips — is mapping the brain, modeling climate, and advancing 6G AI.", + body="JUPITER, Europe's first exascale supercomputer, is built on NVIDIA Grace " + "Hopper Superchips. Researchers are using it to map the human brain, model " + "climate systems, advance 6G AI research, and break scientific records — a " + "showcase of what exascale-class accelerated computing makes possible."), + dict(slug="ai-for-science-software-cuda", + title="New NVIDIA AI Software Unlocks Scientific Discoveries", + category="AI", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/ai-for-science-software-cuda.png", read_minutes=5, + excerpt="NVIDIA CUDA-X libraries, microservices and reference code accelerate AI " + "for science.", + body="From materials simulation to experimental astronomy, new NVIDIA CUDA-X " + "libraries, microservices, and reference workflows are accelerating AI for " + "science. The tools help researchers move from data to discovery faster across " + "physics, chemistry, biology, and astronomy."), + dict(slug="nvidia-vera-cpu-los-alamos-national-laboratory", + title="NVIDIA Vera CPU Opens the Way for Agentic Scientific AI at Los Alamos", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/nvidia-vera-cpu-los-alamos-national-laboratory.png", read_minutes=5, + excerpt="Mission, Vision and Veritas supercomputers with Vera CPUs to advance " + "materials simulation, scientific AI agents and molecular design.", + body="Los Alamos National Laboratory is deploying new Mission, Vision, and Veritas " + "supercomputers powered by the NVIDIA Vera CPU. The systems will advance " + "materials simulation, agentic scientific AI, and molecular design for national " + "research priorities."), + dict(slug="blackwell-mlperf-training-6-0", + title="Fastest, Largest, Strongest: NVIDIA Blackwell Sweeps MLPerf Training 6.0", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 16), + image="news/blackwell-mlperf-training-6-0.png", read_minutes=6, + excerpt="NVIDIA delivers the performance, scale and reliability that frontier " + "training requires — in benchmarks and beyond.", + body="In the latest MLPerf Training 6.0 results, the NVIDIA Blackwell platform " + "swept every benchmark, delivering record performance at scale. The results " + "demonstrate the throughput and reliability that frontier model training " + "demands across the largest GPU clusters."), + dict(slug="rtx-ai-garage-local-gemma-diffusion", + title="NVIDIA Accelerates Google DeepMind's DiffusionGemma for Local AI", + category="AI", author="NVIDIA Newsroom", published=date(2026, 6, 10), + image="news/rtx-ai-garage-local-gemma-diffusion.png", read_minutes=5, + excerpt="The new DiffusionGemma open model generates text in parallel and is " + "optimized to run locally on the NVIDIA RTX platform.", + body="DiffusionGemma, a new open model from Google DeepMind, generates text in " + "parallel rather than one token at a time. NVIDIA has optimized it to run " + "locally on GeForce RTX and RTX PRO GPUs, bringing fast, private generative " + "AI to the desktop."), + dict(slug="eco-wave-power-ai-digital-twins", + title="Eco Wave Power Turns Waves Into Watts With NVIDIA AI and Digital Twins", + category="AI", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/eco-wave-power-ai-digital-twins.png", read_minutes=4, + excerpt="An NVIDIA Inception startup is developing wave-energy technology powered " + "by NVIDIA AI infrastructure and digital twins.", + body="Eco Wave Power, part of the NVIDIA Inception program's Sustainable Futures " + "initiative, is building wave-energy technology using NVIDIA AI infrastructure " + "and Omniverse digital twins to simulate and optimize clean power generation " + "from ocean waves."), + dict(slug="halos-os-robotaxi-safety", + title="For Robotaxis, Safety Must Be Built In, Not Bolted On", + category="Driving", author="NVIDIA Newsroom", published=date(2026, 6, 10), + image="news/halos-os-robotaxi-safety.png", read_minutes=5, + excerpt="NVIDIA Halos OS delivers safety-certified platform software, standardized " + "interfaces, AI guardrails and pre-deployment validation for L4 robotaxis.", + body="NVIDIA Halos OS provides safety-certified platform software, standardized " + "interfaces, AI guardrails, and pre-deployment validation for Level 4 robotaxi " + "fleets. The approach builds safety into the autonomous-driving stack from the " + "ground up rather than adding it after the fact."), +] + +# -------------------------------------------------------------------------- +# Driver downloads +# -------------------------------------------------------------------------- +DRIVERS = [ + dict(product_series="GeForce RTX 50 Series", branch="Game Ready", version="566.36", + os="Windows 11", released=date(2026, 6, 10), size_mb=712, + highlights="Day-0 support for the latest AAA titles and DLSS 4 updates."), + dict(product_series="GeForce RTX 50 Series", branch="Studio", version="566.14", + os="Windows 11", released=date(2026, 5, 28), size_mb=705, + highlights="Optimized and validated for creative applications."), + dict(product_series="GeForce RTX 50 Series", branch="Game Ready", version="566.36", + os="Windows 10", released=date(2026, 6, 10), size_mb=708, + highlights="Day-0 support for the latest AAA titles and DLSS 4 updates."), + dict(product_series="GeForce RTX 40 Series", branch="Game Ready", version="566.36", + os="Windows 11", released=date(2026, 6, 10), size_mb=690, + highlights="Performance optimizations for recent releases."), + dict(product_series="GeForce RTX 40 Series", branch="Studio", version="566.14", + os="Windows 11", released=date(2026, 5, 28), size_mb=688, + highlights="Validated for content-creation workflows."), + dict(product_series="GeForce RTX 40 Series", branch="Game Ready", version="565.90", + os="Linux", released=date(2026, 4, 30), size_mb=395, + highlights="Production branch for Linux gaming and Vulkan."), + dict(product_series="GeForce RTX 30 Series", branch="Game Ready", version="566.36", + os="Windows 11", released=date(2026, 6, 10), size_mb=672, + highlights="Continued support for Ampere GeForce GPUs."), + dict(product_series="RTX PRO / Workstation", branch="Studio", version="553.62", + os="Windows 11", released=date(2026, 5, 15), size_mb=820, + highlights="Enterprise-validated production branch (NVIDIA RTX Enterprise)."), + dict(product_series="RTX PRO / Workstation", branch="Studio", version="553.24", + os="Linux", released=date(2026, 3, 20), size_mb=430, + highlights="Long-term support branch for professional Linux workstations."), +] + +# -------------------------------------------------------------------------- +# Benchmark users — alice.j@test.com et al. (password: TestPass123!) +# -------------------------------------------------------------------------- +BENCHMARK_USERS = [ + dict(email="alice.j@test.com", name="Alice Johnson", password="TestPass123!", + company="Pixel Forge Studios", country="United States", newsletter_opt_in=True), + dict(email="bob.c@test.com", name="Bob Chen", password="TestPass123!", + company="", country="United States", newsletter_opt_in=False), + dict(email="carol.d@test.com", name="Carol Davis", password="TestPass123!", + company="Helix Robotics", country="Canada", newsletter_opt_in=True), + dict(email="david.k@test.com", name="David Kim", password="TestPass123!", + company="", country="United Kingdom", newsletter_opt_in=False), +] + +# -------------------------------------------------------------------------- +# Notable reviews — placed on NON-task-target products to avoid answer leaks +# -------------------------------------------------------------------------- +NOTABLE_REVIEWS = [ + dict(user_email="bob.c@test.com", product_slug="geforce-rtx-4090", rating=5, + title="Still a monster", body="Two years on and the 4090 handles everything at 4K."), + dict(user_email="carol.d@test.com", product_slug="jetson-agx-orin", rating=5, + title="Perfect for our robots", body="64 GB lets us run large perception models at the edge."), + dict(user_email="david.k@test.com", product_slug="geforce-rtx-4070-super", rating=4, + title="Great 1440p value", body="Runs quiet and cool; DLSS 3 is excellent."), + dict(user_email="bob.c@test.com", product_slug="shield-tv-pro", rating=5, + title="Best streamer", body="The AI upscaling genuinely makes 1080p content look great."), + dict(user_email="carol.d@test.com", product_slug="rtx-6000-ada", rating=5, + title="Workstation beast", body="48 GB of ECC memory handles our largest scenes."), +] + +# -------------------------------------------------------------------------- +# Benchmark activity — pre-existing wishlists / orders (avoid task targets) +# -------------------------------------------------------------------------- +BENCHMARK_ACTIVITY = [ + dict(user_email="alice.j@test.com", + wishlist=["rtx-pro-6000-blackwell", "geforce-rtx-4080-super"], + orders=[dict(products=["geforce-rtx-4090"], status="Delivered")]), + dict(user_email="carol.d@test.com", + wishlist=["jetson-orin-nx"], + orders=[dict(products=["jetson-agx-orin"], status="Delivered")]), +] diff --git a/sites/nvidia/static/css/.gitkeep b/sites/nvidia/static/css/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/static/css/main.css b/sites/nvidia/static/css/main.css new file mode 100644 index 00000000..7eed328f --- /dev/null +++ b/sites/nvidia/static/css/main.css @@ -0,0 +1,160 @@ +/* NVIDIA mirror — design system (black + NVIDIA green #76B900) */ +:root{ + --nv-green:#76b900; --nv-green-d:#5e9400; --ink:#1a1a1a; --muted:#6b6b6b; + --line:#e3e3e3; --bg:#ffffff; --bg-soft:#f7f7f7; --black:#000; --dark:#1a1a1a; + --radius:6px; --maxw:1280px; +} +*{box-sizing:border-box} +html,body{margin:0;padding:0} +body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; + color:var(--ink);background:var(--bg);line-height:1.5;font-size:15px} +a{color:inherit;text-decoration:none} +a:hover{color:var(--nv-green-d)} +img{max-width:100%;display:block} +.container{max-width:var(--maxw);margin:0 auto;padding:0 24px} + +/* header */ +.topbar{background:var(--black);color:#fff;font-size:13px} +.topbar .container{display:flex;align-items:center;gap:22px;height:56px} +.logo{display:flex;align-items:center;gap:8px;font-weight:800;letter-spacing:.5px;font-size:20px} +.logo .eye{color:var(--nv-green);font-size:24px;line-height:1} +.logo b{color:#fff} +.nav{display:flex;gap:20px;flex:1} +.nav a{color:#eaeaea;font-weight:500} +.nav a:hover{color:var(--nv-green)} +.topbar .actions{display:flex;align-items:center;gap:16px} +.topbar .actions a{color:#eaeaea} +.topbar .actions a:hover{color:var(--nv-green)} +.searchbox{display:flex;align-items:center;background:#222;border-radius:20px;padding:5px 12px} +.searchbox input{background:transparent;border:0;color:#fff;outline:none;width:150px} +.searchbox input::placeholder{color:#999} +.cart-badge{background:var(--nv-green);color:#000;border-radius:10px;padding:0 7px; + font-weight:700;font-size:11px;margin-left:3px} + +/* buttons */ +.btn{display:inline-block;background:var(--nv-green);color:#000;font-weight:700; + padding:10px 22px;border-radius:var(--radius);border:0;cursor:pointer;font-size:14px} +.btn:hover{background:var(--nv-green-d);color:#000} +.btn-ghost{background:transparent;border:1px solid #555;color:#fff} +.btn-ghost:hover{border-color:var(--nv-green);color:var(--nv-green)} +.btn-dark{background:var(--dark);color:#fff} +.btn-dark:hover{background:#000;color:var(--nv-green)} +.btn-block{display:block;width:100%;text-align:center} +.btn:disabled{opacity:.5;cursor:not-allowed} + +/* hero */ +.hero{background:var(--black);color:#fff;position:relative;overflow:hidden} +.hero .container{display:flex;align-items:center;gap:40px;padding:64px 24px;min-height:420px} +.hero-copy{flex:1;z-index:2} +.hero-copy .eyebrow{color:var(--nv-green);font-weight:700;letter-spacing:1px;text-transform:uppercase;font-size:13px} +.hero-copy h1{font-size:52px;line-height:1.05;margin:12px 0 16px;font-weight:800} +.hero-copy p{font-size:18px;color:#cfcfcf;max-width:540px} +.hero-media{flex:1;text-align:center} +.hero-media img{margin:0 auto;filter:drop-shadow(0 20px 40px rgba(118,185,0,.25))} + +/* sections */ +.section{padding:56px 0} +.section h2{font-size:30px;font-weight:800;margin:0 0 6px} +.section .sub{color:var(--muted);margin:0 0 28px} +.section.alt{background:var(--bg-soft)} + +/* category pills */ +.pills{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0} +.pill{border:1px solid var(--line);border-radius:20px;padding:7px 16px;background:#fff;font-weight:600;font-size:13px} +.pill.active,.pill:hover{border-color:var(--nv-green);color:var(--nv-green-d)} + +/* product grid */ +.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:22px} +.card{border:1px solid var(--line);border-radius:8px;background:#fff;overflow:hidden; + display:flex;flex-direction:column;transition:box-shadow .15s,transform .15s} +.card:hover{box-shadow:0 8px 24px rgba(0,0,0,.10);transform:translateY(-2px)} +.card .thumb{background:#0b0b0b;aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;padding:14px} +.card .thumb img{max-height:100%;object-fit:contain} +.card .body{padding:14px 16px 18px;display:flex;flex-direction:column;gap:6px;flex:1} +.card .series{color:var(--nv-green-d);font-weight:700;font-size:12px;text-transform:uppercase} +.card .name{font-weight:700;font-size:16px} +.card .tagline{color:var(--muted);font-size:13px;flex:1} +.card .price{font-weight:800;font-size:17px;margin-top:6px} +.card .stars{color:#f5a623;font-size:13px} +.muted{color:var(--muted)} +.green{color:var(--nv-green-d)} + +/* spec table */ +.specs{width:100%;border-collapse:collapse;margin:18px 0} +.specs th,.specs td{text-align:left;padding:11px 14px;border-bottom:1px solid var(--line);font-size:14px} +.specs th{width:240px;color:var(--muted);font-weight:600} +.compare-tbl{width:100%;border-collapse:collapse;overflow-x:auto} +.compare-tbl th,.compare-tbl td{border:1px solid var(--line);padding:12px 14px;text-align:center;font-size:14px} +.compare-tbl th:first-child,.compare-tbl td:first-child{text-align:left;color:var(--muted);font-weight:600;background:var(--bg-soft)} +.compare-tbl thead th{background:#000;color:#fff} +.scroll-x{overflow-x:auto} + +/* detail layout */ +.detail{display:grid;grid-template-columns:1fr 1fr;gap:40px;padding:40px 0} +.detail .media{background:#0b0b0b;border-radius:10px;padding:30px;display:flex;align-items:center;justify-content:center} +.detail h1{font-size:34px;margin:6px 0 10px;font-weight:800} +.detail .price{font-size:26px;font-weight:800;margin:14px 0} +.row{display:flex;gap:12px;align-items:center;flex-wrap:wrap} + +/* forms */ +.form{max-width:440px;margin:0 auto} +.form.wide{max-width:720px} +.field{margin-bottom:16px} +.field label{display:block;font-weight:600;margin-bottom:6px;font-size:14px} +.field input,.field select,.field textarea{width:100%;padding:10px 12px;border:1px solid var(--line); + border-radius:var(--radius);font-size:14px;font-family:inherit} +.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--nv-green)} +.err{color:#c0392b;font-size:13px;margin-top:4px} +.check{display:flex;align-items:center;gap:8px} +.check input{width:auto} + +/* flash */ +.flashes{margin:16px 0} +.flash{padding:12px 16px;border-radius:var(--radius);margin-bottom:10px;font-size:14px} +.flash.success{background:#eef7df;color:#3c6300;border:1px solid #cce6a0} +.flash.error{background:#fdecea;color:#a83228;border:1px solid #f5c6c2} +.flash.info{background:#eef4fb;color:#27496b;border:1px solid #c9ddf0} + +/* misc */ +.page-head{background:var(--black);color:#fff;padding:40px 0} +.page-head h1{font-size:34px;margin:0;font-weight:800} +.page-head p{color:#bbb;margin:8px 0 0} +.breadcrumb{font-size:13px;color:var(--muted);margin:18px 0} +.breadcrumb a:hover{color:var(--nv-green-d)} +.toolbar{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;margin:18px 0} +.toolbar select{padding:8px 10px;border:1px solid var(--line);border-radius:var(--radius)} +.tag{display:inline-block;background:var(--bg-soft);border:1px solid var(--line);border-radius:4px; + padding:2px 8px;font-size:12px;color:var(--muted);margin-right:6px} +.badge-stock{color:var(--nv-green-d);font-weight:700;font-size:13px} +.badge-out{color:#c0392b;font-weight:700;font-size:13px} + +/* news */ +.news-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:24px} +.news-card{border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#fff} +.news-card .thumb{aspect-ratio:16/9;background:#0b0b0b;overflow:hidden} +.news-card .thumb img{width:100%;height:100%;object-fit:cover} +.news-card .body{padding:16px} +.news-card .cat{color:var(--nv-green-d);font-weight:700;font-size:12px;text-transform:uppercase} +.news-card h3{margin:6px 0;font-size:17px} +.article{max-width:760px;margin:0 auto;padding:40px 0} +.article h1{font-size:36px;font-weight:800;line-height:1.1} +.article .meta{color:var(--muted);margin:12px 0 24px;font-size:14px} +.article img{border-radius:8px;margin:20px 0} +.article p{font-size:17px;margin:18px 0} + +/* footer */ +.footer{background:#000;color:#bbb;padding:40px 0;font-size:13px;margin-top:40px} +.footer .cols{display:flex;flex-wrap:wrap;gap:40px;justify-content:space-between} +.footer h4{color:#fff;font-size:13px;text-transform:uppercase;letter-spacing:.5px;margin:0 0 12px} +.footer a{display:block;color:#bbb;margin-bottom:7px} +.footer a:hover{color:var(--nv-green)} +.footer .news-signup input{padding:9px 12px;border-radius:4px 0 0 4px;border:0;width:200px} +.footer .news-signup button{border-radius:0 4px 4px 0} +.footer .copyright{border-top:1px solid #222;margin-top:28px;padding-top:18px;color:#777} + +@media(max-width:880px){ + .hero .container{flex-direction:column;padding:40px 24px} + .hero-copy h1{font-size:36px} + .detail{grid-template-columns:1fr} + .nav{display:none} +} diff --git a/sites/nvidia/static/icons/.gitkeep b/sites/nvidia/static/icons/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/static/icons/favicon.svg b/sites/nvidia/static/icons/favicon.svg new file mode 100644 index 00000000..e8c141fd --- /dev/null +++ b/sites/nvidia/static/icons/favicon.svg @@ -0,0 +1 @@ + diff --git a/sites/nvidia/static/icons/nvidia-logo-black.svg b/sites/nvidia/static/icons/nvidia-logo-black.svg new file mode 100644 index 00000000..0d7efb3f --- /dev/null +++ b/sites/nvidia/static/icons/nvidia-logo-black.svg @@ -0,0 +1,26 @@ + + + + +NVIDIA-Logo + + + + diff --git a/sites/nvidia/static/icons/nvidia-logo-white.svg b/sites/nvidia/static/icons/nvidia-logo-white.svg new file mode 100644 index 00000000..f4f17895 --- /dev/null +++ b/sites/nvidia/static/icons/nvidia-logo-white.svg @@ -0,0 +1 @@ +NVIDIA_logo \ No newline at end of file diff --git a/sites/nvidia/static/js/.gitkeep b/sites/nvidia/static/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/static/js/main.js b/sites/nvidia/static/js/main.js new file mode 100644 index 00000000..2aa215c7 --- /dev/null +++ b/sites/nvidia/static/js/main.js @@ -0,0 +1,7 @@ +// NVIDIA mirror — minimal progressive enhancement +document.addEventListener('DOMContentLoaded', function () { + // auto-dismiss flash messages after a few seconds + document.querySelectorAll('.flash').forEach(function (el) { + setTimeout(function () { el.style.transition = 'opacity .4s'; el.style.opacity = '0'; }, 6000); + }); +}); diff --git a/sites/nvidia/tasks.jsonl b/sites/nvidia/tasks.jsonl new file mode 100644 index 00000000..3b66184c --- /dev/null +++ b/sites/nvidia/tasks.jsonl @@ -0,0 +1,20 @@ +{"web_name": "NVIDIA", "id": "NVIDIA--0", "ques": "Find the GeForce RTX 5090 graphics card and report how much memory it has and what type.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--1", "ques": "How many CUDA cores does the GeForce RTX 5080 have?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--2", "ques": "What is the graphics card power (TDP, in watts) of the GeForce RTX 4090?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--3", "ques": "Among the GeForce RTX 50 Series graphics cards, which one is the least expensive, and what is its price?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--4", "ques": "Browse the Data Center products and find which GPU has 141 GB of memory.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--5", "ques": "In the Embedded / robotics products, what is the price of the Jetson Orin Nano Super Developer Kit?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--6", "ques": "Using the GPU comparison tool, compare the GeForce RTX 5090 and the GeForce RTX 4090. Which has more CUDA cores, and how many more?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--7", "ques": "Compare the GeForce RTX 5080 and the GeForce RTX 4080 SUPER. Which one has the higher memory bandwidth?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--8", "ques": "Of all the Studio / Professional GPUs, which one has the most memory, and how much?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--9", "ques": "Go to the Drivers section and find the latest Game Ready driver version for the GeForce RTX 50 Series on Windows 11.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--10", "ques": "Find the Studio branch driver for the GeForce RTX 40 Series on Windows 11 and report its version number.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--11", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the GeForce RTX 5080 to your cart and proceed through checkout to place the order.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--12", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the least expensive GeForce RTX 40 Series graphics card to your cart.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--13", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then save the GeForce RTX 5070 Ti to your wishlist.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--14", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then change the country on your profile to Germany.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--15", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then write a 5-star review titled 'Incredible' for the Jetson Orin Nano Super Developer Kit.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--16", "ques": "Sign in with email alice.j@test.com and password TestPass123!. Your wishlist already has more than one item — remove the workstation GPU (the RTX PRO 6000 Blackwell) from it.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--17", "ques": "Among all GeForce Gaming graphics cards with at least 16 GB of memory, find the least expensive one and add it to your cart (sign in as alice.j@test.com / TestPass123!).", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--18", "ques": "In the NVIDIA Newsroom, find the article about Blackwell sweeping MLPerf Training 6.0 and report its publication date.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--19", "ques": "Subscribe the email address gamer42@example.com to the NVIDIA newsletter.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} diff --git a/sites/nvidia/templates/.gitkeep b/sites/nvidia/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/templates/404.html b/sites/nvidia/templates/404.html new file mode 100644 index 00000000..9d9c3aa1 --- /dev/null +++ b/sites/nvidia/templates/404.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title %}Page Not Found — NVIDIA{% endblock %} +{% block content %} + +
+
+
Error 404
+

Page Not Found

+

The page you're looking for doesn't exist or has been moved.

+ Back to Home +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/_product_card.html b/sites/nvidia/templates/_product_card.html new file mode 100644 index 00000000..0e711ff4 --- /dev/null +++ b/sites/nvidia/templates/_product_card.html @@ -0,0 +1,16 @@ +{# expects: p (Product) #} + +
+ {% if p.image %} + {{ p.name }} + {% endif %} +
+
+ {% if p.series %}
{{ p.series }}
{% endif %} +
{{ p.name }}
+ {% if p.tagline %}
{{ p.tagline }}
{% endif %} + {% if p.avg_rating %}
★ {{ p.avg_rating }} ({{ p.review_count }})
{% endif %} +
{{ p.price_display }}
+
+
diff --git a/sites/nvidia/templates/account.html b/sites/nvidia/templates/account.html new file mode 100644 index 00000000..26e519ec --- /dev/null +++ b/sites/nvidia/templates/account.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block title %}My Account — NVIDIA{% endblock %} +{% block content %} + +
+
+

Welcome, {{ current_user.name }}

+

{{ current_user.email }}{% if current_user.company %} · {{ current_user.company }}{% endif %}{% if current_user.country %} · {{ current_user.country }}{% endif %}

+
+
+ +
+
+ + +

Order History

+ {% if orders %} + {% for o in orders %} +
+
+
Order #{{ o.id }} · {{ o.created.strftime('%b %d, %Y') }}
+
{{ o.status }} ${{ '{:,}'.format(o.total_usd) }}
+
+ + {% for it in o.items %} + + + + + {% endfor %} +
{{ it.name }}Qty {{ it.quantity }} · ${{ '{:,}'.format(it.price_usd or 0) }}
+ View order +
+ {% endfor %} + {% else %} +

You have no orders yet. Start shopping.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/account_edit.html b/sites/nvidia/templates/account_edit.html new file mode 100644 index 00000000..f2ea26e2 --- /dev/null +++ b/sites/nvidia/templates/account_edit.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} +{% block title %}Edit Profile — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Edit Profile

+
+ {{ form.hidden_tag() }} +
+ {{ form.name.label }} + {{ form.name() }} + {% for e in form.name.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.company.label }} + {{ form.company() }} + {% for e in form.company.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.country.label }} + {{ form.country() }} + {% for e in form.country.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.newsletter_opt_in() }} + {{ form.newsletter_opt_in.label }} +
+ +
+

Back to account

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/account_password.html b/sites/nvidia/templates/account_password.html new file mode 100644 index 00000000..a30e9357 --- /dev/null +++ b/sites/nvidia/templates/account_password.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %}Change Password — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Change Password

+
+ {{ form.hidden_tag() }} +
+ {{ form.current.label }} + {{ form.current() }} + {% for e in form.current.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.new.label }} + {{ form.new() }} + {% for e in form.new.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.confirm.label }} + {{ form.confirm() }} + {% for e in form.confirm.errors %}
{{ e }}
{% endfor %} +
+ +
+

Back to account

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/base.html b/sites/nvidia/templates/base.html new file mode 100644 index 00000000..68eecc9d --- /dev/null +++ b/sites/nvidia/templates/base.html @@ -0,0 +1,84 @@ + + + + + + {% block title %}NVIDIA{% endblock %} + + + + +
+
+ + +
+ + {% if current_user.is_authenticated %} + {{ current_user.name.split()[0] }} + Sign Out + {% else %} + Login + {% endif %} + Cart{% if cart_count %}{{ cart_count }}{% endif %} +
+
+
+ +{% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for cat, msg in messages %}
{{ msg }}
{% endfor %} +
+ {% endif %} +{% endwith %} + +
{% block content %}{% endblock %}
+ + + + + diff --git a/sites/nvidia/templates/cart.html b/sites/nvidia/templates/cart.html new file mode 100644 index 00000000..45784fe0 --- /dev/null +++ b/sites/nvidia/templates/cart.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} +{% block title %}Shopping Cart — NVIDIA{% endblock %} +{% block content %} + +
+

Shopping Cart

+
+ +
+
+ {% if items %} +
+ + + + + + + + + + {% for item in items %} + {% set p = item.product %} + + + + + + + + + {% endfor %} + +
ProductPriceQuantityTotal
+ {% if p.image %} + {{ p.name }} + {% endif %} + {{ p.name }}{{ p.price_display }} +
+ + + +
+
${{ '{:,}'.format((p.price_usd or 0) * item.quantity) }} +
+ + +
+
+
+ +
+
Subtotal: ${{ '{:,}'.format(subtotal) }}
+ Proceed to Checkout +
+ {% else %} +

Your cart is empty. Browse products.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/checkout.html b/sites/nvidia/templates/checkout.html new file mode 100644 index 00000000..2b5c275e --- /dev/null +++ b/sites/nvidia/templates/checkout.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% block title %}Checkout — NVIDIA{% endblock %} +{% block content %} + +
+

Checkout

+
+ +
+
+
+
+

Shipping & Payment

+
+ {{ form.hidden_tag() }} +
+ {{ form.full_name.label }} + {{ form.full_name() }} + {% for e in form.full_name.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.address.label }} + {{ form.address() }} + {% for e in form.address.errors %}
{{ e }}
{% endfor %} +
+
+
+ {{ form.city.label }} + {{ form.city() }} + {% for e in form.city.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.zip_code.label }} + {{ form.zip_code() }} + {% for e in form.zip_code.errors %}
{{ e }}
{% endfor %} +
+
+
+ {{ form.card.label }} + {{ form.card(placeholder="Card number") }} + {% for e in form.card.errors %}
{{ e }}
{% endfor %} +
+ +
+
+ +
+

Order Summary

+ + {% for item in items %} + + + + + {% endfor %} + + + +
{{ item.product.name }} × {{ item.quantity }}${{ '{:,}'.format((item.product.price_usd or 0) * item.quantity) }}
Subtotal${{ '{:,}'.format(subtotal) }}
Tax${{ '{:,}'.format(tax) }}
Total${{ '{:,}'.format(total) }}
+
+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/compare.html b/sites/nvidia/templates/compare.html new file mode 100644 index 00000000..d48e01b5 --- /dev/null +++ b/sites/nvidia/templates/compare.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% block title %}Compare GPUs — NVIDIA{% endblock %} +{% block content %} + +
+

Compare GPUs

Select products to compare side by side.

+
+ +
+
+
+
+ {% for ap in all_products %} + + {% endfor %} +
+ +
+ + {% if items %} +
+ + + + + {% for p in items %} + + {% endfor %} + + + + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + +
Specification + + {% if p.image %} + {{ p.name }} + {% endif %} + {{ p.name }} + +
Price{{ p.price_display }}
Series{{ p.series or '—' }}
Architecture{{ p.architecture or '—' }}
CUDA Cores{{ '{:,}'.format(p.cuda_cores) if p.cuda_cores else '—' }}
Tensor Cores{{ '{:,}'.format(p.tensor_cores) if p.tensor_cores else '—' }}
RT Cores{{ '{:,}'.format(p.rt_cores) if p.rt_cores else '—' }}
Memory{% if p.memory_gb %}{{ p.memory_gb }} GB {{ p.memory_type }}{% else %}—{% endif %}
Memory Bandwidth{{ p.memory_bandwidth or '—' }}
Boost Clock{{ p.boost_clock_ghz ~ ' GHz' if p.boost_clock_ghz else '—' }}
TDP{{ p.tdp_watts ~ ' W' if p.tdp_watts else '—' }}
Interface{{ p.interface or '—' }}
Recommended PSU{{ p.recommended_psu_watts ~ ' W' if p.recommended_psu_watts else '—' }}
+
+ {% else %} +

Select two or more products above and click "Compare Selected" to see a side-by-side spec sheet.

+ {% endif %} +
+
+ + + +{% endblock %} diff --git a/sites/nvidia/templates/driver_detail.html b/sites/nvidia/templates/driver_detail.html new file mode 100644 index 00000000..59fec422 --- /dev/null +++ b/sites/nvidia/templates/driver_detail.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% block title %}{{ d.product_series }} Driver {{ d.version }} — NVIDIA{% endblock %} +{% block content %} + +
+ + +
+
{{ d.branch }} Driver
+

{{ d.product_series }} — Version {{ d.version }}

+ + + + + + + + + +
Version{{ d.version }}
Branch{{ d.branch }}
Product Series{{ d.product_series }}
Operating System{{ d.os }}
Release Date{{ d.released.strftime('%b %d, %Y') }}
File Size{{ d.size_mb }} MB
Downloads{{ '{:,}'.format(d.download_count) }}
+ + {% if d.highlights %} +

Release Highlights

+

{{ d.highlights }}

+ {% endif %} + +
+ + +
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/drivers.html b/sites/nvidia/templates/drivers.html new file mode 100644 index 00000000..9b82e3fd --- /dev/null +++ b/sites/nvidia/templates/drivers.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% block title %}Download Drivers — NVIDIA{% endblock %} +{% block content %} + +
+

NVIDIA Drivers

Find the latest Game Ready and Studio drivers for your GPU.

+
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + {% if searched %} + {% if results %} + + + + + + + + + + {% for d in results %} + + + + + + + + + + {% endfor %} + +
Product SeriesBranchVersionOSReleasedSize
{{ d.product_series }}{{ d.branch }}{{ d.version }}{{ d.os }}{{ d.released.strftime('%b %d, %Y') }}{{ d.size_mb }} MBDetails
+ {% else %} +

No drivers match your selection.

+ {% endif %} + {% else %} +

Pick a product series above to find available drivers.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/index.html b/sites/nvidia/templates/index.html new file mode 100644 index 00000000..7296e2a0 --- /dev/null +++ b/sites/nvidia/templates/index.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} +{% block title %}NVIDIA — Accelerated Computing{% endblock %} +{% block content %} + +{% if flagship %} +
+
+
+
{{ flagship.series }}
+

{{ flagship.name }}

+

{{ flagship.tagline }}

+ +
+
+ {% if flagship.image %} + {{ flagship.name }} + {% endif %} +
+
+
+{% endif %} + +
+
+

Explore Products

+

From gaming GPUs to data-center accelerators.

+
+ {% for name, slug in CATEGORIES %} + {{ name }} + {% endfor %} +
+
+
+ +
+
+

Featured Products

+

Our most powerful hardware, hand-picked.

+
+ {% for p in featured %} + {% include "_product_card.html" %} + {% endfor %} +
+
+
+ +{% if latest_news %} +
+ +
+{% endif %} + +{% endblock %} diff --git a/sites/nvidia/templates/login.html b/sites/nvidia/templates/login.html new file mode 100644 index 00000000..b68393b8 --- /dev/null +++ b/sites/nvidia/templates/login.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% block title %}Login — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Sign In

+
+ {{ form.hidden_tag() }} +
+ {{ form.email.label }} + {{ form.email(type="email", placeholder="you@example.com") }} + {% for e in form.email.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.password.label }} + {{ form.password(placeholder="Password") }} + {% for e in form.password.errors %}
{{ e }}
{% endfor %} +
+ +
+

+ New to NVIDIA? Create an account +

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/news.html b/sites/nvidia/templates/news.html new file mode 100644 index 00000000..35356e1c --- /dev/null +++ b/sites/nvidia/templates/news.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} +{% block title %}Newsroom — NVIDIA{% endblock %} +{% block content %} + +
+

NVIDIA Newsroom

The latest news, announcements, and stories.

+
+ +
+
+
+ All + {% for c in cats %} + {{ c }} + {% endfor %} +
+ + {% if items %} + + {% else %} +

No articles found.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/news_detail.html b/sites/nvidia/templates/news_detail.html new file mode 100644 index 00000000..4085f11f --- /dev/null +++ b/sites/nvidia/templates/news_detail.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block title %}{{ a.title }} — NVIDIA{% endblock %} +{% block content %} + +
+
+
{{ a.category }}
+

{{ a.title }}

+
{{ a.author }} · {{ a.published.strftime('%b %d, %Y') }} · {{ a.read_minutes }} min read
+ {% if a.image %} + {{ a.title }} + {% endif %} + {% for para in a.body.split('\n\n') %} + {% if para.strip() %}

{{ para.strip() }}

{% endif %} + {% endfor %} +
+
+ +{% if more %} +
+ +
+{% endif %} + +{% endblock %} diff --git a/sites/nvidia/templates/order_detail.html b/sites/nvidia/templates/order_detail.html new file mode 100644 index 00000000..0a5fee8d --- /dev/null +++ b/sites/nvidia/templates/order_detail.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block title %}Order #{{ order.id }} — NVIDIA{% endblock %} +{% block content %} + +
+
+
+ Thank you for your order! A confirmation has been emailed to you. +
+ +
+
+

Order #{{ order.id }}

+
{{ order.status }} {{ order.created.strftime('%b %d, %Y') }}
+
+ + + {% for it in order.items %} + + + + + {% endfor %} + + + + +
{{ it.name }} × {{ it.quantity }}${{ '{:,}'.format(it.price_usd or 0) }}
Total${{ '{:,}'.format(order.total_usd) }}
+ + Back to Account +
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/product_detail.html b/sites/nvidia/templates/product_detail.html new file mode 100644 index 00000000..50aa0e8f --- /dev/null +++ b/sites/nvidia/templates/product_detail.html @@ -0,0 +1,130 @@ +{% extends "base.html" %} +{% block title %}{{ p.name }} — NVIDIA{% endblock %} +{% block content %} + +
+ + +
+
+ {% if p.image %} + {{ p.name }} + {% endif %} +
+
+ {% if p.series %}
{{ p.series }}
{% endif %} +

{{ p.name }}

+

{{ p.tagline }}

+
{{ p.price_display }}
+

+ {% if p.in_stock %}In Stock + {% else %}Out of Stock{% endif %} + {% if p.avg_rating %}★ {{ p.avg_rating }} ({{ p.review_count }}){% endif %} +

+ +
+ {% if p.price_usd is not none %} +
+ + +
+ {% else %} + Contact Sales + {% endif %} + +
+ + +
+ + Compare +
+ + {% if p.description %}

{{ p.description }}

{% endif %} +
+
+ + {# Tech specs — only non-empty fields #} + {% set has_specs = p.architecture or p.cuda_cores or p.tensor_cores or p.rt_cores + or p.memory_gb or p.memory_bandwidth or p.boost_clock_ghz or p.tdp_watts + or p.interface or p.recommended_psu_watts %} + {% if has_specs %} +

Tech Specs

+ + {% if p.architecture %}{% endif %} + {% if p.cuda_cores %}{% endif %} + {% if p.tensor_cores %}{% endif %} + {% if p.rt_cores %}{% endif %} + {% if p.memory_gb %}{% endif %} + {% if p.memory_bandwidth %}{% endif %} + {% if p.boost_clock_ghz %}{% endif %} + {% if p.tdp_watts %}{% endif %} + {% if p.interface %}{% endif %} + {% if p.recommended_psu_watts %}{% endif %} +
Architecture{{ p.architecture }}
CUDA Cores{{ '{:,}'.format(p.cuda_cores) }}
Tensor Cores{{ '{:,}'.format(p.tensor_cores) }}
RT Cores{{ '{:,}'.format(p.rt_cores) }}
Memory{{ p.memory_gb }} GB {{ p.memory_type }}
Memory Bandwidth{{ p.memory_bandwidth }}
Boost Clock{{ p.boost_clock_ghz }} GHz
TDP{{ p.tdp_watts }} W
Interface{{ p.interface }}
Recommended PSU{{ p.recommended_psu_watts }} W
+ {% endif %} + + {# Reviews #} +
+

Reviews

+ {% if p.avg_rating %} +

★ {{ p.avg_rating }} · {{ p.review_count }} review{{ '' if p.review_count == 1 else 's' }}

+ {% else %} +

No reviews yet.

+ {% endif %} + + {% for r in reviews %} +
+
{% for _ in range(r.rating) %}★{% endfor %}{% for _ in range(5 - r.rating) %}☆{% endfor %}
+ {{ r.title }} +

{{ r.body }}

+

— {{ r.user.name }} · {{ r.created.strftime('%b %d, %Y') }}

+
+ {% endfor %} + + {% if current_user.is_authenticated %} +
+

Write a Review

+
+ {{ review_form.hidden_tag() }} +
+ {{ review_form.rating.label }} + {{ review_form.rating() }} + {% for e in review_form.rating.errors %}
{{ e }}
{% endfor %} +
+
+ {{ review_form.title.label }} + {{ review_form.title(class_="") }} + {% for e in review_form.title.errors %}
{{ e }}
{% endfor %} +
+
+ {{ review_form.body.label }} + {{ review_form.body(rows=4) }} + {% for e in review_form.body.errors %}
{{ e }}
{% endfor %} +
+ +
+
+ {% else %} +

Log in to write a review.

+ {% endif %} +
+ + {% if related %} +
+

Related Products

+
+ {% for p in related %} + {% include "_product_card.html" %} + {% endfor %} +
+
+ {% endif %} +
+ +{% endblock %} diff --git a/sites/nvidia/templates/products.html b/sites/nvidia/templates/products.html new file mode 100644 index 00000000..960b4975 --- /dev/null +++ b/sites/nvidia/templates/products.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} +{% block title %}{{ cat_name or "Products" }} — NVIDIA{% endblock %} +{% block content %} + +
+
+

{{ cat_name or "Products" }}

+ {% if q %}

Results for "{{ q }}"

{% endif %} +
+
+ +
+
+
+ All + {% for name, slug in CATEGORIES %} + {{ name }} + {% endfor %} +
+ +
+
{{ items|length }} product{{ '' if items|length == 1 else 's' }}
+
+ {% if cat %}{% endif %} + {% if series %}{% endif %} + {% if q %}{% endif %} + + + +
+
+ + {% if items %} +
+ {% for p in items %} + {% include "_product_card.html" %} + {% endfor %} +
+ {% else %} +

No products found. Try a different category or search term.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/register.html b/sites/nvidia/templates/register.html new file mode 100644 index 00000000..d49891cc --- /dev/null +++ b/sites/nvidia/templates/register.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} +{% block title %}Create Account — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Create Your NVIDIA Account

+
+ {{ form.hidden_tag() }} +
+ {{ form.name.label }} + {{ form.name(placeholder="Your full name") }} + {% for e in form.name.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.email.label }} + {{ form.email(type="email", placeholder="you@example.com") }} + {% for e in form.email.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.password.label }} + {{ form.password() }} + {% for e in form.password.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.confirm.label }} + {{ form.confirm() }} + {% for e in form.confirm.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.newsletter_opt_in() }} + {{ form.newsletter_opt_in.label }} +
+ +
+

+ Already have an account? Sign in +

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/search.html b/sites/nvidia/templates/search.html new file mode 100644 index 00000000..83da840a --- /dev/null +++ b/sites/nvidia/templates/search.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% block title %}Search — NVIDIA{% endblock %} +{% block content %} + +
+
+

Search

+ {% if q %}

Results for "{{ q }}"

{% endif %} +
+
+ +
+
+ {% if not q %} +

Type a query in the search box above to find products and news.

+ {% elif not products and not articles %} +

No results for "{{ q }}". Try different keywords.

+ {% else %} + {% if products %} +

Products

+
+ {% for p in products %} + {% include "_product_card.html" %} + {% endfor %} +
+ {% endif %} + + {% if articles %} +

News

+ + {% endif %} + {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/wishlist.html b/sites/nvidia/templates/wishlist.html new file mode 100644 index 00000000..19dc50b6 --- /dev/null +++ b/sites/nvidia/templates/wishlist.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% block title %}My Wishlist — NVIDIA{% endblock %} +{% block content %} + +
+

My Wishlist

+
+ +
+
+ {% if items %} +
+ {% for item in items %} + {% set p = item.product %} +
+ + {% if p.image %} + {{ p.name }} + {% endif %} + +
+ {% if p.series %}
{{ p.series }}
{% endif %} + {{ p.name }} +
{{ p.price_display }}
+
+ + +
+
+
+ {% endfor %} +
+ {% else %} +

Your wishlist is empty. Browse products.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 72defad8..4863bca1 100644 --- a/websyn_start.sh +++ b/websyn_start.sh @@ -5,7 +5,7 @@ set -e SITES=(allrecipes amazon apple arxiv bbc_news booking github google_flights google_map google_search huggingface wolfram_alpha - cambridge_dictionary coursera espn) + cambridge_dictionary coursera espn nvidia) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR" From 73add283045070b0c9400bea1ee6e2357fde4da9 Mon Sep 17 00:00:00 2001 From: DEM1TASSE Date: Fri, 3 Jul 2026 07:22:02 +0000 Subject: [PATCH 2/2] Add NVIDIA task verifiers and judge rubrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer deliverable for the NVIDIA mirror (site by @KaKituken, PR #55): one deterministic verifier per task under sites/nvidia/verify/, plus verifier_path + judge_rubric recorded in every tasks.jsonl row. No answer key in tasks.jsonl — ground truth lives only inside the verifiers. Design is deterministic-first: (1) trajectory navigation (anti knowledge- shortcut), (2) SQLite DB after-state for the 8 stateful tasks (cart / order / wishlist / review / profile / newsletter), (3) answer vs frozen ground truth, with the LLM only as an anchored consistency check. Validated against the official react agent (agent_demo/agent.py): a no-op run fails all 20 verifiers, a correct-answer-without-navigation run fails on the nav gate, and genuine completions pass. On the full 20-task run the verifier and LLM judge agreed 18/20; both divergences were the LLM judge false- negativing a genuine success the verifier caught via DB after-state. Co-Authored-By: Claude Opus 4.8 --- sites/nvidia/tasks.jsonl | 40 ++-- sites/nvidia/verify/verify_0.py | 23 +++ sites/nvidia/verify/verify_1.py | 23 +++ sites/nvidia/verify/verify_10.py | 23 +++ sites/nvidia/verify/verify_11.py | 26 +++ sites/nvidia/verify/verify_12.py | 25 +++ sites/nvidia/verify/verify_13.py | 28 +++ sites/nvidia/verify/verify_14.py | 27 +++ sites/nvidia/verify/verify_15.py | 31 +++ sites/nvidia/verify/verify_16.py | 31 +++ sites/nvidia/verify/verify_17.py | 32 +++ sites/nvidia/verify/verify_18.py | 27 +++ sites/nvidia/verify/verify_19.py | 26 +++ sites/nvidia/verify/verify_2.py | 22 +++ sites/nvidia/verify/verify_3.py | 23 +++ sites/nvidia/verify/verify_4.py | 22 +++ sites/nvidia/verify/verify_5.py | 23 +++ sites/nvidia/verify/verify_6.py | 25 +++ sites/nvidia/verify/verify_7.py | 22 +++ sites/nvidia/verify/verify_8.py | 23 +++ sites/nvidia/verify/verify_9.py | 23 +++ sites/nvidia/verify/verify_lib.py | 314 ++++++++++++++++++++++++++++++ 22 files changed, 839 insertions(+), 20 deletions(-) create mode 100644 sites/nvidia/verify/verify_0.py create mode 100644 sites/nvidia/verify/verify_1.py create mode 100644 sites/nvidia/verify/verify_10.py create mode 100644 sites/nvidia/verify/verify_11.py create mode 100644 sites/nvidia/verify/verify_12.py create mode 100644 sites/nvidia/verify/verify_13.py create mode 100644 sites/nvidia/verify/verify_14.py create mode 100644 sites/nvidia/verify/verify_15.py create mode 100644 sites/nvidia/verify/verify_16.py create mode 100644 sites/nvidia/verify/verify_17.py create mode 100644 sites/nvidia/verify/verify_18.py create mode 100644 sites/nvidia/verify/verify_19.py create mode 100644 sites/nvidia/verify/verify_2.py create mode 100644 sites/nvidia/verify/verify_3.py create mode 100644 sites/nvidia/verify/verify_4.py create mode 100644 sites/nvidia/verify/verify_5.py create mode 100644 sites/nvidia/verify/verify_6.py create mode 100644 sites/nvidia/verify/verify_7.py create mode 100644 sites/nvidia/verify/verify_8.py create mode 100644 sites/nvidia/verify/verify_9.py create mode 100644 sites/nvidia/verify/verify_lib.py diff --git a/sites/nvidia/tasks.jsonl b/sites/nvidia/tasks.jsonl index 3b66184c..f86d2ad9 100644 --- a/sites/nvidia/tasks.jsonl +++ b/sites/nvidia/tasks.jsonl @@ -1,20 +1,20 @@ -{"web_name": "NVIDIA", "id": "NVIDIA--0", "ques": "Find the GeForce RTX 5090 graphics card and report how much memory it has and what type.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--1", "ques": "How many CUDA cores does the GeForce RTX 5080 have?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--2", "ques": "What is the graphics card power (TDP, in watts) of the GeForce RTX 4090?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--3", "ques": "Among the GeForce RTX 50 Series graphics cards, which one is the least expensive, and what is its price?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--4", "ques": "Browse the Data Center products and find which GPU has 141 GB of memory.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--5", "ques": "In the Embedded / robotics products, what is the price of the Jetson Orin Nano Super Developer Kit?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--6", "ques": "Using the GPU comparison tool, compare the GeForce RTX 5090 and the GeForce RTX 4090. Which has more CUDA cores, and how many more?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--7", "ques": "Compare the GeForce RTX 5080 and the GeForce RTX 4080 SUPER. Which one has the higher memory bandwidth?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--8", "ques": "Of all the Studio / Professional GPUs, which one has the most memory, and how much?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--9", "ques": "Go to the Drivers section and find the latest Game Ready driver version for the GeForce RTX 50 Series on Windows 11.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--10", "ques": "Find the Studio branch driver for the GeForce RTX 40 Series on Windows 11 and report its version number.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--11", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the GeForce RTX 5080 to your cart and proceed through checkout to place the order.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--12", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the least expensive GeForce RTX 40 Series graphics card to your cart.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--13", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then save the GeForce RTX 5070 Ti to your wishlist.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--14", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then change the country on your profile to Germany.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--15", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then write a 5-star review titled 'Incredible' for the Jetson Orin Nano Super Developer Kit.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--16", "ques": "Sign in with email alice.j@test.com and password TestPass123!. Your wishlist already has more than one item — remove the workstation GPU (the RTX PRO 6000 Blackwell) from it.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--17", "ques": "Among all GeForce Gaming graphics cards with at least 16 GB of memory, find the least expensive one and add it to your cart (sign in as alice.j@test.com / TestPass123!).", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--18", "ques": "In the NVIDIA Newsroom, find the article about Blackwell sweeping MLPerf Training 6.0 and report its publication date.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} -{"web_name": "NVIDIA", "id": "NVIDIA--19", "ques": "Subscribe the email address gamer42@example.com to the NVIDIA newsletter.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--0", "ques": "Find the GeForce RTX 5090 graphics card and report how much memory it has and what type.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_0.py", "judge_rubric": "MUST open the RTX 5090 product page (/products/geforce-rtx-5090). Answer MUST state 32 GB and GDDR7. An answer with no product-page visit or missing either fact = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--1", "ques": "How many CUDA cores does the GeForce RTX 5080 have?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_1.py", "judge_rubric": "MUST open the RTX 5080 product page. Answer MUST be 10,752 CUDA cores. Empty/other number = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--2", "ques": "What is the graphics card power (TDP, in watts) of the GeForce RTX 4090?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_2.py", "judge_rubric": "MUST open the RTX 4090 product page. Answer MUST be 450 W (TDP). Empty/other = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--3", "ques": "Among the GeForce RTX 50 Series graphics cards, which one is the least expensive, and what is its price?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_3.py", "judge_rubric": "MUST browse the catalog / RTX 50 Series listing. Answer MUST name the GeForce RTX 5060 AND its $299 price. Naming a pricier card or omitting the price = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--4", "ques": "Browse the Data Center products and find which GPU has 141 GB of memory.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_4.py", "judge_rubric": "MUST browse the Data Center products. Answer MUST identify the NVIDIA H200 Tensor Core GPU (the 141 GB card). The GH200 (144 GB) is a near-miss = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--5", "ques": "In the Embedded / robotics products, what is the price of the Jetson Orin Nano Super Developer Kit?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_5.py", "judge_rubric": "MUST view the Jetson Orin Nano Super Developer Kit (Embedded). Answer MUST be $249."} +{"web_name": "NVIDIA", "id": "NVIDIA--6", "ques": "Using the GPU comparison tool, compare the GeForce RTX 5090 and the GeForce RTX 4090. Which has more CUDA cores, and how many more?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_6.py", "judge_rubric": "MUST use the /compare tool on the RTX 5090 vs RTX 4090. Answer MUST say the RTX 5090 has more CUDA cores AND give the 5,376 difference (21,760 vs 16,384). Missing the delta = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--7", "ques": "Compare the GeForce RTX 5080 and the GeForce RTX 4080 SUPER. Which one has the higher memory bandwidth?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_7.py", "judge_rubric": "MUST use the /compare tool on the RTX 5080 vs RTX 4080 SUPER. Answer MUST say the RTX 5080 has the higher memory bandwidth (960 GB/s vs 736 GB/s)."} +{"web_name": "NVIDIA", "id": "NVIDIA--8", "ques": "Of all the Studio / Professional GPUs, which one has the most memory, and how much?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_8.py", "judge_rubric": "MUST browse the Studio / Professional GPUs. Answer MUST name the RTX PRO 6000 Blackwell AND its 96 GB (the most memory)."} +{"web_name": "NVIDIA", "id": "NVIDIA--9", "ques": "Go to the Drivers section and find the latest Game Ready driver version for the GeForce RTX 50 Series on Windows 11.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_9.py", "judge_rubric": "MUST use the Drivers finder (/drivers). Answer MUST be Game Ready version 566.36 (RTX 50 Series, Windows 11). Recalling a real NVIDIA version instead of the on-page one = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--10", "ques": "Find the Studio branch driver for the GeForce RTX 40 Series on Windows 11 and report its version number.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_10.py", "judge_rubric": "MUST use the Drivers finder. Answer MUST be Studio version 566.14 (RTX 40 Series, Windows 11)."} +{"web_name": "NVIDIA", "id": "NVIDIA--11", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the GeForce RTX 5080 to your cart and proceed through checkout to place the order.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_11.py", "judge_rubric": "MUST sign in as alice, add the RTX 5080 to the cart, and complete checkout. A NEW order containing the RTX 5080 must exist in the account. Claiming success without a placed order = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--12", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the least expensive GeForce RTX 40 Series graphics card to your cart.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_12.py", "judge_rubric": "MUST sign in as alice and add the least-expensive GeForce RTX 40 Series card — the RTX 4060 ($299) — to the cart. The cart must contain the RTX 4060."} +{"web_name": "NVIDIA", "id": "NVIDIA--13", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then save the GeForce RTX 5070 Ti to your wishlist.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_13.py", "judge_rubric": "MUST sign in as alice and add the GeForce RTX 5070 Ti to the wishlist. The wishlist must contain the 5070 Ti afterward."} +{"web_name": "NVIDIA", "id": "NVIDIA--14", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then change the country on your profile to Germany.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_14.py", "judge_rubric": "MUST sign in as alice and change the profile country to Germany. The saved profile country must read Germany."} +{"web_name": "NVIDIA", "id": "NVIDIA--15", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then write a 5-star review titled 'Incredible' for the Jetson Orin Nano Super Developer Kit.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_15.py", "judge_rubric": "MUST sign in as alice and post a 5-star review titled 'Incredible' on the Jetson Orin Nano Super Developer Kit page. A review row with rating 5 and that title must exist."} +{"web_name": "NVIDIA", "id": "NVIDIA--16", "ques": "Sign in with email alice.j@test.com and password TestPass123!. Your wishlist already has more than one item — remove the workstation GPU (the RTX PRO 6000 Blackwell) from it.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_16.py", "judge_rubric": "MUST sign in as alice and remove the workstation GPU (RTX PRO 6000 Blackwell) from the wishlist, leaving the other item (RTX 4080 SUPER) in place."} +{"web_name": "NVIDIA", "id": "NVIDIA--17", "ques": "Among all GeForce Gaming graphics cards with at least 16 GB of memory, find the least expensive one and add it to your cart (sign in as alice.j@test.com / TestPass123!).", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_17.py", "judge_rubric": "MUST sign in as alice and add the cheapest GeForce Gaming card with at least 16 GB — the RTX 5060 Ti ($429, 16 GB) — to the cart. The $299 RTX 5060 / RTX 4060 (8 GB) and $549 RTX 5070 (12 GB) are wrong (sub-16GB) = FAIL."} +{"web_name": "NVIDIA", "id": "NVIDIA--18", "ques": "In the NVIDIA Newsroom, find the article about Blackwell sweeping MLPerf Training 6.0 and report its publication date.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_18.py", "judge_rubric": "MUST open the Newsroom article on Blackwell sweeping MLPerf Training 6.0. Answer MUST give its publication date, shown on the page as 'Jun 16, 2026' (i.e. June 16, 2026)."} +{"web_name": "NVIDIA", "id": "NVIDIA--19", "ques": "Subscribe the email address gamer42@example.com to the NVIDIA newsletter.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/", "verifier_path": "sites/nvidia/verify/verify_19.py", "judge_rubric": "MUST submit the newsletter form for gamer42@example.com. The newsletter subscribers must include that address afterward. A bare claim without the subscription = FAIL."} diff --git a/sites/nvidia/verify/verify_0.py b/sites/nvidia/verify/verify_0.py new file mode 100644 index 00000000..43039565 --- /dev/null +++ b/sites/nvidia/verify/verify_0.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""NVIDIA--0: find the GeForce RTX 5090 and report memory size + type. +Ground truth: 32 GB, GDDR7. Deterministic: nav to the 5090 detail + answer tokens. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_any, + number_mentioned, llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--0', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_5090_detail", navigated_to(t, "/products/geforce-rtx-5090"), + "expected the RTX 5090 product page") + j.check("answer_memory_size", number_mentioned(fa, 32), f"expected 32 (GB); final={fa!r}") + j.check("answer_memory_type", contains_any(fa, ["GDDR7"]), "expected memory type GDDR7") + ok, ev = llm_text_match(fa, "32 GB of GDDR7 memory", + "How much memory does the RTX 5090 have and what type?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_1.py b/sites/nvidia/verify/verify_1.py new file mode 100644 index 00000000..20ba5178 --- /dev/null +++ b/sites/nvidia/verify/verify_1.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""NVIDIA--1: how many CUDA cores does the GeForce RTX 5080 have? +Ground truth: 10,752. Deterministic: nav to the 5080 detail + numeric answer. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, number_mentioned, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--1', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_5080_detail", navigated_to(t, "/products/geforce-rtx-5080"), + "expected the RTX 5080 product page") + j.check("answer_cuda_cores", number_mentioned(fa, 10752), + f"expected 10,752 CUDA cores; final={fa!r}") + ok, ev = llm_text_match(fa, "10,752 CUDA cores", + "How many CUDA cores does the RTX 5080 have?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_10.py b/sites/nvidia/verify/verify_10.py new file mode 100644 index 00000000..08ade98b --- /dev/null +++ b/sites/nvidia/verify/verify_10.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""NVIDIA--10: the Studio branch driver for the GeForce RTX 40 Series on Windows 11 — its +version number. Ground truth (this mirror's frozen build): 566.14. +Deterministic: nav to /drivers + exact version token. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_any, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--10', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_drivers", navigated_to(t, "/drivers"), "expected the Drivers finder") + j.check("answer_version", contains_any(fa, ["566.14"]), + f"expected Studio version 566.14; final={fa!r}") + ok, ev = llm_text_match(fa, "Studio driver version 566.14", + "Studio driver for GeForce RTX 40 Series on Windows 11?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_11.py b/sites/nvidia/verify/verify_11.py new file mode 100644 index 00000000..8d5c84de --- /dev/null +++ b/sites/nvidia/verify/verify_11.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""NVIDIA--11: sign in as alice, add the RTX 5080 to cart, and check out to place the order. +Deterministic-first: nav to /checkout | DB after-state: alice has a NEW order and one of her +orders contains the RTX 5080. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, resolve_db, orders_for, Judge, parse_args) + +EMAIL = "alice.j@test.com" + +def main(): + a = parse_args(); j = Judge('NVIDIA--11', a.no_llm) + t = load_run(a.run_dir) + after = resolve_db(a.after_db, a.container, "instance") + init = resolve_db(a.initial_db, a.container, "instance_seed") + ao, io = orders_for(after, EMAIL), orders_for(init, EMAIL) + j.check("nav_checkout", navigated_to(t, "/checkout"), "expected the checkout flow") + j.check("db_order_created", ao is not None and io is not None and len(ao) > len(io), + f"orders after={None if ao is None else len(ao)} vs initial={None if io is None else len(io)}") + has = bool(ao) and any("geforce-rtx-5080" in [s for s, _ in o["items"]] for o in ao) + j.check("db_order_has_5080", has, "a placed order must contain the RTX 5080") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_12.py b/sites/nvidia/verify/verify_12.py new file mode 100644 index 00000000..69b62d51 --- /dev/null +++ b/sites/nvidia/verify/verify_12.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""NVIDIA--12: sign in as alice, add the least-expensive GeForce RTX 40 Series card to cart. +Ground truth: GeForce RTX 4060 ($299, the cheapest RTX 40 Series). +Deterministic-first: nav to catalog/cart | DB after-state: alice's cart contains the 4060. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_any, resolve_db, cart_for, Judge, parse_args) + +EMAIL = "alice.j@test.com" + +def main(): + a = parse_args(); j = Judge('NVIDIA--12', a.no_llm) + t = load_run(a.run_dir) + after = resolve_db(a.after_db, a.container, "instance") + cart = cart_for(after, EMAIL) + j.check("nav_catalog_or_cart", navigated_any(t, ["/products", "/cart"]), + "expected browsing the catalog / cart") + slugs = [s for s, _, _ in (cart or [])] + j.check("db_cart_has_4060", "geforce-rtx-4060" in slugs, + f"alice's cart must contain the RTX 4060 (cheapest RTX 40); cart={slugs}") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_13.py b/sites/nvidia/verify/verify_13.py new file mode 100644 index 00000000..19492404 --- /dev/null +++ b/sites/nvidia/verify/verify_13.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""NVIDIA--13: sign in as alice, save the GeForce RTX 5070 Ti to the wishlist. +Deterministic-first: nav to the 5070 Ti page / wishlist | DB after-state: the 5070 Ti is in +alice's wishlist after AND was NOT there in the initial seed (proves the agent added it). +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_any, resolve_db, wishlist_for, Judge, parse_args) + +EMAIL = "alice.j@test.com" +SLUG = "geforce-rtx-5070-ti" + +def main(): + a = parse_args(); j = Judge('NVIDIA--13', a.no_llm) + t = load_run(a.run_dir) + after = resolve_db(a.after_db, a.container, "instance") + init = resolve_db(a.initial_db, a.container, "instance_seed") + wa = [s for s, _, _ in (wishlist_for(after, EMAIL) or [])] + wi = [s for s, _, _ in (wishlist_for(init, EMAIL) or [])] + j.check("nav_product_or_wishlist", + navigated_any(t, [f"/products/{SLUG}", "/wishlist", "/account/wishlist"]), + "expected the 5070 Ti page or the wishlist") + j.check("db_wishlist_added", SLUG in wa and SLUG not in wi, + f"5070 Ti in wishlist after={SLUG in wa}, initial={SLUG in wi}") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_14.py b/sites/nvidia/verify/verify_14.py new file mode 100644 index 00000000..94fd7afb --- /dev/null +++ b/sites/nvidia/verify/verify_14.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""NVIDIA--14: sign in as alice, change the country on the profile to Germany. +Deterministic-first: nav to /account/edit | DB after-state: alice's country is 'Germany' +after AND was something else initially (proves the agent changed it). +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_any, resolve_db, user_field, Judge, parse_args) + +EMAIL = "alice.j@test.com" + +def main(): + a = parse_args(); j = Judge('NVIDIA--14', a.no_llm) + t = load_run(a.run_dir) + after = resolve_db(a.after_db, a.container, "instance") + init = resolve_db(a.initial_db, a.container, "instance_seed") + ca = user_field(after, EMAIL, "country") + ci = user_field(init, EMAIL, "country") + j.check("nav_account_edit", navigated_any(t, ["/account/edit", "/account"]), + "expected the profile edit page") + j.check("db_country_germany", + (ca or "").strip().lower() == "germany" and (ci or "").strip().lower() != "germany", + f"country after={ca!r} initial={ci!r} (must become Germany)") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_15.py b/sites/nvidia/verify/verify_15.py new file mode 100644 index 00000000..272488cd --- /dev/null +++ b/sites/nvidia/verify/verify_15.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""NVIDIA--15: sign in as alice, write a 5-star review titled 'Incredible' for the Jetson +Orin Nano Super Developer Kit. +Deterministic-first: nav to the Jetson product page | DB after-state: a NEW review by alice +on that product exists with rating 5 and title containing 'Incredible'. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, resolve_db, reviews_for, Judge, parse_args) + +EMAIL = "alice.j@test.com" +SLUG = "jetson-orin-nano-super" + +def main(): + a = parse_args(); j = Judge('NVIDIA--15', a.no_llm) + t = load_run(a.run_dir) + after = resolve_db(a.after_db, a.container, "instance") + init = resolve_db(a.initial_db, a.container, "instance_seed") + ra = reviews_for(after, EMAIL, SLUG) or [] + ri = reviews_for(init, EMAIL, SLUG) or [] + j.check("nav_product", navigated_to(t, f"/products/{SLUG}"), + "expected the Jetson Orin Nano Super page") + j.check("db_review_created", len(ra) > len(ri), + f"reviews by alice on the Jetson after={len(ra)} vs initial={len(ri)}") + match = any(r[1] == 5 and "incredible" in (r[2] or "").lower() for r in ra) + j.check("db_review_5star_titled", match, + "a review with rating=5 and title 'Incredible' must exist") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_16.py b/sites/nvidia/verify/verify_16.py new file mode 100644 index 00000000..9fdf584b --- /dev/null +++ b/sites/nvidia/verify/verify_16.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""NVIDIA--16: sign in as alice (whose wishlist already has >1 item) and remove the +workstation GPU (the RTX PRO 6000 Blackwell) from the wishlist. +Deterministic-first: nav to the wishlist | DB after-state: the RTX PRO 6000 was in alice's +initial wishlist and is GONE after, while her other item (RTX 4080 SUPER) remains. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_any, resolve_db, wishlist_for, Judge, parse_args) + +EMAIL = "alice.j@test.com" +REMOVE = "rtx-pro-6000-blackwell" +KEEP = "geforce-rtx-4080-super" + +def main(): + a = parse_args(); j = Judge('NVIDIA--16', a.no_llm) + t = load_run(a.run_dir) + after = resolve_db(a.after_db, a.container, "instance") + init = resolve_db(a.initial_db, a.container, "instance_seed") + wa = [s for s, _, _ in (wishlist_for(after, EMAIL) or [])] + wi = [s for s, _, _ in (wishlist_for(init, EMAIL) or [])] + j.check("nav_wishlist", navigated_any(t, ["/account/wishlist", "/wishlist", "/account"]), + "expected the wishlist") + j.check("db_pro6000_removed", REMOVE in wi and REMOVE not in wa, + f"RTX PRO 6000 initial={REMOVE in wi} after={REMOVE in wa} (must be removed)") + j.check("db_other_item_kept", KEEP in wa, + f"the other wishlist item (RTX 4080 SUPER) must remain; after={wa}") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_17.py b/sites/nvidia/verify/verify_17.py new file mode 100644 index 00000000..0f9ebba0 --- /dev/null +++ b/sites/nvidia/verify/verify_17.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""NVIDIA--17: sign in as alice; among all GeForce Gaming cards with >=16 GB of memory, add +the least-expensive one to the cart. +Ground truth: GeForce RTX 5060 Ti ($429, 16 GB) — the cheapest GeForce Gaming card meeting +the >=16 GB constraint (the $299 RTX 5060 / RTX 4060 are only 8 GB; the $549 RTX 5070 is +12 GB — near-miss distractors that must be excluded). +Deterministic-first: nav to catalog/cart | DB after-state: alice's cart contains the 5060 Ti. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_any, resolve_db, cart_for, Judge, parse_args) + +EMAIL = "alice.j@test.com" +TARGET = "geforce-rtx-5060-ti" + +def main(): + a = parse_args(); j = Judge('NVIDIA--17', a.no_llm) + t = load_run(a.run_dir) + after = resolve_db(a.after_db, a.container, "instance") + slugs = [s for s, _, _ in (cart_for(after, EMAIL) or [])] + j.check("nav_catalog_or_cart", navigated_any(t, ["/products", "/cart"]), + "expected browsing the catalog / cart") + j.check("db_cart_has_5060ti", TARGET in slugs, + f"cart must contain the RTX 5060 Ti (cheapest GeForce >=16GB); cart={slugs}") + # guard against the two 8 GB near-misses being added instead + wrong = {"geforce-rtx-5060", "geforce-rtx-4060", "geforce-rtx-5070"} & set(slugs) + j.check("db_no_wrong_card", not wrong or TARGET in slugs, + f"a sub-16GB near-miss was added: {wrong}") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_18.py b/sites/nvidia/verify/verify_18.py new file mode 100644 index 00000000..4443d599 --- /dev/null +++ b/sites/nvidia/verify/verify_18.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""NVIDIA--18: in the Newsroom, the article about Blackwell sweeping MLPerf Training 6.0 — +its publication date. Ground truth: 2026-06-16 (June 16, 2026). +Deterministic: nav to the article + date answer. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_any, contains_any, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--18', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_article", navigated_any(t, ["/news/blackwell-mlperf-training-6-0", "/news"]), + "expected the MLPerf Training 6.0 article") + # the article page renders the date as "Jun 16, 2026" (abbreviated month); accept that + # exact on-page form plus the common normalizations. + j.check("answer_date", contains_any(fa, ["2026-06-16", "jun 16, 2026", "jun 16", + "june 16, 2026", "june 16", "16 june"]), + f"expected publication date 2026-06-16 (shown as 'Jun 16, 2026'); final={fa!r}") + ok, ev = llm_text_match(fa, "published June 16, 2026 (2026-06-16)", + "What is the publication date of the Blackwell MLPerf Training 6.0 article?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_19.py b/sites/nvidia/verify/verify_19.py new file mode 100644 index 00000000..966c5640 --- /dev/null +++ b/sites/nvidia/verify/verify_19.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""NVIDIA--19: subscribe the email gamer42@example.com to the NVIDIA newsletter. +Deterministic-first: DB after-state is decisive — the newsletter table contains +gamer42@example.com after AND did not initially (proves the agent submitted the form). +The newsletter form POSTs to /newsletter (footer), which may not surface as a nav URL, so +the DB is the anchor. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, resolve_db, newsletter_has, Judge, parse_args) + +TARGET = "gamer42@example.com" + +def main(): + a = parse_args(); j = Judge('NVIDIA--19', a.no_llm) + load_run(a.run_dir) # ensure trajectory exists / well-formed + after = resolve_db(a.after_db, a.container, "instance") + init = resolve_db(a.initial_db, a.container, "instance_seed") + ha = newsletter_has(after, TARGET) + hi = newsletter_has(init, TARGET) + j.check("db_subscribed", ha and not hi, + f"newsletter has {TARGET} after={ha} initial={hi} (agent must subscribe it)") + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_2.py b/sites/nvidia/verify/verify_2.py new file mode 100644 index 00000000..ba97c10e --- /dev/null +++ b/sites/nvidia/verify/verify_2.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""NVIDIA--2: what is the TDP (watts) of the GeForce RTX 4090? +Ground truth: 450 W. Deterministic: nav to the 4090 detail + numeric answer. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, number_mentioned, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--2', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_4090_detail", navigated_to(t, "/products/geforce-rtx-4090"), + "expected the RTX 4090 product page") + j.check("answer_tdp", number_mentioned(fa, 450), f"expected 450 (W); final={fa!r}") + ok, ev = llm_text_match(fa, "450 W TDP", + "What is the TDP in watts of the RTX 4090?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_3.py b/sites/nvidia/verify/verify_3.py new file mode 100644 index 00000000..c06ec348 --- /dev/null +++ b/sites/nvidia/verify/verify_3.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""NVIDIA--3: among the GeForce RTX 50 Series cards, the least expensive one + its price. +Ground truth: GeForce RTX 5060 @ $299. Deterministic: nav to the catalog + answer tokens. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_any, + price_mentioned, llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--3', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_catalog", navigated_to(t, "/products"), "expected the products catalog") + j.check("answer_names_5060", contains_any(fa, ["5060"]), + f"expected the RTX 5060; final={fa!r}") + j.check("answer_price", price_mentioned(fa, 299), "expected price $299") + ok, ev = llm_text_match(fa, "GeForce RTX 5060 at $299 (the cheapest RTX 50 Series card)", + "Which RTX 50 Series card is least expensive and what is its price?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_4.py b/sites/nvidia/verify/verify_4.py new file mode 100644 index 00000000..4b2aeaf4 --- /dev/null +++ b/sites/nvidia/verify/verify_4.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""NVIDIA--4: browse Data Center products; find which GPU has 141 GB of memory. +Ground truth: NVIDIA H200 Tensor Core GPU. Deterministic: nav to catalog + answer names H200. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_any, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--4', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_catalog", navigated_to(t, "/products"), "expected the Data Center catalog") + j.check("answer_names_h200", contains_any(fa, ["H200"]), + f"expected the H200; final={fa!r}") + ok, ev = llm_text_match(fa, "NVIDIA H200 Tensor Core GPU (141 GB)", + "Which Data Center GPU has 141 GB of memory?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_5.py b/sites/nvidia/verify/verify_5.py new file mode 100644 index 00000000..8ca0916c --- /dev/null +++ b/sites/nvidia/verify/verify_5.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""NVIDIA--5: in Embedded / robotics products, the price of the Jetson Orin Nano Super +Developer Kit. Ground truth: $249. Deterministic: nav to catalog/detail + price answer. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_any, price_mentioned, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--5', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_catalog_or_detail", + navigated_any(t, ["/products", "jetson-orin-nano-super"]), + "expected the Embedded catalog or the Jetson Orin Nano Super page") + j.check("answer_price", price_mentioned(fa, 249), f"expected $249; final={fa!r}") + ok, ev = llm_text_match(fa, "$249", + "What is the price of the Jetson Orin Nano Super Developer Kit?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_6.py b/sites/nvidia/verify/verify_6.py new file mode 100644 index 00000000..813fdd65 --- /dev/null +++ b/sites/nvidia/verify/verify_6.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""NVIDIA--6: use the comparison tool to compare RTX 5090 vs RTX 4090 — which has more +CUDA cores and by how many? Ground truth: 5090 (21,760) has 5,376 more than 4090 (16,384). +Deterministic: nav to /compare + answer names 5090 and the 5,376 delta. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_any, + number_mentioned, llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--6', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_compare", navigated_to(t, "/compare"), "expected the GPU comparison tool") + j.check("answer_names_5090", contains_any(fa, ["5090"]), + f"expected the RTX 5090 as the winner; final={fa!r}") + j.check("answer_delta", number_mentioned(fa, 5376), + "expected the 5,376 CUDA-core difference (21,760 - 16,384)") + ok, ev = llm_text_match(fa, "the RTX 5090 has more CUDA cores — 5,376 more (21,760 vs 16,384)", + "Which GPU has more CUDA cores and by how many, 5090 vs 4090?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_7.py b/sites/nvidia/verify/verify_7.py new file mode 100644 index 00000000..34dddddb --- /dev/null +++ b/sites/nvidia/verify/verify_7.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""NVIDIA--7: compare RTX 5080 vs RTX 4080 SUPER — which has the higher memory bandwidth? +Ground truth: RTX 5080 (960 GB/s vs 736 GB/s). Deterministic: nav to /compare + answer. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_any, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--7', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_compare", navigated_to(t, "/compare"), "expected the GPU comparison tool") + j.check("answer_names_5080", contains_any(fa, ["5080"]), + f"expected the RTX 5080 as the higher-bandwidth card; final={fa!r}") + ok, ev = llm_text_match(fa, "the RTX 5080 has higher memory bandwidth (960 GB/s vs 736 GB/s)", + "Which has higher memory bandwidth, RTX 5080 or RTX 4080 SUPER?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_8.py b/sites/nvidia/verify/verify_8.py new file mode 100644 index 00000000..3f6fc19b --- /dev/null +++ b/sites/nvidia/verify/verify_8.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""NVIDIA--8: of all Studio / Professional GPUs, which has the most memory, and how much? +Ground truth: RTX PRO 6000 Blackwell @ 96 GB. Deterministic: nav to catalog + answer tokens. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_all, + number_mentioned, llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--8', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_catalog", navigated_to(t, "/products"), "expected the Studio/Professional catalog") + j.check("answer_names_pro6000", contains_all(fa, ["RTX PRO 6000"]), + f"expected the RTX PRO 6000 Blackwell; final={fa!r}") + j.check("answer_memory", number_mentioned(fa, 96), "expected 96 (GB)") + ok, ev = llm_text_match(fa, "RTX PRO 6000 Blackwell with 96 GB (the most of any Studio/Professional GPU)", + "Which Studio/Professional GPU has the most memory and how much?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_9.py b/sites/nvidia/verify/verify_9.py new file mode 100644 index 00000000..10f12a6a --- /dev/null +++ b/sites/nvidia/verify/verify_9.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""NVIDIA--9: in Drivers, the latest Game Ready driver version for the GeForce RTX 50 +Series on Windows 11. Ground truth (this mirror's frozen build): 566.36. +Deterministic: nav to /drivers + exact version token. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, final_answer, navigated_to, contains_any, + llm_text_match, Judge, parse_args) + +def main(): + a = parse_args(); j = Judge('NVIDIA--9', a.no_llm) + t = load_run(a.run_dir); fa = final_answer(t) + j.check("nav_drivers", navigated_to(t, "/drivers"), "expected the Drivers finder") + j.check("answer_version", contains_any(fa, ["566.36"]), + f"expected Game Ready version 566.36; final={fa!r}") + ok, ev = llm_text_match(fa, "Game Ready driver version 566.36", + "Latest Game Ready driver for GeForce RTX 50 Series on Windows 11?") + j.check("answer_consistent", ok, ev, llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/nvidia/verify/verify_lib.py b/sites/nvidia/verify/verify_lib.py new file mode 100644 index 00000000..4e5f07b5 --- /dev/null +++ b/sites/nvidia/verify/verify_lib.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +"""verify_lib.py — shared deterministic + LLM utilities for the NVIDIA mirror. + +Philosophy: DETERMINISTIC FIRST (mirrors sites/merriam_webster/verify/verify_lib.py +and sites/carmax/verify/verify_lib.py). + 1. Trajectory navigation check (anti knowledge-shortcut): the agent MUST have opened + the relevant on-site page. NVIDIA's spec sheets / driver versions here are the + mirror's *own* frozen values (e.g. a fictional 566.36 driver build), so a correct + answer with no matching navigation is a memory-recall shortcut = FAIL. + 2. Answer check: exact / token-containment / numeric against frozen ground truth. + 3. DB after-state check (stateful tasks): query the SQLite instance DB directly — the + strongest signal (cart_items / orders / wishlist_items / reviews / newsletter / users). + 4. LLM utilities (text match, screenshot-contains) ONLY where exact matching is brittle, + ALWAYS anchored on ground truth; the model verifies *presence*, never supplies knowledge. + +Input signature (per task): + --run_dir DIR agent trajectory dir: trajectory.json + screenshots/step_NNN.png + --initial_db PATH initial-state SQLite DB (default: fetched instance_seed from container) + --after_db PATH after-state SQLite DB (default: fetched live instance DB from container) + --container NAME docker container to fetch DBs from (default: $WH_CONTAINER or wh-review) + --no_llm skip LLM-based checks (deterministic-only) +Output: JSON {task_id, pass, reason, evidence[]} to stdout; exit 0 on PASS, 1 on FAIL. +""" +import base64, json, os, re, sqlite3, subprocess, sys, tempfile, urllib.request +from pathlib import Path +from dataclasses import dataclass +import simpleArgParser as sap + +SITE = "nvidia" + +# ---------------------------------------------------------------- trajectory +def load_run(run_dir): + d = Path(run_dir) + traj = json.loads((d / "trajectory.json").read_text()) + traj["_run_dir"] = d + traj["_shots"] = {p.name: p for p in sorted((d / "screenshots").glob("step_*.png"))} + return traj + +def step_urls(traj): + return [s.get("url", "") for s in traj.get("steps", [])] + +def navigated_to(traj, substr, times=1): + return sum(1 for u in step_urls(traj) if substr in u) >= times + +def navigated_any(traj, substrs): + return any(navigated_to(traj, s) for s in substrs) + +def navigated_re(traj, pattern): + rx = re.compile(pattern) + return any(rx.search(u or "") for u in step_urls(traj)) + +def final_answer(traj): + return (traj.get("final_answer") or "").strip() + +def _shot(traj, name): + if not name: + return None + p = traj["_shots"].get(Path(name).name) + return p if (p and p.exists()) else None + +def shot_after_url(traj, substr): + for s in traj.get("steps", []): + if substr in s.get("url", ""): + p = _shot(traj, s.get("screenshot_after")) + if p: + return p + return None + +def last_shot(traj): + for s in reversed(traj.get("steps", [])): + p = _shot(traj, s.get("screenshot_after")) or _shot(traj, s.get("screenshot_before")) + if p: + return p + shots = sorted(traj["_shots"].values()) + return shots[-1] if shots else None + +# ---------------------------------------------------------------- deterministic answer match +def norm(s): + return re.sub(r"\s+", " ", (s or "").strip()).casefold() + +def answer_equals(final, expected): + return norm(final) == norm(expected) + +def contains_all(final, tokens): + f = norm(final) + return all(norm(t) in f for t in tokens) + +def contains_any(final, tokens): + f = norm(final) + return any(norm(t) in f for t in tokens) + +def extract_prices(text): + """Return list of dollar amounts as ints, e.g. '$1,999' -> 1999.""" + return [int(m.replace(",", "")) for m in re.findall(r"\$?\s?([\d]{1,3}(?:,\d{3})+|\d{3,7})", text or "")] + +def price_mentioned(final, amount, tol=0): + """True if `amount` (int) appears in final answer (comma or plain).""" + f = norm(final) + cands = {str(amount), f"{amount:,}"} + if tol: + return any(abs(p - amount) <= tol for p in extract_prices(final)) + return any(c in f for c in cands) + +def number_mentioned(final, amount): + """True if integer `amount` appears with or without thousands commas.""" + f = norm(final) + return str(amount) in f or f"{amount:,}" in f + +def extract_ints(text): + return [int(m.replace(",", "")) for m in re.findall(r"\b(\d{1,3}(?:,\d{3})+|\d+)\b", text or "")] + +# ---------------------------------------------------------------- DB state +def fetch_db(container, kind): + """kind: 'instance' (after) or 'instance_seed' (initial). docker cp -> temp file.""" + src = f"{container}:/opt/WebSyn/{SITE}/{kind}/{SITE}.db" + fd, path = tempfile.mkstemp(suffix=".db") + os.close(fd) + r = subprocess.run(["docker", "cp", src, path], capture_output=True, text=True) + if r.returncode != 0: + try: + os.unlink(path) + except OSError: + pass + raise RuntimeError(f"docker cp {src} failed: {r.stderr.strip()}") + return path + +def resolve_db(arg, container, kind): + if arg: + return arg + try: + return fetch_db(container, kind) + except Exception: + return None # caller treats None as "unavailable" and FAILs that check + +def db_query(db_path, sql, params=()): + con = sqlite3.connect(db_path) + try: + return con.execute(sql, params).fetchall() + finally: + con.close() + +# --- NVIDIA-specific state helpers (return None if db unavailable) ----------- +def user_field(db_path, email, field): + if not db_path: + return None + r = db_query(db_path, f"SELECT {field} FROM users WHERE email=?", (email,)) + return r[0][0] if r else None + +def user_exists(db_path, email): + if not db_path: + return None + return bool(db_query(db_path, "SELECT 1 FROM users WHERE email=?", (email,))) + +def product_field(db_path, slug, field): + if not db_path: + return None + r = db_query(db_path, f"SELECT {field} FROM products WHERE slug=?", (slug,)) + return r[0][0] if r else None + +def cart_for(db_path, email): + """Return list of (slug, name, quantity) in the user's cart, or None.""" + if not db_path: + return None + return [tuple(r) for r in db_query(db_path, + "SELECT p.slug, p.name, c.quantity FROM cart_items c " + "JOIN users u ON u.id=c.user_id JOIN products p ON p.id=c.product_id " + "WHERE u.email=?", (email,))] + +def wishlist_for(db_path, email): + """Return list of (slug, name, category) in the user's wishlist, or None.""" + if not db_path: + return None + return [tuple(r) for r in db_query(db_path, + "SELECT p.slug, p.name, p.category FROM wishlist_items w " + "JOIN users u ON u.id=w.user_id JOIN products p ON p.id=w.product_id " + "WHERE u.email=?", (email,))] + +def orders_for(db_path, email): + """Return list of dicts {status, total, items:[(slug,name)]} for a user's orders, or None.""" + if not db_path: + return None + rows = db_query(db_path, + "SELECT o.id, o.status, o.total_usd FROM orders o " + "JOIN users u ON u.id=o.user_id WHERE u.email=?", (email,)) + out = [] + for oid, status, total in rows: + items = db_query(db_path, + "SELECT p.slug, oi.name FROM order_items oi " + "LEFT JOIN products p ON p.id=oi.product_id WHERE oi.order_id=?", (oid,)) + out.append({"id": oid, "status": status, "total": total, + "items": [tuple(i) for i in items]}) + return out + +def reviews_for(db_path, email, product_slug=None): + """Return list of (product_slug, rating, title, body) for a user's reviews, or None.""" + if not db_path: + return None + sql = ("SELECT p.slug, r.rating, r.title, r.body FROM reviews r " + "JOIN users u ON u.id=r.user_id JOIN products p ON p.id=r.product_id " + "WHERE u.email=?") + p = [email] + if product_slug: + sql += " AND p.slug=?"; p.append(product_slug) + return [tuple(r) for r in db_query(db_path, sql, tuple(p))] + +def newsletter_has(db_path, email): + if not db_path: + return None + return bool(db_query(db_path, "SELECT 1 FROM newsletter WHERE email=?", (email,))) + +# ---------------------------------------------------------------- shared LLM utilities (anchored) +_NO_LLM = False + +def _llm_config(): + return (os.environ.get("OPENAI_API_KEY", ""), + os.environ.get("OPENAI_BASE_URL", ""), + os.environ.get("JUDGE_MODEL", "")) + +def _chat(messages, max_tokens=1024): + if _NO_LLM: + return None + key, base, model = _llm_config() + if not (key and base and model): + return None + payload = {"model": model, "messages": messages, "max_tokens": max_tokens, "temperature": 1.0} + # Accept OPENAI_BASE_URL as either the base (".../api", like agent.py/eval_judge) or a + # full chat-completions URL (".../chat/completions", the merriam_webster convention). + b = base.rstrip("/") + endpoint = b if b.endswith("/chat/completions") else b + "/chat/completions" + req = urllib.request.Request(endpoint, + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json", + "Authorization": f"Bearer {key}"}) + try: + data = json.loads(urllib.request.urlopen(req, timeout=180).read()) + return data["choices"][0]["message"]["content"] + except Exception: + return None + +def _verdict(out): + if not out: + return False, "" + s = out.strip() + return s.upper().startswith("PASS"), s + +def llm_text_match(agent_answer, ground_truth, question): + if _NO_LLM: + return False, "[skipped: --no_llm]" + out = _chat([{"role": "user", "content": + f"You are a STRICT binary grader.\nQuestion: {question}\n" + f"Ground-truth answer (ANCHOR — judge against THIS, never use your own knowledge): {ground_truth}\n" + f"Agent's answer: {agent_answer}\n" + f"Decide PASS or FAIL ignoring case/punctuation/word order/surrounding prose. " + f"PASS only if the agent's answer is consistent with the ground truth AND actually answers the question. " + f"Line 1: PASS or FAIL. Line 2: one-sentence reason."}]) + return _verdict(out) + +def llm_screenshot_shows(shot_path, must_show, question=""): + if _NO_LLM: + return False, "[skipped: --no_llm]" + b64 = base64.b64encode(Path(shot_path).read_bytes()).decode() + out = _chat([{"role": "user", "content": [ + {"type": "text", "text": + f"You are a STRICT binary grader. Only what is VISIBLY rendered in this screenshot counts.\n" + f"Question the page should answer: {question}\n" + f"Expected content to verify PRESENCE of: {must_show}\n" + f"PASS only if the expected content (or a semantically equivalent on-screen value) is visibly shown. " + f"Do NOT use prior knowledge — judge only the rendered pixels.\n" + f"Line 1: PASS or FAIL. Line 2: quote the visible evidence."}, + {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}]}]) + return _verdict(out) + +# ---------------------------------------------------------------- judge harness + CLI +class Judge: + def __init__(self, task_id, no_llm=False): + global _NO_LLM + _NO_LLM = bool(no_llm) + self.task_id = task_id + self.no_llm = no_llm + self.ok = True + self.reason = "" + self.evidence = [] + + def check(self, name, cond, evidence="", llm=False): + if llm and self.no_llm: + self.evidence.append(f"[SKIP] {name} (--no_llm)") + return True + if cond: + self.evidence.append(f"[PASS] {name}: {evidence}") + else: + self.ok = False + if not self.reason: + self.reason = name + self.evidence.append(f"[FAIL] {name}: {evidence}") + return bool(cond) + + def emit(self): + print(json.dumps({"task_id": self.task_id, "pass": self.ok, + "reason": self.reason, "evidence": self.evidence}, indent=2)) + sys.exit(0 if self.ok else 1) + +def parse_args(): + @dataclass + class VerifyArgs: + run_dir: str = "" + initial_db: str = "" + after_db: str = "" + container: str = os.environ.get("WH_CONTAINER", "wh-review") + no_llm: bool = False + + def post_process(self): + if not self.run_dir: + raise SystemExit("--run_dir is required") + return sap.parse_args(VerifyArgs)