diff --git a/.gemini /config.yaml b/.gemini /config.yaml new file mode 100644 index 0000000..110d0f5 --- /dev/null +++ b/.gemini /config.yaml @@ -0,0 +1,11 @@ +# Referred to SkyRL +have_fun: true # the bot sometimes adds lighthearted / fun comments. +code_review: + disable: false + comment_severity_threshold: MEDIUM + max_review_comments: -1 + pull_request_opened: + help: false + summary: false # enable PR summaries + code_review: false +ignore_patterns: [] diff --git a/.github/workflows/precommit.yml b/.github/workflows/precommit.yml new file mode 100644 index 0000000..c450db6 --- /dev/null +++ b/.github/workflows/precommit.yml @@ -0,0 +1,12 @@ +name: pre-commit +on: [push, pull_request] +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - run: pip install pre-commit + - run: pre-commit run --all-files diff --git a/.gitignore b/.gitignore index 4d29575..e016210 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ .env.development.local .env.test.local .env.production.local +.venv*/* npm-debug.log* yarn-debug.log* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6c98982 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +repos: + # --- Basic hygiene --- + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + exclude: '\\.svg$' + exclude_types: [svg] + - id: end-of-file-fixer + exclude: '\\.svg$' + exclude_types: [svg] + - id: check-json + - id: check-yaml + - id: check-added-large-files + args: ["--maxkb=2048"] + - id: check-merge-conflict + - id: check-case-conflict + - id: detect-private-key + - id: mixed-line-ending + + # --- JS/HTML formatting and linting --- + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + args: [--check] + additional_dependencies: [prettier@3.2.5] + files: '\\.(js|jsx|ts|tsx|json|css|scss|md|markdown|yml|yaml|html)$' + + # --- Optional: spell check or link check --- + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + args: ["--ignore-words-list=teh,fo"] + exclude: '^package-lock\.json$' diff --git a/README.md b/README.md index cb9acef..4fd8dc0 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ If you use RouterArena in your research, please cite our paper: ```bibtex @misc{lu2024routerarena, - title={RouterArena: An Open Platform for Comprehensive Comparison of LLM Routers}, + title={RouterArena: An Open Platform for Comprehensive Comparison of LLM Routers}, author={Yifan Lu and Rixin Liu and Jiayi Yuan and Xingqi Cui and Shenrun Zhang and Hongyi Liu and Jiarong Xing}, year={2024}, eprint={2510.00202}, @@ -172,4 +172,4 @@ If you use RouterArena in your research, please cite our paper: ## Acknowledgments -We thank all contributors who have made this project possible. \ No newline at end of file +We thank all contributors who have made this project possible. diff --git a/src/App.css b/src/App.css index 212c4fa..52e4f3e 100644 --- a/src/App.css +++ b/src/App.css @@ -91,9 +91,9 @@ code { .container { padding: 0 0.75rem; } - + .btn { padding: 0.625rem 1.25rem; font-size: 0.9rem; } -} \ No newline at end of file +} diff --git a/src/components/DatasetCompositionChart.tsx b/src/components/DatasetCompositionChart.tsx index eef4d0e..ee4332e 100644 --- a/src/components/DatasetCompositionChart.tsx +++ b/src/components/DatasetCompositionChart.tsx @@ -88,7 +88,7 @@ const categoryColors = [ // Difficulty level colors const difficultyColors = { 'Easy': '#22C55E', - 'Medium': '#F59E0B', + 'Medium': '#F59E0B', 'Hard': '#EF4444' }; @@ -166,7 +166,7 @@ const DatasetCompositionChart: React.FC = () => { const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0].payload; - + // Get subcategory descriptions const getSubcategoryDescription = (name: string, parent: string) => { const descriptions: { [key: string]: { [key: string]: string } } = { @@ -217,10 +217,10 @@ const DatasetCompositionChart: React.FC = () => { 'Biography and genealogy': 'Personal histories & lineages' } }; - + return descriptions[parent]?.[name] || name; }; - + return (

{data.name}

@@ -257,19 +257,19 @@ const DatasetCompositionChart: React.FC = () => { onMouseLeave={() => setHoveredSubcategory(null)} > {subcategoryData.map((entry, index) => ( - ))} - + {/* Outer ring - Main categories */} { onMouseLeave={() => setHoveredCategory(null)} > {mainChartData.map((entry, index) => ( - ))} - } + } wrapperStyle={{ zIndex: 9999 }} /> - + {/* Independent SVG Labels Overlay */} - { {mainChartData.map((entry, index) => { const totalValue = mainChartData.reduce((sum, item) => sum + item.value, 0); let cumulativeValue = 0; - + for (let i = 0; i < index; i++) { cumulativeValue += mainChartData[i].value; } - + const startAngle = (cumulativeValue / totalValue) * 360 - 90; const endAngle = ((cumulativeValue + entry.value) / totalValue) * 360 - 90; const midAngle = (startAngle + endAngle) / 2; - + const RADIAN = Math.PI / 180; // Adaptive label radius: farther for sides, closer for top/bottom const baseOffset = 60; // baseline distance beyond pie edge const sideBoost = 40; // extra distance for left/right labels - const angleFactor = Math.abs(Math.sin(midAngle * RADIAN)); + const angleFactor = Math.abs(Math.sin(midAngle * RADIAN)); // 0 for top/bottom, 1 for sides const radius = CHART_DIMENSIONS.outerRadius + baseOffset + sideBoost * angleFactor; - + // Use exact center coordinates matching Recharts const centerX = 400; // Exact center of 800x800 viewBox const centerY = 400; // Exact center of 800x800 viewBox const x = centerX - radius * Math.sin(midAngle * RADIAN); const y = centerY - radius * Math.cos(midAngle * RADIAN); - + // Create shortened versions of category names const getShortName = (name: string) => { const shortNames: { [key: string]: string } = { @@ -361,9 +361,9 @@ const DatasetCompositionChart: React.FC = () => { }; return shortNames[name] || name; }; - + const shortName = getShortName(entry.name); - + return ( = ({ academicPoints, commercia const allPoints = [...Object.values(academicPoints), ...Object.values(commercialPoints)]; const accuracyValues = allPoints.map(p => p.accuracy); const costValues = allPoints.map(p => p.cost_per_1k); - + const minAccuracy = Math.min(...accuracyValues); const maxAccuracy = Math.max(...accuracyValues); const minCost = Math.min(...costValues); const maxCost = Math.max(...costValues); - + // Add some padding to the ranges, but ensure oracle accuracy (0.9089) is visible const accuracyRange = maxAccuracy - minAccuracy; const costRange = maxCost - minCost; const accuracyMin = Math.max(0, minAccuracy - accuracyRange * 0.1); const accuracyMax = Math.max(0.95, maxAccuracy + accuracyRange * 0.1); // Ensure oracle accuracy is visible - + // Zoom in on X-axis to spread out the data points better const costMin = Math.max(0.01, minCost * 0.5); // Start closer to minimum const costMax = maxCost * 1.2; // Extend a bit beyond maximum - + // Chart dimensions const chartWidth = 500; const chartHeight = 400; const margin = { top: 20, right: 20, bottom: 60, left: 80 }; const plotWidth = chartWidth - margin.left - margin.right; const plotHeight = chartHeight - margin.top - margin.bottom; - + // Scale functions const scaleX = (cost: number) => { const logMin = Math.log10(costMin); @@ -41,11 +41,11 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia const logValue = Math.log10(cost); return margin.left + ((logValue - logMin) / (logMax - logMin)) * plotWidth; }; - + const scaleY = (accuracy: number) => { return margin.top + (1 - (accuracy - accuracyMin) / (accuracyMax - accuracyMin)) * plotHeight; }; - + // Router colors and shapes const routerColors = [ '#3b82f6', // Blue @@ -150,13 +150,13 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia ); } }; - + // Generate powers of 10 for x-axis (0.1, 1, 10, etc.) const generatePowersOf10 = (min: number, max: number) => { const ticks = []; const minLog = Math.floor(Math.log10(min)); const maxLog = Math.ceil(Math.log10(max)); - + for (let i = minLog; i <= maxLog; i++) { const baseValue = Math.pow(10, i); if (baseValue >= min && baseValue <= max) { @@ -165,10 +165,10 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia } return ticks; }; - + const costTicks = generatePowersOf10(costMin, costMax); - - + + return (
@@ -188,7 +188,7 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia /> ); })} - + {[0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9].map(tick => { if (tick >= accuracyMin && tick <= accuracyMax) { const y = scaleY(tick); @@ -206,25 +206,25 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia } return null; })} - + {/* Academic routers */} {Object.entries(academicPoints).map(([name, point], index) => { const x = scaleX(point.cost_per_1k); const y = scaleY(point.accuracy); const color = routerColors[index % routerColors.length]; - + return renderShape(x, y, 'circle', color, `academic-${name}`); })} - + {/* Commercial routers */} {Object.entries(commercialPoints).map(([name, point], index) => { const x = scaleX(point.cost_per_1k); const y = scaleY(point.accuracy); const color = routerColors[(index + Object.keys(academicPoints).length) % routerColors.length]; - + return renderShape(x, y, 'triangle', color, `commercial-${name}`); })} - + {/* Oracle accuracy line */} = ({ academicPoints, commercia > Oracle Accuracy - + {/* Axes */} = ({ academicPoints, commercia stroke="#374151" strokeWidth="2" /> - + {/* X-axis labels - powers of 10 only */} {costTicks.map(tick => { const x = scaleX(tick); @@ -289,7 +289,7 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia ); })} - + {/* Y-axis labels */} {[0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9].map(tick => { if (tick >= accuracyMin && tick <= accuracyMax) { @@ -318,7 +318,7 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia } return null; })} - + {/* Axis titles */} = ({ academicPoints, commercia Accuracy - + {/* Legend */}
@@ -361,7 +361,7 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia })}
- +

Commercial

@@ -385,4 +385,3 @@ const DeferralCurve: React.FC = ({ academicPoints, commercia }; export default DeferralCurve; - diff --git a/src/components/Figure.tsx b/src/components/Figure.tsx index f679f93..ef3c26c 100644 --- a/src/components/Figure.tsx +++ b/src/components/Figure.tsx @@ -20,10 +20,10 @@ const Figure: React.FC = ({ src, alt, caption, className = '' }) => return (
- {alt} diff --git a/src/components/Header.css b/src/components/Header.css index 87627ac..d95643d 100644 --- a/src/components/Header.css +++ b/src/components/Header.css @@ -126,11 +126,11 @@ .nav-desktop { display: none; } - + .menu-button { display: block; } - + .header-container { padding: 0 1rem; } diff --git a/src/components/SpiderChart.css b/src/components/SpiderChart.css index 96b3777..a75cb44 100644 --- a/src/components/SpiderChart.css +++ b/src/components/SpiderChart.css @@ -81,15 +81,15 @@ .spider-chart-container { padding: 1rem; } - + .spider-legend { gap: 1rem; } - + .legend-item { padding: 0.4rem 0.8rem; } - + .legend-label { font-size: 0.8rem; } diff --git a/src/components/SpiderChart.tsx b/src/components/SpiderChart.tsx index 7c4be2c..05af0b5 100644 --- a/src/components/SpiderChart.tsx +++ b/src/components/SpiderChart.tsx @@ -53,19 +53,19 @@ const SpiderChart: React.FC = ({ routers, maxRouters = 5 }) => }; // Calculate adaptive axis scaling to show more variation - const allValues = topRouters.flatMap(router => + const allValues = topRouters.flatMap(router => metrics.map(metric => router.metrics[metric.key]) ); const minValue = Math.min(...allValues); const maxValue = Math.max(...allValues); const valueRange = maxValue - minValue; - + // Set axis range to show more variation // If values are clustered (e.g., 0.8-0.9), show axis with 25% padding above and below const axisMin = Math.max(0, minValue - valueRange * 0.25); const axisMax = Math.min(1, maxValue + valueRange * 0.25); const axisRange = axisMax - axisMin; - + const chartRadius = 180; const centerX = 225; const centerY = 225; @@ -113,7 +113,7 @@ const SpiderChart: React.FC = ({ routers, maxRouters = 5 }) => const angle = (index * 2 * Math.PI) / metrics.length - Math.PI / 2; const endX = centerX + Math.cos(angle) * chartRadius; const endY = centerY + Math.sin(angle) * chartRadius; - + return ( = ({ routers, maxRouters = 5 }) => const angle = (index * 2 * Math.PI) / metrics.length - Math.PI / 2; const labelX = centerX + Math.cos(angle) * (chartRadius + 40); const labelY = centerY + Math.sin(angle) * (chartRadius + 40); - + return ( = ({ routers, maxRouters = 5 }) => {topRouters.map((router, routerIndex) => { const path = getRouterPath(router, chartRadius); const color = routerColors[routerIndex % routerColors.length]; - + return ( {/* Fill area */} @@ -176,7 +176,7 @@ const SpiderChart: React.FC = ({ routers, maxRouters = 5 }) => {metrics.map((metric, metricIndex) => { const value = router.metrics[metric.key]; const pos = getMetricPosition(metricIndex, value, chartRadius); - + return ( = ({ routers, maxRouters = 5 }) => const color = routerColors[index % routerColors.length]; return (
-
{router.name} diff --git a/src/pages/HomePage.css b/src/pages/HomePage.css index 58fd08b..88db693 100644 --- a/src/pages/HomePage.css +++ b/src/pages/HomePage.css @@ -625,15 +625,15 @@ gap: 2rem; text-align: center; } - + .hero-title { font-size: 2rem; } - + .hero-actions { justify-content: center; } - + .action-buttons { flex-direction: column; align-items: center; @@ -647,32 +647,32 @@ grid-template-columns: repeat(2, 1fr); gap: 0.75rem; } - + .dataset-stats { grid-template-columns: repeat(2, 1fr); } - + .difficulty-grid { grid-template-columns: 1fr; } - + .domain-grid { grid-template-columns: repeat(2, 1fr); } - + .difficulty-bars { max-width: 100%; } - - + + .cta-card { max-width: 300px; } - + .cta-heading { font-size: 1.1rem; } - + .cta-description { font-size: 1.1rem; } diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index b000475..9cea765 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -62,7 +62,7 @@ const HomePage: React.FC = () => { ))}
- +

Want to see your router on the leaderboard?

@@ -82,31 +82,31 @@ const HomePage: React.FC = () => {

Key Features

- + {/* Tab Navigation */}
- - - - -
)} - + {activeTab === 'deferral' && (
-
)} diff --git a/src/pages/SubmitPromptPage.css b/src/pages/SubmitPromptPage.css index 017c4b9..9d7708e 100644 --- a/src/pages/SubmitPromptPage.css +++ b/src/pages/SubmitPromptPage.css @@ -266,11 +266,11 @@ grid-template-columns: 1fr; gap: 2rem; } - + .page-title { font-size: 2rem; } - + .submit-info, .submit-form-container { padding: 1.5rem; diff --git a/src/pages/SubmitPromptPage.tsx b/src/pages/SubmitPromptPage.tsx index fbf8820..d1b62e3 100644 --- a/src/pages/SubmitPromptPage.tsx +++ b/src/pages/SubmitPromptPage.tsx @@ -28,7 +28,7 @@ const SubmitPromptPage: React.FC = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); - + // Simulate API call try { await new Promise(resolve => setTimeout(resolve, 2000)); @@ -136,7 +136,7 @@ const SubmitPromptPage: React.FC = () => {

Router Submission Form

- +