diff --git a/.github/workflows/site-build.yml b/.github/workflows/site-build.yml new file mode 100644 index 0000000..447fbed --- /dev/null +++ b/.github/workflows/site-build.yml @@ -0,0 +1,54 @@ +name: Marketing Site Build + +on: + pull_request: + paths: + - 'web/**' + - 'package.json' + - 'pnpm-workspace.yaml' + - '.github/workflows/site-build.yml' + push: + branches: [main] + paths: + - 'web/**' + - 'package.json' + - 'pnpm-workspace.yaml' + +concurrency: + group: site-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Set up pnpm + # No `version:` — packageManager in package.json is the canonical source. + uses: pnpm/action-setup@v4 + + - name: pnpm cache + uses: actions/cache@v4 + with: + path: ~/.pnpm-store + key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: pnpm-${{ runner.os }}- + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Build site + run: pnpm -F @qg/site build + + - name: Upload dist artifact + uses: actions/upload-artifact@v4 + with: + name: site-dist + path: web/site/dist + retention-days: 7 diff --git a/package.json b/package.json index bfae5f3..61186f8 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "description": "Workspace root for QueryGym websites (leaderboard + marketing). Not published.", "scripts": { "build:leaderboard": "pnpm -F @qg/leaderboard build:data && pnpm -F @qg/leaderboard build", - "dev:leaderboard": "pnpm -F @qg/leaderboard dev" + "dev:leaderboard": "pnpm -F @qg/leaderboard dev", + "build:site": "pnpm -F @qg/site build", + "dev:site": "pnpm -F @qg/site dev" }, "engines": { "node": ">=20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 253e43a..e7a14a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,28 @@ importers: web/shared: {} + web/site: + dependencies: + '@astrojs/tailwind': + specifier: ^5.1.5 + version: 5.1.5(astro@5.18.1(@types/node@20.19.39)(jiti@1.21.7)(rollup@4.60.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) + '@qg/shared': + specifier: workspace:* + version: link:../shared + astro: + specifier: ^5.0.0 + version: 5.18.1(@types/node@20.19.39)(jiti@1.21.7)(rollup@4.60.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) + tailwindcss: + specifier: ^3.4.0 + version: 3.4.19(tsx@4.21.0)(yaml@2.8.3) + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.39 + typescript: + specifier: ^5.6.0 + version: 5.9.3 + packages: '@alloc/quick-lru@5.2.0': diff --git a/web/site/astro.config.mjs b/web/site/astro.config.mjs new file mode 100644 index 0000000..23500d6 --- /dev/null +++ b/web/site/astro.config.mjs @@ -0,0 +1,13 @@ +import { defineConfig } from "astro/config"; +import tailwind from "@astrojs/tailwind"; + +export default defineConfig({ + site: "https://querygym.com", + output: "static", + integrations: [tailwind({ applyBaseStyles: false })], + vite: { + ssr: { + noExternal: ["@qg/shared"], + }, + }, +}); diff --git a/web/site/package.json b/web/site/package.json new file mode 100644 index 0000000..4c98859 --- /dev/null +++ b/web/site/package.json @@ -0,0 +1,23 @@ +{ + "name": "@qg/site", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Astro site for querygym.com — marketing/landing site for the toolkit.", + "scripts": { + "build": "astro build", + "dev": "astro dev", + "preview": "astro preview", + "check": "astro check" + }, + "dependencies": { + "@qg/shared": "workspace:*", + "astro": "^5.0.0", + "@astrojs/tailwind": "^5.1.5", + "tailwindcss": "^3.4.0" + }, + "devDependencies": { + "typescript": "^5.6.0", + "@types/node": "^20.0.0" + } +} diff --git a/web/site/public/_redirects b/web/site/public/_redirects new file mode 100644 index 0000000..8314cd8 --- /dev/null +++ b/web/site/public/_redirects @@ -0,0 +1,8 @@ +# Cloudflare Pages-style _redirects file. Same-origin redirects served from +# querygym.com. Cross-origin redirects (to leaderboard.querygym.com) live in +# Cloudflare Bulk Redirects on the zone, not here. + +# Old Jekyll URLs → new pages +/leaderboard https://leaderboard.querygym.com/ 301 +/leaderboard.html https://leaderboard.querygym.com/ 301 +/QueryGym/* https://querygym.com/:splat 301 diff --git a/web/site/public/querygym-logo.png b/web/site/public/querygym-logo.png new file mode 100644 index 0000000..b2cf166 Binary files /dev/null and b/web/site/public/querygym-logo.png differ diff --git a/web/site/src/components/CitationCard.astro b/web/site/src/components/CitationCard.astro new file mode 100644 index 0000000..2c8916e --- /dev/null +++ b/web/site/src/components/CitationCard.astro @@ -0,0 +1,38 @@ +--- +interface Props { + title: string; + venue: string; + authors: string; + bibtex: string; + url?: string; +} + +const { title, venue, authors, bibtex, url } = Astro.props; +--- + +
+
{venue}
+

{title}

+

{authors}

+ { + url && ( + + {url} ↗ + + ) + } + +
+
+ + BibTeX + +
{bibtex}
+
+
+
diff --git a/web/site/src/components/CodeBlock.astro b/web/site/src/components/CodeBlock.astro new file mode 100644 index 0000000..d5cc9c5 --- /dev/null +++ b/web/site/src/components/CodeBlock.astro @@ -0,0 +1,35 @@ +--- +interface Props { + language?: string; + filename?: string; +} +const { language = "bash", filename } = Astro.props; +--- + +
+
+ {filename ?? language} + +
+
+
+ + diff --git a/web/site/src/components/EcosystemMap.astro b/web/site/src/components/EcosystemMap.astro new file mode 100644 index 0000000..270ff0c --- /dev/null +++ b/web/site/src/components/EcosystemMap.astro @@ -0,0 +1,77 @@ +--- +/** + * Visual map of the QueryGym ecosystem: Library / Dashboard / Leaderboard / Docs. + * Static SVG-free version using CSS grid + gradient borders. + */ + +const cards = [ + { + label: "Library", + sub: "querygym (pip)", + body: "The toolkit itself. Methods, prompt bank, searchers, CLI.", + href: "https://pypi.org/project/querygym/", + cta: "pip install querygym", + primary: false, + }, + { + label: "Dashboard", + sub: "dashboard.querygym.com", + body: "Hosted product. Run methods through a UI, compare results live, share runs. API keys + billing.", + href: "https://dashboard.querygym.com", + cta: "Open Dashboard ↗", + primary: true, + }, + { + label: "Leaderboard", + sub: "leaderboard.querygym.com", + body: "SIGIR 2026 reproducibility results across IR benchmarks. Every row backed by a citable JSON.", + href: "https://leaderboard.querygym.com", + cta: "View Leaderboard →", + primary: false, + }, + { + label: "Docs", + sub: "querygym.readthedocs.io", + body: "API reference, methods reference, contributor guide, schema docs.", + href: "https://querygym.readthedocs.io", + cta: "Read the Docs →", + primary: false, + }, +]; +--- + +
+

The ecosystem

+

+ QueryGym is split into four surfaces. They share the same data contract, + so a run from the dashboard or a third-party submitter lands in the same + leaderboard as the toolkit's own results. +

+ +
+ { + cards.map((c) => ( + +
+
+ {c.sub} +
+
+ {c.label} +
+

{c.body}

+
+
{c.cta}
+
+ )) + } +
+
diff --git a/web/site/src/components/FeatureGrid.astro b/web/site/src/components/FeatureGrid.astro new file mode 100644 index 0000000..15fabd1 --- /dev/null +++ b/web/site/src/components/FeatureGrid.astro @@ -0,0 +1,60 @@ +--- +const features = [ + { + title: "Single Prompt Bank", + body: + "One YAML registry of every prompt with version, license, and authorship metadata. Cite the exact text used in any run.", + icon: "📚", + }, + { + title: "Pluggable searchers", + body: + "Drop-in adapters for Pyserini, PyTerrier, BEIR, MS MARCO, and any custom retriever. Bring your own index.", + icon: "🔌", + }, + { + title: "Stable run schema", + body: + "Every run emits a versioned JSON conforming to a public JSON Schema — same shape across the toolkit, dashboard, and third-party submitters.", + icon: "🧬", + }, + { + title: "OpenAI-compatible LLMs", + body: + "Works with any OpenAI-compatible endpoint. Gpt-4.1, Qwen, Mistral, vLLM, Ollama — switch with a config change.", + icon: "🧠", + }, + { + title: "Reproducible by design", + body: + "Every leaderboard row links a JSON, a TREC run file, and the reformulated queries. Re-evaluate from a fresh clone.", + icon: "🔁", + }, + { + title: "Citable artifacts", + body: + "Backed by two papers (WWW 2026 Demos, SIGIR 2026 Reproducibility) and a tagged reproducibility corpus on GitHub.", + icon: "📄", + }, +]; +--- + +
+

What you get

+

+ QueryGym pairs a small, opinionated library with a contract-driven + reproducibility pipeline. The toolkit, the dashboard, and the leaderboard + all share the same data shape. +

+ +
diff --git a/web/site/src/components/Hero.astro b/web/site/src/components/Hero.astro new file mode 100644 index 0000000..e9025ed --- /dev/null +++ b/web/site/src/components/Hero.astro @@ -0,0 +1,75 @@ +--- +/** + * Marketing hero: gradient panel + typed-query → expanded-query animation. + * Two CTAs side-by-side: primary "Open Dashboard" (the hosted product), + * secondary "Get Started" (pip install + docs). + */ +--- + +
+
+
+ + Open source · WWW 2026 · SIGIR 2026 Reproducibility + +

+ Reproducible query reformulation, powered by LLMs. +

+

+ QueryGym is a toolkit for benchmarking and reproducing LLM-based query + rewriting methods across IR datasets. Open prompt bank, pluggable + searchers, frozen schemas, citable runs. +

+ +
+ + +
+
+ + Live reformulation preview +
+
USER QUERY
+
+ what causes diabetes? +
+ +
+ + querygym.create_reformulator("genqr_ensemble") + +
+ +
REFORMULATED
+
+
+ what causes diabetes? +
+
+ what causes diabetes type 1 type 2 insulin resistance pancreas autoimmune + glucose metabolism risk factors genetic predisposition lifestyle obesity +
+
+
+
+
diff --git a/web/site/src/components/MethodGrid.astro b/web/site/src/components/MethodGrid.astro new file mode 100644 index 0000000..95fb3b5 --- /dev/null +++ b/web/site/src/components/MethodGrid.astro @@ -0,0 +1,34 @@ +--- +import methods from "../data/methods.json"; + +interface Props { + /** Section variant: "compact" (3-up, smaller) or "full" (3-up, with paper links). */ + variant?: "compact" | "full"; +} + +const { variant = "full" } = Astro.props; +--- + + diff --git a/web/site/src/data/methods.json b/web/site/src/data/methods.json new file mode 100644 index 0000000..5764e8c --- /dev/null +++ b/web/site/src/data/methods.json @@ -0,0 +1,65 @@ +[ + { + "id": "genqr", + "name": "GenQR", + "tagline": "Generic LLM-driven keyword expansion.", + "paper": "Wang et al., 2023", + "paper_url": "https://arxiv.org/abs/2308.00415" + }, + { + "id": "genqr_ensemble", + "name": "GenQR Ensemble", + "tagline": "10 instruction variants for diverse keyword expansion.", + "paper": "Dhole & Agichtein, 2024", + "paper_url": "https://arxiv.org/abs/2404.03746" + }, + { + "id": "query2doc", + "name": "Query2Doc", + "tagline": "Generates pseudo-documents from LLM knowledge.", + "paper": "Wang et al., 2023", + "paper_url": "https://arxiv.org/abs/2303.07678" + }, + { + "id": "qa_expand", + "name": "QA Expand", + "tagline": "Question-answer expansion with sub-questions.", + "paper": "Seo et al., 2025", + "paper_url": "https://arxiv.org/abs/2502.08557" + }, + { + "id": "mugi", + "name": "MuGI", + "tagline": "Multi-granularity expansion with adaptive concatenation.", + "paper": "Zhang et al., 2024", + "paper_url": "https://arxiv.org/abs/2401.06311" + }, + { + "id": "lamer", + "name": "LameR", + "tagline": "Context-based passage synthesis from retrieved docs.", + "paper": "Mackie et al., 2023", + "paper_url": "https://arxiv.org/abs/2304.14233" + }, + { + "id": "csqe", + "name": "CSQE", + "tagline": "Sentence-level context expansion (KEQE + CSQE).", + "paper": "Lee et al., 2024", + "paper_url": "https://arxiv.org/abs/2402.18031" + }, + { + "id": "thinkqe", + "name": "ThinkQE", + "tagline": "Multi-round reasoning with corpus feedback.", + "paper": "Le et al., 2025", + "paper_url": "https://arxiv.org/abs/2506.09260" + }, + { + "id": "query2e", + "name": "Query2E", + "tagline": "Query-to-entity / keyword expansion.", + "paper": "Jagerman et al., 2023", + "paper_url": "https://arxiv.org/abs/2305.03653" + } +] diff --git a/web/site/src/layouts/Default.astro b/web/site/src/layouts/Default.astro new file mode 100644 index 0000000..63cbdaf --- /dev/null +++ b/web/site/src/layouts/Default.astro @@ -0,0 +1,52 @@ +--- +import "../styles/global.css"; +import Header from "@qg/shared/components/Header.astro"; +import Footer from "@qg/shared/components/Footer.astro"; + +interface Props { + title: string; + description?: string; + /** Hide the gradient header (used for hero pages that draw their own). */ + bareHeader?: boolean; +} + +const { title, description, bareHeader = false } = Astro.props; + +const navLinks = [ + { label: "Install", href: "/install" }, + { label: "Methods", href: "/methods" }, + { label: "Reproducibility", href: "/reproducibility" }, + { label: "Cite", href: "/cite" }, + { label: "Docs", href: "https://querygym.readthedocs.io", external: true }, + { label: "Dashboard ↗", href: "https://dashboard.querygym.com", external: true }, +]; +--- + + + + + + + {title} — QueryGym + {description && } + + {description && } + + + + + { + !bareHeader && ( +
+ ) + } +
+ +
+