diff --git a/packages/app/cypress/e2e/inference-chart.cy.ts b/packages/app/cypress/e2e/inference-chart.cy.ts index 9ad7e9b61..5fa283b07 100644 --- a/packages/app/cypress/e2e/inference-chart.cy.ts +++ b/packages/app/cypress/e2e/inference-chart.cy.ts @@ -14,6 +14,7 @@ import { } from '../support/e2e'; import { interceptOverlayRun, + AGENTIC_DATE, OVERLAY_RUN_ID, OVERLAY_RUN_URL, SINGLE_TURN_DATE, @@ -154,6 +155,7 @@ describe('Inference Chart', () => { .and('not.contain.text', 'Workload:') .and('not.contain.text', 'Precision:') .and('not.contain.text', 'Metric:'); + cy.get('[data-testid="deprecated-watermark"]').should('not.exist'); }); it('plots OpenRouter-priced token revenue for official and unofficial runs', () => { @@ -388,6 +390,125 @@ describe('Inference Chart', () => { }); }); +describe('Deprecated model background watermark', () => { + it('marks every deprecated model and clears the mark for an active model', () => { + cy.viewport(1280, 900); + for (const [display, dbKey, deprecated] of [ + ['MiniMax-M3', 'minimaxm3', true], + ['MiniMax-M2.5', 'minimaxm2.5', true], + ['Kimi-K2.5', 'kimik2.5', true], + ['GLM-5', 'glm5', true], + ['gpt-oss-120b', 'gptoss120b', true], + ['Llama-3.3-70B-Instruct-FP8', 'llama70b', true], + ['MiniMax-M3.1-Flash', 'minimaxm31flash', false], + ] as const) { + const rows = b300Rows(null).map((row) => ({ ...row, model: dbKey })); + cy.intercept('GET', '/api/v1/availability', { + body: rows.map(({ metrics: _metrics, ...row }) => row), + }); + cy.intercept('GET', '/api/v1/benchmarks*', { body: rows }); + cy.visit(`/inference?g_model=${display}&i_seq=agentic-traces`, { + onBeforeLoad: unlockAgenticGate, + }); + cy.get('#chart-0 svg[data-testid="d3-chart-svg"]').should('be.visible'); + if (deprecated) { + cy.get('#chart-0 .deprecated-watermark') + .should('have.length', 2) + .each(($mark) => { + cy.wrap($mark).should('have.text', 'Deprecated').and('have.attr', 'opacity', '0.3'); + }); + } else { + cy.get('#chart-0 .deprecated-watermark').should('not.exist'); + } + } + }); + + it('keeps a large plot watermark in PNG exports, overlays, and the Chinese mobile chart', () => { + const model = 'minimaxm3'; + const officialRows = b300Rows(null).map((row) => ({ ...row, model })); + const overlayRows = b300Rows(OVERLAY_RUN_URL).map((row) => ({ ...row, model })); + cy.intercept('GET', '/api/v1/availability', { + body: officialRows.map(({ metrics: _metrics, ...row }) => row), + }); + cy.intercept('GET', '/api/v1/benchmarks*', { body: officialRows }); + cy.intercept('GET', '/api/unofficial-run*', { + body: { + runInfos: [ + { + id: OVERLAY_RUN_ID, + name: 'historical-m3-overlay', + branch: 'historical-m3-overlay', + sha: 'abc000', + createdAt: `${AGENTIC_DATE}T00:00:00Z`, + url: OVERLAY_RUN_URL, + conclusion: 'success', + status: 'completed', + isNonMainBranch: true, + }, + ], + benchmarks: overlayRows, + evaluations: [], + }, + }).as('historicalM3Overlay'); + + cy.visit('/inference/minimax-m3?i_seq=agentic-traces', { + onBeforeLoad: unlockAgenticGate, + }); + cy.get('#chart-0 [data-testid="result-context"]') + .should('contain.text', 'Source: SemiAnalysis InferenceX™') + .and('not.contain.text', 'Historical benchmark — submissions closed'); + cy.get('#chart-0 svg [data-testid="deprecated-watermark"]').should('be.visible'); + let exportedPng = ''; + let exportedLabels: string[] = []; + cy.window().then((win) => { + win.HTMLAnchorElement.prototype.click = function () { + if (this.download.endsWith('.png')) { + exportedPng = this.href; + exportedLabels = [ + ...win.document.querySelectorAll('#chart-0-export .deprecated-watermark'), + ].map((mark) => mark.textContent ?? ''); + } + }; + }); + cy.get('[data-testid="chart-figure"] [data-testid="export-button"]').first().click(); + cy.get('[data-testid="export-png-button"]').click(); + cy.window() + .should(() => expect(exportedPng).to.match(/^data:image\/png;base64,/u)) + .then(() => { + expect(exportedLabels).to.deep.equal(['Deprecated', 'Deprecated']); + cy.writeFile( + 'cypress/downloads/deprecated-model-watermark.png', + exportedPng.split(',')[1], + 'base64', + ); + }); + + cy.visit(`/inference/minimax-m3?i_seq=agentic-traces&unofficialrun=${OVERLAY_RUN_ID}`, { + onBeforeLoad: unlockAgenticGate, + }); + cy.wait('@historicalM3Overlay'); + cy.get('#chart-0 .unofficial-overlay-pt').should('have.length.greaterThan', 0); + cy.get('#chart-0 .deprecated-watermark').should('contain.text', 'Deprecated'); + cy.get('#chart-0 .unofficial-watermark-image').should('exist'); + cy.get('[aria-label="Dismiss historical-m3-overlay"]').click(); + cy.get('#chart-0 .unofficial-overlay-pt').should('not.exist'); + cy.get('#chart-0 svg [data-testid="deprecated-watermark"]').should('be.visible'); + + cy.viewport(375, 900); + cy.visit(`/zh/inference/minimax-m3?i_seq=agentic-traces&unofficialrun=${OVERLAY_RUN_ID}`, { + onBeforeLoad: unlockAgenticGate, + }); + cy.wait('@historicalM3Overlay'); + cy.get('#chart-0 svg [data-testid="deprecated-watermark"]') + .should('be.visible') + .and('have.length', 2) + .each(($mark) => cy.wrap($mark).should('have.text', '已弃用')); + cy.document().then((doc) => { + expect(doc.documentElement.scrollWidth).to.be.at.most(doc.documentElement.clientWidth); + }); + }); +}); + describe('Inference Chart — Simplified Chinese mobile path', () => { beforeEach(() => { cy.viewport(375, 900); diff --git a/packages/app/src/components/inference/ui/GPUGraph.tsx b/packages/app/src/components/inference/ui/GPUGraph.tsx index 95a51ad99..4cf29f835 100644 --- a/packages/app/src/components/inference/ui/GPUGraph.tsx +++ b/packages/app/src/components/inference/ui/GPUGraph.tsx @@ -26,7 +26,11 @@ import { getInferenceRunLabel, getOverlayLineLabel, } from '@/lib/benchmarks/inference-labels'; -import { getChartWatermark, Sequence } from '@/lib/catalog/data-mappings'; +import { + getChartWatermark, + getDeprecatedChartWatermark, + Sequence, +} from '@/lib/catalog/data-mappings'; import { useLocale } from '@/lib/i18n/use-locale'; import { formatNumber, getDisplayLabel, updateRepoUrl } from '@/lib/shared/utils'; import { perfRulerAxisMetricKey, usePerfRulerAxisReset } from '@/hooks/usePerfRulerAxisReset'; @@ -1310,6 +1314,7 @@ const GPUGraph = React.memo( data={officialPoints} margin={CHART_MARGIN} watermark={getChartWatermark()} + backgroundWatermark={getDeprecatedChartWatermark(modelLabel, locale)} testId="gpu-graph" grabCursor={true} caption={caption} diff --git a/packages/app/src/components/inference/ui/PowerTimeline.tsx b/packages/app/src/components/inference/ui/PowerTimeline.tsx index fc41e0d74..d228b7bc7 100644 --- a/packages/app/src/components/inference/ui/PowerTimeline.tsx +++ b/packages/app/src/components/inference/ui/PowerTimeline.tsx @@ -29,6 +29,7 @@ import { lttbDownsample } from '@/lib/d3-chart/downsample'; import { CHART_FONT_SANS, CHART_TYPE, px } from '@/lib/d3-chart/typography'; import { overlayRunColor, overlayRunIndex } from '@/lib/charts/overlay-run-style'; import { useLocale } from '@/lib/i18n/use-locale'; +import { getDeprecatedChartWatermark } from '@/lib/catalog/data-mappings'; import { getDisplayLabel } from '@/lib/shared/utils'; import { @@ -628,7 +629,8 @@ export default function PowerTimeline({ const locale = useLocale(); const t = STRINGS[locale]; const { hardwareConfig, hwTypesWithData } = useInferenceData(); - const { activeHwTypes, selectedPrecisions, quickFilters, activeDates } = useInferenceFilters(); + const { activeHwTypes, selectedPrecisions, quickFilters, activeDates, selectedModel } = + useInferenceFilters(); const { isLegendExpanded, highContrast } = useInferenceDisplay(); const { setBestPerSku, toggleHwType, setIsLegendExpanded, toggleActiveDate } = useInferenceActions(); @@ -1423,6 +1425,7 @@ export default function PowerTimeline({ height={CHART_HEIGHT} margin={MARGIN} watermark={overlayData && overlayPoints.length > 0 ? 'unofficial' : 'logo'} + backgroundWatermark={getDeprecatedChartWatermark(selectedModel, locale)} testId="power-timeline-chart-svg" grabCursor instructions={t.instructions} diff --git a/packages/app/src/components/inference/ui/ScatterGraph.tsx b/packages/app/src/components/inference/ui/ScatterGraph.tsx index 1418457f7..1bec27b01 100644 --- a/packages/app/src/components/inference/ui/ScatterGraph.tsx +++ b/packages/app/src/components/inference/ui/ScatterGraph.tsx @@ -45,6 +45,7 @@ import { } from '@/lib/catalog/constants'; import { getChartWatermark, + getDeprecatedChartWatermark, getPrecisionLabel, getSequenceLabel, type Precision, @@ -3723,6 +3724,7 @@ const ScatterGraph = React.memo( metricIdentity={metricIdentity} margin={CHART_MARGIN} watermark={getChartWatermark(isUnofficialRun)} + backgroundWatermark={getDeprecatedChartWatermark(modelLabel, locale)} testId="scatter-graph" grabCursor={true} caption={caption} diff --git a/packages/app/src/lib/catalog/data-mappings.test.ts b/packages/app/src/lib/catalog/data-mappings.test.ts index 86509ab62..1599ab5dd 100644 --- a/packages/app/src/lib/catalog/data-mappings.test.ts +++ b/packages/app/src/lib/catalog/data-mappings.test.ts @@ -4,6 +4,7 @@ import { getModelAndSequence, getModelAndSequenceFromArtifact, getModelLabel, + getDeprecatedChartWatermark, getOpenRouterModelId, MODEL_OPTIONS, SEQUENCE_OPTIONS, @@ -167,6 +168,27 @@ describe('getModelAndSequenceFromArtifact', () => { }); describe('isModelDeprecated', () => { + it.each([ + Model.MiniMax_M3, + Model.MiniMax_M2_5, + Model.Kimi_K2_5, + Model.GLM_5, + Model.GptOss, + Model.Llama3_3_70B, + ])('adds a localized Deprecated watermark to %s', (model) => { + expect(getDeprecatedChartWatermark(model, 'en')).toBe('Deprecated'); + expect(getDeprecatedChartWatermark(model, 'zh')).toBe('已弃用'); + }); + + it.each([ + Model.MiniMax_M3_1_Flash, + Model.DeepSeek_R1, + Model.Qwen3_8_27B, + Model.Llama3_1_70B, + undefined, + ])('does not mark the non-deprecated model %s', (model) => { + expect(getDeprecatedChartWatermark(model, 'en')).toBeUndefined(); + }); it('returns true for deprecated model Llama3_3_70B', () => { expect(isModelDeprecated(Model.Llama3_3_70B)).toBe(true); }); diff --git a/packages/app/src/lib/catalog/data-mappings.ts b/packages/app/src/lib/catalog/data-mappings.ts index b6530f06d..f8f69131d 100644 --- a/packages/app/src/lib/catalog/data-mappings.ts +++ b/packages/app/src/lib/catalog/data-mappings.ts @@ -258,6 +258,14 @@ export function getChartWatermark(isUnofficialRun = false): 'logo' | 'unofficial return isUnofficialRun ? 'unofficial' : 'logo'; } +export function getDeprecatedChartWatermark( + model: string | undefined, + locale: 'en' | 'zh', +): string | undefined { + if (!model || !isModelDeprecated(model as Model)) return undefined; + return locale === 'zh' ? '已弃用' : 'Deprecated'; +} + export const MODEL_PREFIX_MAPPING: Record = Object.fromEntries( (Object.entries(MODEL_CONFIG) as [Model, (typeof MODEL_CONFIG)[Model]][]) .filter(([, c]) => c.prefix) diff --git a/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx b/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx index 4a466736d..a6ebf28a1 100644 --- a/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx +++ b/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx @@ -21,6 +21,7 @@ function D3ChartInner( height = 600, margin, watermark, + backgroundWatermark, testId, grabCursor = true, instructions, @@ -107,6 +108,7 @@ function D3ChartInner( height, margin, watermark, + backgroundWatermark, testId, grabCursor, instructions, diff --git a/packages/app/src/lib/d3-chart/D3Chart/types.ts b/packages/app/src/lib/d3-chart/D3Chart/types.ts index 775cf2ef8..027fdcd11 100644 --- a/packages/app/src/lib/d3-chart/D3Chart/types.ts +++ b/packages/app/src/lib/d3-chart/D3Chart/types.ts @@ -195,6 +195,7 @@ export interface D3ChartProps { height?: number; margin?: ChartMargin; watermark?: 'logo' | 'unofficial' | 'none'; + backgroundWatermark?: string; testId?: string; grabCursor?: boolean; instructions?: string; diff --git a/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts b/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts index b50cf7d21..f84970f33 100644 --- a/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts +++ b/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts @@ -232,6 +232,7 @@ export function useD3ChartRenderer(props: D3ChartProps, deps: RendererDeps data, margin = { top: 24, right: 10, bottom: 40, left: 60 }, watermark = 'logo', + backgroundWatermark, clipContent = true, dataIdentity, metricIdentity, @@ -317,6 +318,7 @@ export function useD3ChartRenderer(props: D3ChartProps, deps: RendererDeps containerHeight: dimensions.height, margin, watermark: getDomainAwareChartWatermark(watermark, window.location.hostname), + backgroundWatermark, xLabel: xAxisConfig?.label, yLabel: yAxisConfig?.label, clipContent, @@ -331,6 +333,7 @@ export function useD3ChartRenderer(props: D3ChartProps, deps: RendererDeps margin.bottom, margin.left, watermark, + backgroundWatermark, xAxisConfig?.label, yAxisConfig?.label, clipContent, diff --git a/packages/app/src/lib/d3-chart/chart-setup.test.ts b/packages/app/src/lib/d3-chart/chart-setup.test.ts index faf5682f3..f7a673e3f 100644 --- a/packages/app/src/lib/d3-chart/chart-setup.test.ts +++ b/packages/app/src/lib/d3-chart/chart-setup.test.ts @@ -186,6 +186,77 @@ describe('setupChartStructure', () => { }); describe('watermark variants', () => { + it.each([ + [400, 600], + [1200, 600], + [800, 300], + ])( + 'preserves the SemiAnalysis logo and places Deprecated above and below it at %s×%s', + (containerWidth, containerHeight) => { + const svgEl = makeSvgEl(); + const config = defaultConfig({ watermark: 'logo', containerWidth, containerHeight }); + setupChartStructure(svgEl, config); + const originalLogo = svgEl.querySelector('#logo-pattern-test-chart')!.outerHTML; + const originalRect = svgEl.querySelector('.watermark-rect')!.outerHTML; + + setupChartStructure(svgEl, { ...config, backgroundWatermark: 'Deprecated' }); + expect(svgEl.querySelector('#logo-pattern-test-chart')!.outerHTML).toBe(originalLogo); + expect(svgEl.querySelector('.watermark-rect')!.outerHTML).toBe(originalRect); + const logo = svgEl.querySelector('#logo-pattern-test-chart image')!; + const above = svgEl.querySelector('.deprecated-watermark[data-position="above"]')!; + const status = svgEl.querySelector('.deprecated-watermark[data-position="below"]')!; + expect(svgEl.querySelectorAll('.deprecated-watermark')).toHaveLength(2); + expect(Number(above.getAttribute('y'))).toBeGreaterThanOrEqual(config.margin.top); + expect(Number(above.getAttribute('y')) + Number(above.getAttribute('height'))).toBeLessThan( + Number(logo.getAttribute('y')), + ); + expect(above.getAttribute('opacity')).toBe('0.3'); + expect(status.getAttribute('opacity')).toBe('0.3'); + const logoBottom = Number(logo.getAttribute('y')) + Number(logo.getAttribute('height')); + expect(Number(status.getAttribute('y'))).toBeGreaterThan(logoBottom); + expect( + Number(status.getAttribute('y')) + Number(status.getAttribute('height')), + ).toBeLessThanOrEqual(containerHeight - config.margin.bottom); + }, + ); + it('keeps a large deprecated watermark behind the plot alongside unofficial marks through resize and removal', () => { + const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + setupChartStructure( + svgEl, + defaultConfig({ + watermark: 'unofficial', + backgroundWatermark: 'Deprecated', + }), + ); + const mark = svgEl.querySelector('.deprecated-watermark')!; + expect(mark.parentElement).toBe(svgEl); + expect([...svgEl.children].indexOf(mark)).toBeLessThan( + [...svgEl.children].indexOf(svgEl.querySelector('.chart-root')!), + ); + expect(mark.getAttribute('viewBox')).toBe('0 0 1000 200'); + expect(mark.getAttribute('pointer-events')).toBe('none'); + expect([...mark.querySelectorAll('text')].map((node) => node.textContent)).toEqual([ + 'Deprecated', + ]); + expect(svgEl.querySelector('.unofficial-watermark-image')).not.toBeNull(); + + const resized = defaultConfig({ + watermark: 'unofficial', + backgroundWatermark: '已弃用', + containerWidth: 400, + }); + setupChartStructure(svgEl, resized); + expect(svgEl.querySelectorAll('.deprecated-watermark')).toHaveLength(2); + expect(svgEl.querySelector('.deprecated-watermark')?.getAttribute('width')).toBe( + String((400 - resized.margin.left - resized.margin.right) * 0.8), + ); + expect(svgEl.querySelector('.deprecated-watermark')?.textContent).toBe('已弃用'); + + setupChartStructure(svgEl, defaultConfig({ watermark: 'logo' })); + expect(svgEl.querySelector('.deprecated-watermark')).toBeNull(); + expect(svgEl.querySelector('.unofficial-watermark-image')).toBeNull(); + expect(svgEl.querySelector('.watermark-rect')).not.toBeNull(); + }); it('creates logo watermark when watermark is "logo"', () => { const svgEl = makeSvgEl(); const config = defaultConfig({ watermark: 'logo' }); diff --git a/packages/app/src/lib/d3-chart/chart-setup.ts b/packages/app/src/lib/d3-chart/chart-setup.ts index da556206a..4c3fbda15 100644 --- a/packages/app/src/lib/d3-chart/chart-setup.ts +++ b/packages/app/src/lib/d3-chart/chart-setup.ts @@ -6,6 +6,7 @@ import { createLogoWatermark, createUnofficialWatermark, positionUnofficialWatermarkImage, + updateBackgroundWatermark, } from './watermark'; export function setupChartStructure( @@ -119,6 +120,7 @@ export function setupChartStructure( .text(config.xLabel); } + updateBackgroundWatermark(svg, config.backgroundWatermark, width, height, margin); return { svg, g, zoomGroup, xAxisGroup, yAxisGroup, gridGroup, defs, width, height, margin }; } @@ -195,6 +197,8 @@ export function setupChartStructure( } } + updateBackgroundWatermark(svg, config.backgroundWatermark, width, height, margin); + const yLabel = svg.select('.y-axis-label'); if (!yLabel.empty()) { yLabel.attr('x', -(margin.top + height / 2)).text(config.yLabel ?? ''); diff --git a/packages/app/src/lib/d3-chart/types.ts b/packages/app/src/lib/d3-chart/types.ts index a9cc10e9c..caa53d592 100644 --- a/packages/app/src/lib/d3-chart/types.ts +++ b/packages/app/src/lib/d3-chart/types.ts @@ -26,6 +26,7 @@ export interface ChartSetupConfig { containerHeight: number; margin: ChartMargin; watermark: 'logo' | 'unofficial' | 'none'; + backgroundWatermark?: string; xLabel?: string; yLabel?: string; clipContent?: boolean; diff --git a/packages/app/src/lib/d3-chart/typography.ts b/packages/app/src/lib/d3-chart/typography.ts index d44c5a60f..f60a13613 100644 --- a/packages/app/src/lib/d3-chart/typography.ts +++ b/packages/app/src/lib/d3-chart/typography.ts @@ -7,6 +7,7 @@ export const CHART_TYPE = { lineLabel: 13, micro: 9, watermark: 20, + backgroundWatermark: 108, } as const; export function px(size: number): string { diff --git a/packages/app/src/lib/d3-chart/watermark.ts b/packages/app/src/lib/d3-chart/watermark.ts index f08f15914..56805b888 100644 --- a/packages/app/src/lib/d3-chart/watermark.ts +++ b/packages/app/src/lib/d3-chart/watermark.ts @@ -5,6 +5,52 @@ import { CHART_TYPE, px } from './typography'; const UNOFFICIAL_WATERMARK_IMAGE_PATH = '/decorative/kanye-west.png'; +export function updateBackgroundWatermark( + svg: d3.Selection, + label: string | undefined, + innerWidth: number, + innerHeight: number, + margin: ChartMargin, +): void { + svg.selectAll('.deprecated-watermark').remove(); + if (!label) return; + + // Main's centered logo occupies at most the middle 60% of the plot height. + // Reserve separate bands above and below it without moving or resizing it. + for (const [position, offset] of [ + ['above', 0.02], + ['below', 0.82], + ] as const) { + const mark = svg + .insert('svg', '.chart-root') + .attr('class', 'deprecated-watermark') + .attr('data-testid', 'deprecated-watermark') + .attr('data-position', position) + .attr('x', margin.left + innerWidth * 0.1) + .attr('y', margin.top + innerHeight * offset) + .attr('width', innerWidth * 0.8) + .attr('height', innerHeight * 0.16) + .attr('viewBox', '0 0 1000 200') + .attr('preserveAspectRatio', 'xMidYMid meet') + .attr('fill', 'currentColor') + .attr('opacity', 0.3) + .attr('pointer-events', 'none') + .attr('role', 'img') + .attr('aria-label', label) + .attr('aria-hidden', position === 'below' ? 'true' : null); + + mark + .append('text') + .attr('x', 500) + .attr('y', 100) + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle') + .attr('font-size', px(CHART_TYPE.backgroundWatermark)) + .attr('font-weight', 800) + .text(label); + } +} + function insertWatermarkRect( svg: d3.Selection, innerWidth: number, diff --git a/packages/app/src/lib/views-api/docs/options.ts b/packages/app/src/lib/views-api/docs/options.ts index dbd471f94..3c7e3b2d0 100644 --- a/packages/app/src/lib/views-api/docs/options.ts +++ b/packages/app/src/lib/views-api/docs/options.ts @@ -239,8 +239,8 @@ export const operations: ApiOperation[] = [ path: '/api/v1/views/options', summary: text('Discover every views-API option domain', '获取 views API 的全部选项域'), description: text( - 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. No database access.', - 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。不访问数据库。', + 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. Models with category deprecated display Deprecated background watermarks in separate bands above and below the existing SemiAnalysis logo. This is presentation only and does not change view data. No database access.', + 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。分类为 deprecated 的模型会在现有 SemiAnalysis 标志上方和下方的独立区域显示“已弃用”背景水印。该水印仅用于展示,不改变视图数据。不访问数据库。', ), audience: 'public', stability: 'beta', diff --git a/packages/skills/skills/inferencex-api/integrity.json b/packages/skills/skills/inferencex-api/integrity.json index cbcac8a4e..7dd040c1c 100644 --- a/packages/skills/skills/inferencex-api/integrity.json +++ b/packages/skills/skills/inferencex-api/integrity.json @@ -9,7 +9,7 @@ "references/cli-contract.md": "4f070877eeefffad2860c58f79f68432fbe27aa21fc578a1c696f0309a3d7d7b", "references/cli.md": "11e1183f7ed70d669019e8435ac16a6a9f873de027507b8850a9943f6659bfd9", "references/collectivex.md": "f79cf8e2779a9289ab69f79f4a4b338e0b9a5f76a3750a3e969f33a744e726a1", - "references/dashboard-views.md": "1e2a0848c4efb9bca66fbf708082aa87402f32fff43fcda3d0638658cba0b90b", + "references/dashboard-views.md": "333e4473a6027a45992a01bc2e908437dc6b5c43542dd481b0929ba95027f506", "references/offline-exports.md": "95aa565dcd4c9e592159baa560a9a58217bc61e395de1c6daf0576795c7f4ec9", "references/pareto.md": "1b4d2d163f982e3f2d97addce310789eae9c1e5badd82dd7501708c9e0385027", "references/powerx.md": "5af5527d54a7e6191a5769080c122638b333b1ceb59e19fdee7df662a5cd3c8f", diff --git a/packages/skills/skills/inferencex-api/references/dashboard-views.md b/packages/skills/skills/inferencex-api/references/dashboard-views.md index 0f2955f16..f2ad25963 100644 --- a/packages/skills/skills/inferencex-api/references/dashboard-views.md +++ b/packages/skills/skills/inferencex-api/references/dashboard-views.md @@ -33,9 +33,20 @@ results or treat its interpolated values as separately measured operating points for static model, hardware, sequence and metric registries; use each specialized view's `options`, `configurations`, `workloads`, or raw run-list operation for data-dependent choices. - MiniMax M3 has `category=deprecated` in `options.models`; it remains selectable - through explicit model parameters and overview `modelScope=all`. MiniMax M3.1 - Flash remains in the default model group. + Every model with `category=deprecated` in `options.models` receives large + “Deprecated” plot watermarks (“已弃用” in Chinese), including PNG exports and + unofficial-run overlays. They appear in separate bands above and below the existing SemiAnalysis logo + so the marks do not overlap; logo styling and official-domain behavior follow the + main site. This is presentation only; view API values and filters are unchanged. + Deprecated models remain selectable through explicit model parameters and + overview `modelScope=all`. MiniMax M3 is deprecated; M3.1 Flash remains default. + + 中文:`options.models` 中分类为 `deprecated` 的所有模型,都会在绘图区背景中显示大号 + “已弃用”水印,PNG 导出和非官方 run overlay 中同样保留。水印分别放在现有 SemiAnalysis + 标志的上方和下方,彼此不会重叠;标志样式及其在正式域名上的显示规则与主站一致。该水印仅用于 + 展示,不改变视图 API 的数值或筛选行为。已弃用模型仍可通过显式模型参数或 overview 的 + `modelScope=all` 查询。MiniMax M3 已弃用,M3.1 Flash 仍保留在默认模型组中。 + 2. Build one public HTTPS GET URL using `URLSearchParams`, especially for JSON costs, commas, slashes, and run-specific comparison dates. No credentials are required. Unknown or repeated parameter names return 400.