diff --git a/README.md b/README.md index 01e5ed1..7fb191b 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,12 @@ echo "VITE_FTW_INFERENCE_OUTPUT_URL=http://127.0.0.1:3000/" > .env.development The app will be available at `http://localhost:5173` +### Benchmark page + +Open [http://localhost:5173/benchmark](http://localhost:5173/benchmark) when your API supports FTW benchmark runs. Pick models and benchmark countries, then review scores and (optionally) the map: **gold** dashed outline = chip footprint, **green** = ground truth fields, **blue** = model predictions. + +![Benchmark map: Corsica test chip — ground truth vs predictions](docs/images/benchmark-map-output.png) + ## Alternative API Setup If you prefer not to use conda, you can install the API manually: diff --git a/docs/images/benchmark-map-output.png b/docs/images/benchmark-map-output.png new file mode 100644 index 0000000..0dd931e Binary files /dev/null and b/docs/images/benchmark-map-output.png differ diff --git a/src/components/BenchmarkMap.vue b/src/components/BenchmarkMap.vue new file mode 100644 index 0000000..31c4df2 --- /dev/null +++ b/src/components/BenchmarkMap.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/src/composables/useBenchmarkCatalog.ts b/src/composables/useBenchmarkCatalog.ts new file mode 100644 index 0000000..f7fedce --- /dev/null +++ b/src/composables/useBenchmarkCatalog.ts @@ -0,0 +1,64 @@ +import { ref, shallowRef } from 'vue' +import { generateJWT } from '../functions/generate-jwt' + +const base = import.meta.env.VITE_API_BASE_URL ?? '' + +export interface BenchmarkCountry { + id: string + title: string + year: number + chips: number + train: number + validation: number + test: number + license: string +} + +export interface ModelRow { + id: string + title: string + description?: string +} + +const countries = shallowRef([]) +const models = shallowRef([]) +const loading = ref(false) +const error = ref(null) + +export async function loadBenchmarkCatalog(): Promise { + loading.value = true + error.value = null + try { + const headers = { + Authorization: `Bearer ${generateJWT()}`, + } + const [cRes, mRes] = await Promise.all([ + fetch(`${base}benchmarks/countries`, { headers }), + fetch(`${base}models`, { headers }), + ]) + if (!cRes.ok) { + throw new Error((await cRes.json()).detail || 'Failed to load benchmarks') + } + if (!mRes.ok) { + throw new Error((await mRes.json()).detail || 'Failed to load models') + } + const cJson = await cRes.json() + const mJson = await mRes.json() + countries.value = cJson.countries || [] + models.value = mJson.models || [] + } catch (e) { + error.value = e instanceof Error ? e.message : String(e) + } finally { + loading.value = false + } +} + +export function useBenchmarkCatalog() { + return { + countries, + models, + loading, + error, + loadBenchmarkCatalog, + } +} diff --git a/src/router/index.ts b/src/router/index.ts index d264def..88822dc 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -8,6 +8,11 @@ const router = createRouter({ name: 'map', component: () => import('../views/MapView.vue'), }, + { + path: '/benchmark', + name: 'benchmark', + component: () => import('../views/BenchmarkView.vue'), + }, ], }) diff --git a/src/views/BenchmarkView.vue b/src/views/BenchmarkView.vue new file mode 100644 index 0000000..33f0691 --- /dev/null +++ b/src/views/BenchmarkView.vue @@ -0,0 +1,571 @@ + + + + + diff --git a/src/views/MapView.vue b/src/views/MapView.vue index 01dbb2e..3ac46b6 100644 --- a/src/views/MapView.vue +++ b/src/views/MapView.vue @@ -2,6 +2,7 @@ import MapComponent from '../components/MapComponent.vue' import { mdiInformation } from '@mdi/js' import { ref, watch } from 'vue' +import { RouterLink } from 'vue-router' import useSettings from '../composables/useSettings' const { settings, availableModes } = useSettings() @@ -53,6 +54,7 @@ function setModeValue(newValue: number) { + Benchmark @@ -88,7 +90,20 @@ function setModeValue(newValue: number) { border-radius: 0 0 1rem 1rem; font-size: 1.33rem; font-weight: 600; - text-align: center; + white-space: nowrap; + display: flex; + align-items: center; + gap: 0.75rem; +} + +#benchmark-link { + font-size: 0.95rem; + font-weight: 500; + color: #90caf9; + text-decoration: none; +} +#benchmark-link:hover { + text-decoration: underline; } #title .logo { diff --git a/vite.config.ts b/vite.config.ts index 8bea5c1..708e13c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,12 +5,19 @@ import vuetify from 'vite-plugin-vuetify' import vueDevTools from 'vite-plugin-vue-devtools' // https://vitejs.dev/config/ -export default defineConfig(({ mode }) => { +export default defineConfig(({ mode, command, isPreview }) => { // Load env file based on `mode` in the current directory. // Set the third parameter to '' to load all env regardless of the `VITE_` prefix. const env = loadEnv(mode, process.cwd(), '') + // Dev server must use base "/" or http://localhost:5173/ returns 404. Do not rely on + // `mode === 'development'` (e.g. `vite --mode production` still uses serve command). + // Build + `vite preview` keep the GitHub Pages subpath; `isPreview` is true for preview only. + const base = + command === 'serve' && !isPreview ? '/' : '/ftw-inference-app/' + return { + base, plugins: [ vue(), vuetify({ @@ -25,7 +32,6 @@ export default defineConfig(({ mode }) => { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, - base: '/ftw-inference-app/', build: { sourcemap: true, outDir: 'ftw-inference-app', @@ -36,7 +42,9 @@ export default defineConfig(({ mode }) => { }, server: { port: 5173, - host: true, + // Bind IPv4 (0.0.0.0) so http://127.0.0.1:5173 works; `host: true` can be IPv6-only on some Windows setups and break "localhost" in the browser. + host: '0.0.0.0', + strictPort: true, proxy: { '/api': { target: env.VITE_API_BASE_URL,