diff --git a/apps/web/app/Poppins-Medium.ttf b/apps/web/app/Poppins-Medium.ttf new file mode 100644 index 0000000000..5b46f19856 Binary files /dev/null and b/apps/web/app/Poppins-Medium.ttf differ diff --git a/apps/web/app/Poppins-SemiBold.ttf b/apps/web/app/Poppins-SemiBold.ttf new file mode 100644 index 0000000000..3bbad2a8be Binary files /dev/null and b/apps/web/app/Poppins-SemiBold.ttf differ diff --git a/apps/web/app/collections/[slug]/opengraph-image.tsx b/apps/web/app/collections/[slug]/opengraph-image.tsx index 991dab2ee2..685c3c3df8 100644 --- a/apps/web/app/collections/[slug]/opengraph-image.tsx +++ b/apps/web/app/collections/[slug]/opengraph-image.tsx @@ -1,40 +1,187 @@ import { ImageResponse } from 'next/og'; +import { fetchCollections } from '@/utils/api'; +import { toCollectionSlug } from '@/lib/collections'; +import { listCollectionPreviewRepos } from '@/lib/server/internal-api'; -export const runtime = 'edge'; -export const alt = 'Collection'; -export const size = { width: 800, height: 418 }; +export const runtime = 'nodejs'; +export const alt = 'Collection Rankings - OSSInsight'; +export const size = { width: 1200, height: 630 }; export const dynamic = 'force-dynamic'; export const contentType = 'image/png'; export default async function Image({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; - const name = decodeURIComponent(slug).replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); - const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then(r => r.arrayBuffer()); - const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then(r => r.arrayBuffer()); + // Resolve collection name from slug + let collectionName = decodeURIComponent(slug).replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); + let topRepos: string[] = []; + + try { + const collections = await fetchCollections(); + const collection = collections.find((c) => toCollectionSlug(c.name) === slug); + if (collection) { + collectionName = collection.name; + const previews = await listCollectionPreviewRepos([collection.id]); + topRepos = previews + .filter((p) => p.collection_id === collection.id) + .sort((a, b) => a.repo_rank - b.repo_rank) + .slice(0, 5) + .map((p) => p.repo_name.split('/')[1] ?? p.repo_name); + } + } catch { + // DB unavailable, use slug-derived name + } + + const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then((r) => + r.arrayBuffer(), + ); + const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then((r) => + r.arrayBuffer(), + ); return new ImageResponse( ( -
-
-
-
- -
COLLECTION
-
{name}
-
Rankings & Trends on OSSInsight
-
ossinsight.io
+
+ {/* Accent top bar */} +
+ {/* Background glows */} +
+ + {/* Label */} +
+ Collection · OSSInsight +
+ + {/* Collection name */} +
+ {collectionName} +
+ + {/* Top repos */} + {topRepos.length > 0 && ( +
+ {topRepos.map((repo, i) => ( +
+ {repo} +
+ ))} +
+ )} + + {/* Subtitle */} +
+ Rankings & Trends · Updated in real time +
+ + {/* Footer */} +
+ ossinsight.io +
), { ...size, fonts: [ - { name: 'Poppins', data: await poppinsMedium, style: 'normal' as const, weight: 500 as const }, - { name: 'Poppins', data: await poppinsSemiBold, style: 'normal' as const, weight: 600 as const }, + { + name: 'Poppins', + data: await poppinsMedium, + style: 'normal' as const, + weight: 500 as const, + }, + { + name: 'Poppins', + data: await poppinsSemiBold, + style: 'normal' as const, + weight: 600 as const, + }, ], }, ); diff --git a/apps/web/app/collections/page.tsx b/apps/web/app/collections/page.tsx index bc21fdd851..eb8af9ff8f 100644 --- a/apps/web/app/collections/page.tsx +++ b/apps/web/app/collections/page.tsx @@ -13,6 +13,14 @@ import { CollectionsList } from './content'; export const metadata: Metadata = { title: 'Explore Collections', description: 'Find insights about the monthly or historical rankings and trends in technical fields with curated repository lists.', + alternates: { + // Canonical strips query params (page, sort, q) to avoid duplicate content + canonical: '/collections', + }, + robots: { + index: true, + follow: true, + }, }; export const revalidate = 3600; diff --git a/apps/web/app/explore/Poppins-Medium.ttf b/apps/web/app/explore/Poppins-Medium.ttf new file mode 100644 index 0000000000..5b46f19856 Binary files /dev/null and b/apps/web/app/explore/Poppins-Medium.ttf differ diff --git a/apps/web/app/explore/Poppins-SemiBold.ttf b/apps/web/app/explore/Poppins-SemiBold.ttf new file mode 100644 index 0000000000..3bbad2a8be Binary files /dev/null and b/apps/web/app/explore/Poppins-SemiBold.ttf differ diff --git a/apps/web/app/explore/opengraph-image.tsx b/apps/web/app/explore/opengraph-image.tsx new file mode 100644 index 0000000000..2009abf7d7 --- /dev/null +++ b/apps/web/app/explore/opengraph-image.tsx @@ -0,0 +1,132 @@ +import { ImageResponse } from 'next/og'; + +export const runtime = 'edge'; +export const alt = 'GitHub Data Explorer - OSSInsight'; +export const size = { width: 1200, height: 630 }; +export const contentType = 'image/png'; + +export default async function Image() { + const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then((r) => + r.arrayBuffer(), + ); + const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then((r) => + r.arrayBuffer(), + ); + + return new ImageResponse( + ( +
+ {/* Accent top bar */} +
+ {/* Background glow */} +
+ + {/* Label */} +
+ OSSInsight +
+ + {/* Title */} +
+ Data Explorer +
+ + {/* Description */} +
+ Ask questions in plain English · AI-generated SQL · 10B+ GitHub events +
+ + {/* Footer */} +
+ ossinsight.io/explore +
+
+ ), + { + ...size, + fonts: [ + { + name: 'Poppins', + data: await poppinsMedium, + style: 'normal' as const, + weight: 500 as const, + }, + { + name: 'Poppins', + data: await poppinsSemiBold, + style: 'normal' as const, + weight: 600 as const, + }, + ], + }, + ); +} diff --git a/apps/web/app/home-content.tsx b/apps/web/app/home-content.tsx index 2e8a99802f..79f15227aa 100644 --- a/apps/web/app/home-content.tsx +++ b/apps/web/app/home-content.tsx @@ -1280,7 +1280,7 @@ function HotCollectionsSection() {
+ {/* Accent line */} +
+ + {/* 404 number */} +
+ 404 +
+ + {/* Heading */} +

+ Page Not Found +

+ + {/* Description */} +

+ The page you're looking for doesn't exist or may have been moved. + Try searching for a repository or exploring our collections. +

+ + {/* Action buttons */} +
+ + ← Back to Home + + + 🔍 Search Data Explorer + + + 📈 Trending Repos + +
+ + {/* Quick links */} +
+ Try: + + Collections + + · + + Trending + + · + + Data Explorer + +
+ + {/* Branding */} +
+ OSSInsight — Open Source Software Insight +
+
+ ); +} diff --git a/apps/web/app/opengraph-image.tsx b/apps/web/app/opengraph-image.tsx new file mode 100644 index 0000000000..9f5c38c2c1 --- /dev/null +++ b/apps/web/app/opengraph-image.tsx @@ -0,0 +1,171 @@ +import { ImageResponse } from 'next/og'; + +export const runtime = 'edge'; +export const alt = 'OSSInsight - Open Source Software Insight'; +export const size = { width: 1200, height: 630 }; +export const contentType = 'image/png'; + +export default async function Image() { + const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then((r) => + r.arrayBuffer(), + ); + const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then((r) => + r.arrayBuffer(), + ); + + return new ImageResponse( + ( +
+ {/* Accent top bar */} +
+ {/* Background glow */} +
+
+ + {/* Logo / brand */} +
+
+
+ O +
+
+
+ OSSInsight +
+
+ + {/* Tagline */} +
+ Open Source Software Insight +
+ + {/* Description */} +
+ Real-time analytics for 10B+ GitHub events · Analyze repos · Compare projects · Explore trends +
+ + {/* Footer */} +
+ ossinsight.io +
+
+ ), + { + ...size, + fonts: [ + { + name: 'Poppins', + data: await poppinsMedium, + style: 'normal' as const, + weight: 500 as const, + }, + { + name: 'Poppins', + data: await poppinsSemiBold, + style: 'normal' as const, + weight: 600 as const, + }, + ], + }, + ); +} diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index d675b07ea0..95e06a60c4 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; import { HomeContent } from './home-content'; -import { FAQPageJsonLd } from '@/components/json-ld'; +import { FAQPageJsonLd, SiteApplicationJsonLd } from '@/components/json-ld'; import { FAQ_ITEMS } from './faq-data'; export const metadata: Metadata = { @@ -24,6 +24,7 @@ export const metadata: Metadata = { export default function HomePage() { return ( <> +

OSSInsight — Open Source Software Insight

diff --git a/apps/web/app/sitemap.ts b/apps/web/app/sitemap.ts index b950e8d754..1cc76b0739 100644 --- a/apps/web/app/sitemap.ts +++ b/apps/web/app/sitemap.ts @@ -9,11 +9,22 @@ export const dynamic = 'force-dynamic'; export default async function sitemap(): Promise { const entries: MetadataRoute.Sitemap = []; - // Static pages - const staticPages = [ - { path: '/', priority: 1.0 }, - { path: '/explore/', priority: 0.8 }, - { path: '/collections/', priority: 0.8 }, + // Static pages — canonical URLs without trailing slash for /explore and /collections + const staticPages: Array<{ path: string; priority: number; images?: Array<{ url: string; title?: string; caption?: string }> }> = [ + { + path: '/', + priority: 1.0, + images: [ + { + url: `${SITE_URL}/seo-widgets-homepage.jpeg`, + title: 'OSSInsight — Open Source Software Insight', + caption: 'Real-time analytics for 10B+ GitHub events', + }, + ], + }, + { path: '/explore', priority: 0.8 }, + { path: '/collections', priority: 0.8 }, + { path: '/trending', priority: 0.8 }, ]; for (const page of staticPages) { @@ -21,6 +32,8 @@ export default async function sitemap(): Promise { url: `${SITE_URL}${page.path}`, changeFrequency: 'weekly', priority: page.priority, + // @ts-expect-error — Next.js MetadataRoute.Sitemap doesn't type images yet + ...(page.images ? { images: page.images } : {}), }); } @@ -33,6 +46,13 @@ export default async function sitemap(): Promise { url: `${SITE_URL}/collections/${slug}`, changeFrequency: 'weekly', priority: 0.6, + // @ts-expect-error — Next.js MetadataRoute.Sitemap doesn't type images yet + images: [ + { + url: `${SITE_URL}/collections/${slug}/opengraph-image`, + title: `${collection.name} — GitHub Repository Rankings`, + }, + ], }); entries.push({ url: `${SITE_URL}/collections/${slug}/trends`, diff --git a/apps/web/components/json-ld.tsx b/apps/web/components/json-ld.tsx index bd7fb16fbe..11c5c1df32 100644 --- a/apps/web/components/json-ld.tsx +++ b/apps/web/components/json-ld.tsx @@ -277,6 +277,34 @@ export function AggregateRatingJsonLd({ ); } +export function SiteApplicationJsonLd() { + return ( + + ); +} + export function DatasetJsonLd({ name, description, diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 738b22cf8c..bc54994ad6 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -10,6 +10,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); /** @type {import('next').NextConfig} */ const nextConfig = { + // Disable trailing slashes — canonical URLs have no trailing slash. + // Vercel will redirect /path/ → /path automatically when this is false (default). + trailingSlash: false, outputFileTracingRoot: path.join(__dirname, '../../'), transpilePackages: ['@repo/site-shell'], typescript: { diff --git a/configs/collections/10128.google-adk.yml b/configs/collections/10128.google-adk.yml new file mode 100644 index 0000000000..45bf5e8699 --- /dev/null +++ b/configs/collections/10128.google-adk.yml @@ -0,0 +1,16 @@ +id: 10128 +name: Google ADK +items: + - google/adk-python + - google/adk-java + - google/adk-web + - a2aproject/A2A + - AIDC-AI/Agentic-ADK + - Sri-Krishna-V/awesome-adk-agents + - tsubasakong/awesome-google-adk + - waldzellai/adk-typescript + - RashRAJ/awesome-adk + - raphaelmansuy/adk_training + - sokart/adk-walkthrough + - RubensZimbres/Multi-Agent-System-A2A-ADK-MCP + - mongodb-developer/MongoDB-ADK-Agents diff --git a/configs/collections/10129.neuro-symbolic-ai.yml b/configs/collections/10129.neuro-symbolic-ai.yml new file mode 100644 index 0000000000..e23286b3ac --- /dev/null +++ b/configs/collections/10129.neuro-symbolic-ai.yml @@ -0,0 +1,12 @@ +id: 10129 +name: Neuro-Symbolic AI +items: + - NucleoidAI/Nucleoid + - IBM/neuro-symbolic-ai + - IBM/torchlogic + - PacktPublishing/Neuro-Symbolic-AI + - aika-algorithm/aika-java + - nerdimite/neuro-symbolic-ai-soc + - anisha2102/awesome-neuro-symbolic-ai + - mattfaltyn/awesome-neuro-symbolic-ai + - ccclyu/awesome-deeplogic diff --git a/configs/collections/10130.ai-finops.yml b/configs/collections/10130.ai-finops.yml new file mode 100644 index 0000000000..a25c1e35ae --- /dev/null +++ b/configs/collections/10130.ai-finops.yml @@ -0,0 +1,13 @@ +id: 10130 +name: AI FinOps +items: + - BerriAI/litellm + - mlflow/mlflow + - langfuse/langfuse + - openlit/openlit + - Helicone/helicone + - AgentOps-AI/agentops + - pydantic/logfire + - uptrain-ai/uptrain + - traceloop/openllmetry + - whylabs/whylogs diff --git a/configs/collections/10131.synthetic-data.yml b/configs/collections/10131.synthetic-data.yml new file mode 100644 index 0000000000..7eff63a6ff --- /dev/null +++ b/configs/collections/10131.synthetic-data.yml @@ -0,0 +1,12 @@ +id: 10131 +name: Synthetic Data +items: + - sdv-dev/SDV + - gretelai/gretel-synthetics + - mostly-ai/mostlyai + - Kiln-AI/Kiln + - ServiceNow/SyGra + - NVIDIA/synthda + - Clearbox-AI/clearbox-synthetic-kit + - starfishdata/starfish + - TonicAI/tonic_validate diff --git a/configs/collections/10132.ai-quantitative-finance.yml b/configs/collections/10132.ai-quantitative-finance.yml new file mode 100644 index 0000000000..4f5386469b --- /dev/null +++ b/configs/collections/10132.ai-quantitative-finance.yml @@ -0,0 +1,15 @@ +id: 10132 +name: AI Quantitative Finance +items: + - AI4Finance-Foundation/FinRL + - AI4Finance-Foundation/FinGPT + - stefan-jansen/machine-learning-for-trading + - hudson-and-thames/mlfinlab + - vnpy/vnpy + - grananqvist/Awesome-Quant-Machine-Learning-Trading + - edtechre/pybroker + - thuquant/awesome-quant + - wangzhe3224/awesome-systematic-trading + - fasiondog/hikyuu + - alpacahq/alpaca-py + - quarkfin/qf-lib diff --git a/configs/collections/10133.ai-agent-marketplace.yml b/configs/collections/10133.ai-agent-marketplace.yml new file mode 100644 index 0000000000..72ba1f3f70 --- /dev/null +++ b/configs/collections/10133.ai-agent-marketplace.yml @@ -0,0 +1,13 @@ +id: 10133 +name: AI Agent Marketplace +items: + - Significant-Gravitas/AutoGPT + - crewAIInc/crewAI + - microsoft/autogen + - agno-agi/agno + - OpenBMB/AgentVerse + - THUDM/AgentBench + - smol-ai/developer + - OpenBMB/ChatDev + - letta-ai/letta + - SWE-agent/SWE-agent diff --git a/configs/collections/10134.knowledge-graphs-for-ai.yml b/configs/collections/10134.knowledge-graphs-for-ai.yml new file mode 100644 index 0000000000..cf057d554b --- /dev/null +++ b/configs/collections/10134.knowledge-graphs-for-ai.yml @@ -0,0 +1,14 @@ +id: 10134 +name: Knowledge Graphs for AI +items: + - microsoft/graphrag + - neo4j-labs/llm-graph-builder + - OSU-NLP-Group/HippoRAG + - FalkorDB/FalkorDB + - pingcap/autoflow + - Azure-Samples/graphrag-accelerator + - topoteretes/cognee + - BaranziniLab/KG_RAG + - Graph-COM/SubgraphRAG + - weaviate/Verba + - tomasonjo/diffbot-kg-chatbot diff --git a/configs/collections/10135.ai-observability.yml b/configs/collections/10135.ai-observability.yml new file mode 100644 index 0000000000..0daf08eb56 --- /dev/null +++ b/configs/collections/10135.ai-observability.yml @@ -0,0 +1,15 @@ +id: 10135 +name: AI Observability +items: + - langfuse/langfuse + - mlflow/mlflow + - Arize-ai/phoenix + - evidentlyai/evidently + - traceloop/openllmetry + - Helicone/helicone + - openlit/openlit + - AgentOps-AI/agentops + - pydantic/logfire + - uptrain-ai/uptrain + - whylabs/whylogs + - zenml-io/zenml diff --git a/configs/collections/10136.ai-code-review.yml b/configs/collections/10136.ai-code-review.yml new file mode 100644 index 0000000000..8e49a31a47 --- /dev/null +++ b/configs/collections/10136.ai-code-review.yml @@ -0,0 +1,12 @@ +id: 10136 +name: AI Code Review +items: + - qodo-ai/pr-agent + - qodo-ai/qodo-cover + - continuedev/continue + - microsoft/promptflow + - aorwall/moatless-tools + - TechNickAI/AICodeBot + - SWE-bench/SWE-bench + - SWE-agent/SWE-agent + - openai/evals diff --git a/configs/collections/10137.agent-sandboxing.yml b/configs/collections/10137.agent-sandboxing.yml new file mode 100644 index 0000000000..dcb674bc7e --- /dev/null +++ b/configs/collections/10137.agent-sandboxing.yml @@ -0,0 +1,12 @@ +id: 10137 +name: Agent Sandboxing +items: + - e2b-dev/E2B + - e2b-dev/code-interpreter + - daytonaio/daytona + - SWE-agent/SWE-ReX + - microsoft/TaskWeaver + - standardagents/arrow-js + - abshkbh/arrakis + - pipecat-ai/pipecat + - modal-labs/modal-client diff --git a/configs/collections/10138.ai-red-teaming.yml b/configs/collections/10138.ai-red-teaming.yml new file mode 100644 index 0000000000..f6e2a91fc4 --- /dev/null +++ b/configs/collections/10138.ai-red-teaming.yml @@ -0,0 +1,13 @@ +id: 10138 +name: AI Red Teaming +items: + - NVIDIA/garak + - promptfoo/promptfoo + - Azure/PyRIT + - confident-ai/deepteam + - llm-attacks/llm-attacks + - msoedov/agentic_security + - sherdencooper/GPTFuzz + - Tencent/AI-Infra-Guard + - protectai/rebuff + - microsoftarchive/promptbench diff --git a/configs/collections/10139.a2a-protocol.yml b/configs/collections/10139.a2a-protocol.yml new file mode 100644 index 0000000000..fdc1566cf4 --- /dev/null +++ b/configs/collections/10139.a2a-protocol.yml @@ -0,0 +1,13 @@ +id: 10139 +name: A2A Protocol +items: + - a2aproject/A2A + - google/adk-python + - google/adk-java + - agent-network-protocol/AgentNetworkProtocol + - themanojdesai/python-a2a + - Coral-Protocol/Anemoi + - neuroglia-io/a2a-net + - cpp-agan-team/a2a-cpp-sdk + - RubensZimbres/Multi-Agent-System-A2A-ADK-MCP + - isekOS/awesome-a2a-agents diff --git a/configs/collections/10140.google-adk-python.yml b/configs/collections/10140.google-adk-python.yml new file mode 100644 index 0000000000..a24c624fc4 --- /dev/null +++ b/configs/collections/10140.google-adk-python.yml @@ -0,0 +1,15 @@ +id: 10140 +name: Google ADK Python +items: + - google/adk-python + - google/adk-web + - google/adk-java + - a2aproject/A2A + - Sri-Krishna-V/awesome-adk-agents + - raphaelmansuy/adk_training + - arjunprabhulal/adk-vertex-ai-rag-engine + - sokart/adk-walkthrough + - RashRAJ/awesome-adk + - tsubasakong/awesome-google-adk + - ksmin23/my-adk-python-samples + - mongodb-developer/MongoDB-ADK-Agents diff --git a/configs/collections/10141.agent-harness.yml b/configs/collections/10141.agent-harness.yml new file mode 100644 index 0000000000..8aac4879d5 --- /dev/null +++ b/configs/collections/10141.agent-harness.yml @@ -0,0 +1,15 @@ +id: 10141 +name: Agent Harness +items: + - microsoft/autogen + - crewAIInc/crewAI + - agno-agi/agno + - letta-ai/letta + - pipecat-ai/pipecat + - microsoft/TaskWeaver + - microsoft/promptflow + - run-llama/llama_deploy + - SWE-agent/SWE-agent + - THUDM/AgentBench + - AgentOps-AI/agentops + - openai/evals