diff --git a/src/app.tsx b/src/app.tsx
index 5d9ba17..cb1c863 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,12 +1,13 @@
import { Byline } from '@kevinschaich/components'
-import { BENCH, MODELS } from '@site/data'
+import { BENCH, MODELS, TABS } from '@site/data'
import { ModelPage } from '@site/pages/model'
-import { Overview } from '@site/pages/overview'
+import { Overview, tabFromPath } from '@site/pages/overview'
-/** The page for a path under BENCH.path: the overview, or one model's page. */
+/** The page for a path under BENCH.path: an overview tab, or one model's page. */
export function App({ path }: { path: string }) {
const id = path.replace(BENCH.path, '').replace(/\/$/, '')
- const model = id ? MODELS.find(candidate => candidate.id === id) : undefined
+ const tab = tabFromPath(path)
+ const model = tab ? undefined : id ? MODELS.find(candidate => candidate.id === id) : undefined
return (
@@ -18,7 +19,7 @@ export function App({ path }: { path: string }) {
- {model ? : }
+ {model ? : }
)
}
diff --git a/src/data.ts b/src/data.ts
index dc1656b..03e9016 100644
--- a/src/data.ts
+++ b/src/data.ts
@@ -14,6 +14,14 @@ export const BENCH = {
origin: 'https://darkgrade.com',
}
+/** The overview's tabs; each after the first is its own page at BENCH.path + id. */
+export const TABS = [
+ { id: 'results', label: 'Results' },
+ { id: 'tradeoff', label: 'Accuracy vs. speed' },
+ { id: 'method', label: 'How we test' },
+ { id: 'script', label: 'The script' },
+]
+
export interface Reason {
area: string
weight: number
@@ -162,5 +170,8 @@ export function routes(): Route[] {
},
})
)
- return [overview, ...models]
+ const tabs = TABS.slice(1).map(
+ (tab): Route => ({ ...overview, path: `${tab.id}/`, title: `${tab.label} | ${BENCH.name} by Darkgrade` })
+ )
+ return [overview, ...tabs, ...models]
}
diff --git a/src/pages/overview.tsx b/src/pages/overview.tsx
index 2325417..4f93242 100644
--- a/src/pages/overview.tsx
+++ b/src/pages/overview.tsx
@@ -12,18 +12,11 @@ import {
TabsTrigger,
TradeoffChart,
} from '@kevinschaich/components'
-import { BENCH, FILTERS, MODELS, type ModelSummary, RUN, percent, sampleSentence, seconds } from '@site/data'
+import { BENCH, FILTERS, TABS, MODELS, type ModelSummary, RUN, percent, sampleSentence, seconds } from '@site/data'
import { Method } from '@site/pages/method'
import { Script } from '@site/pages/script'
import { useEffect, useState } from 'react'
-const TABS = [
- { id: 'results', label: 'Results' },
- { id: 'tradeoff', label: 'Accuracy vs. speed' },
- { id: 'method', label: 'How we test' },
- { id: 'script', label: 'The script' },
-]
-
const COLUMNS: LeaderboardColumn[] = [
{
key: 'model',
@@ -52,12 +45,15 @@ const COLUMNS: LeaderboardColumn[] = [
},
]
-function tabFromHash(): string {
- if (typeof location === 'undefined') return TABS[0].id
- const id = location.hash.replace(/^#/, '')
- return TABS.some(tab => tab.id === id) ? id : TABS[0].id
+/** The tab a path under BENCH.path names; the first tab is the bench's root. */
+export function tabFromPath(path: string): string | undefined {
+ const id = path.replace(BENCH.path, '').replace(/\/$/, '')
+ if (!id) return TABS[0].id
+ return TABS.some(tab => tab.id === id) ? id : undefined
}
+const tabPath = (id: string): string => (id === TABS[0].id ? BENCH.path : `${BENCH.path}${id}/`)
+
/** One row of filters per category, each a one-of-a-few choice. */
function Filters({ chosen, choose }: { chosen: Record; choose: (filter: string, option: string) => void }) {
return (
@@ -77,20 +73,24 @@ function Filters({ chosen, choose }: { chosen: Record; choose: (
)
}
-export function Overview() {
- const [tab, setTab] = useState(TABS[0].id)
+export function Overview({ initialTab }: { initialTab: string }) {
+ const [tab, setTab] = useState(initialTab)
const [chosen, setChosen] = useState>(Object.fromEntries(FILTERS.map(filter => [filter.id, 'all'])))
const shown = MODELS.filter(model => FILTERS.every(filter => filter.options.find(option => option.id === chosen[filter.id])!.matches(model)))
const choose = (filter: string, option: string) => setChosen(previous => ({ ...previous, [filter]: option }))
useEffect(() => {
- setTab(tabFromHash())
- const onHash = () => setTab(tabFromHash())
- window.addEventListener('hashchange', onHash)
- return () => window.removeEventListener('hashchange', onHash)
+ const legacy = location.hash.replace(/^#/, '')
+ if (TABS.some(item => item.id === legacy)) {
+ history.replaceState(null, '', tabPath(legacy))
+ setTab(legacy)
+ }
+ const onPop = () => setTab(tabFromPath(location.pathname) ?? TABS[0].id)
+ window.addEventListener('popstate', onPop)
+ return () => window.removeEventListener('popstate', onPop)
}, [])
const select = (id: string) => {
setTab(id)
- history.replaceState(null, '', `#${id}`)
+ history.pushState(null, '', tabPath(id))
}
return (
diff --git a/vercel.json b/vercel.json
index 85463ac..c3305d2 100644
--- a/vercel.json
+++ b/vercel.json
@@ -35,5 +35,6 @@
"destination": "/toolbench/",
"permanent": false
}
- ]
+ ],
+ "trailingSlash": true
}