feat(benchmark): dashboard re-run banner prompting a Score v2 re-run
Shows a dismissible dashboard banner to users who submitted a benchmark under v1 scoring but don't yet have a Score v2 result, nudging them to re-run so the community leaderboard gets their v2 number. - Show logic (benchmark_service.shouldShowRerunBanner): has a result with submitted_to_repository=true AND no result with nomad_score_v2 AND the dismiss KV isn't set. Self-clears two ways — dismiss sets the KV, and any v2 run gives a result a nomad_score_v2 so the condition flips off on its own. - New GET /api/benchmark/rerun-banner + useBenchmarkRerunBanner hook (mirrors useUpdateAvailable), rendered as a dismissible Alert on the dashboard with a "Re-run benchmark" CTA to /settings/benchmark. - New KV key benchmark.rerunBannerDismissed (boolean) in both KV_STORE_SCHEMA and the SETTINGS_KEYS whitelist (the PATCH /system/settings validator enum); dismiss writes it via the existing updateSetting endpoint + invalidates the query. Sibling to the Score v2 app client (#1094); stacked on it for the nomad_score_v2 column. Browser-verified on the NOMAD3 dev env: show/dismiss/ reload-persist/self-clear-on-v2 all correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
651df80dc8
commit
dbc49e92d4
|
|
@ -248,6 +248,13 @@ export default class BenchmarkController {
|
|||
return this.benchmarkService.getStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show the dashboard "re-run under Score v2" banner
|
||||
*/
|
||||
async rerunBanner({}: HttpContext) {
|
||||
return { show: await this.benchmarkService.shouldShowRerunBanner() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get benchmark settings
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -192,6 +192,25 @@ export class BenchmarkService {
|
|||
return await BenchmarkResult.findBy('benchmark_id', benchmarkId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show the dashboard "re-run under Score v2" banner. Shown to users
|
||||
* who have a v1 leaderboard submission but no Score v2 result yet, and who
|
||||
* haven't dismissed it. Self-clears two ways: dismiss sets the KV flag, and any
|
||||
* result carrying a nomad_score_v2 (i.e. a v2 run happened) flips (b) false.
|
||||
*/
|
||||
async shouldShowRerunBanner(): Promise<boolean> {
|
||||
const dismissed = await KVStore.getValue('benchmark.rerunBannerDismissed')
|
||||
if (dismissed === true) return false
|
||||
|
||||
const hasV2Run = await BenchmarkResult.query().whereNotNull('nomad_score_v2').first()
|
||||
if (hasV2Run) return false
|
||||
|
||||
const hasSubmittedV1 = await BenchmarkResult.query()
|
||||
.where('submitted_to_repository', true)
|
||||
.first()
|
||||
return hasSubmittedV1 !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit benchmark results to central repository
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import { KVStoreKey } from "../types/kv_store.js";
|
||||
|
||||
export const SETTINGS_KEYS: KVStoreKey[] = ['chat.suggestionsEnabled', 'chat.lastModel', 'ui.hasVisitedEasySetup', 'ui.theme', 'system.earlyAccess', 'system.internetStatusTestUrl', 'ai.assistantCustomName', 'ai.remoteOllamaUrl', 'ai.ollamaFlashAttention', 'rag.defaultIngestPolicy', 'autoUpdate.enabled', 'autoUpdate.windowStart', 'autoUpdate.windowEnd', 'autoUpdate.cooloffHours', 'appAutoUpdate.enabled', 'contentAutoUpdate.enabled', 'contentAutoUpdate.windowStart', 'contentAutoUpdate.windowEnd', 'contentAutoUpdate.cooloffHours', 'contentAutoUpdate.maxBytesPerWindow'];
|
||||
export const SETTINGS_KEYS: KVStoreKey[] = ['chat.suggestionsEnabled', 'chat.lastModel', 'ui.hasVisitedEasySetup', 'ui.theme', 'system.earlyAccess', 'system.internetStatusTestUrl', 'ai.assistantCustomName', 'ai.remoteOllamaUrl', 'ai.ollamaFlashAttention', 'rag.defaultIngestPolicy', 'autoUpdate.enabled', 'autoUpdate.windowStart', 'autoUpdate.windowEnd', 'autoUpdate.cooloffHours', 'appAutoUpdate.enabled', 'contentAutoUpdate.enabled', 'contentAutoUpdate.windowStart', 'contentAutoUpdate.windowEnd', 'contentAutoUpdate.cooloffHours', 'contentAutoUpdate.maxBytesPerWindow', 'benchmark.rerunBannerDismissed'];
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import api from "~/lib/api"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
|
||||
export const BENCHMARK_RERUN_BANNER_QUERY_KEY = ['benchmark-rerun-banner']
|
||||
|
||||
export const useBenchmarkRerunBanner = () => {
|
||||
const queryData = useQuery<{ show: boolean } | undefined>({
|
||||
queryKey: BENCHMARK_RERUN_BANNER_QUERY_KEY,
|
||||
queryFn: () => api.checkBenchmarkRerunBanner(),
|
||||
refetchInterval: Infinity, // Disable automatic refetching
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
return queryData.data
|
||||
}
|
||||
|
|
@ -374,6 +374,13 @@ class API {
|
|||
})()
|
||||
}
|
||||
|
||||
async checkBenchmarkRerunBanner() {
|
||||
return catchInternal(async () => {
|
||||
const response = await this.client.get<{ show: boolean }>('/benchmark/rerun-banner')
|
||||
return response.data
|
||||
})()
|
||||
}
|
||||
|
||||
async getChatSessions() {
|
||||
return catchInternal(async () => {
|
||||
const response = await this.client.get<
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ import { ServiceSlim } from '../../types/services'
|
|||
import DynamicIcon, { DynamicIconName } from '~/components/DynamicIcon'
|
||||
import { useUpdateAvailable } from '~/hooks/useUpdateAvailable'
|
||||
import { useSystemSetting } from '~/hooks/useSystemSetting'
|
||||
import {
|
||||
useBenchmarkRerunBanner,
|
||||
BENCHMARK_RERUN_BANNER_QUERY_KEY,
|
||||
} from '~/hooks/useBenchmarkRerunBanner'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import api from '~/lib/api'
|
||||
import Alert from '~/components/Alert'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||
|
||||
|
|
@ -91,8 +97,15 @@ export default function Home(props: {
|
|||
}) {
|
||||
const items: DashboardItem[] = []
|
||||
const updateInfo = useUpdateAvailable();
|
||||
const rerunBanner = useBenchmarkRerunBanner()
|
||||
const queryClient = useQueryClient()
|
||||
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
||||
|
||||
const handleDismissRerunBanner = async () => {
|
||||
await api.updateSetting('benchmark.rerunBannerDismissed', true)
|
||||
queryClient.invalidateQueries({ queryKey: BENCHMARK_RERUN_BANNER_QUERY_KEY })
|
||||
}
|
||||
|
||||
// Check if user has visited Easy Setup
|
||||
const { data: easySetupVisited } = useSystemSetting({
|
||||
key: 'ui.hasVisitedEasySetup'
|
||||
|
|
@ -155,6 +168,27 @@ export default function Home(props: {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
rerunBanner?.show && (
|
||||
<div className='flex justify-center items-center px-4 pt-4 w-full'>
|
||||
<Alert
|
||||
title="Your benchmark can be re-scored with Score v2"
|
||||
message="We've upgraded the benchmark scoring system. Re-run your benchmark to get an updated Score v2 result on the community leaderboard."
|
||||
type="info-inverted"
|
||||
variant="solid"
|
||||
className="w-full"
|
||||
dismissible
|
||||
onDismiss={handleDismissRerunBanner}
|
||||
buttonProps={{
|
||||
variant: 'primary',
|
||||
children: 'Re-run benchmark',
|
||||
icon: 'IconRefresh',
|
||||
onClick: () => router.visit('/settings/benchmark'),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
||||
{items.map((item) => {
|
||||
const isEasySetup = item.label === 'Easy Setup'
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ router
|
|||
router.post('/builder-tag', [BenchmarkController, 'updateBuilderTag'])
|
||||
router.get('/comparison', [BenchmarkController, 'comparison'])
|
||||
router.get('/status', [BenchmarkController, 'status'])
|
||||
router.get('/rerun-banner', [BenchmarkController, 'rerunBanner'])
|
||||
router.get('/settings', [BenchmarkController, 'settings'])
|
||||
router.post('/settings', [BenchmarkController, 'updateSettings'])
|
||||
})
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export const KV_STORE_SCHEMA = {
|
|||
'ai.amdHsaOverride': 'string',
|
||||
'ai.autoFixGpuPassthrough': 'boolean',
|
||||
'gpu.autoRemediatedAt': 'string',
|
||||
'benchmark.rerunBannerDismissed': 'boolean',
|
||||
} as const
|
||||
|
||||
type KVTagToType<T extends string> = T extends 'boolean' ? boolean : string
|
||||
|
|
|
|||
Loading…
Reference in New Issue