Yoobit commited on
Commit
4329daa
·
1 Parent(s): 15174ae

feat(chart): 优化图表主题适配与数据处理逻辑

Browse files

- 图表支持深色模式自动检测与切换,根据系统主题调整配色
- 修改数据单位转换逻辑,调整为 xRaw * 1e12 / 1024 更准确反映 FLOPs
- 数据点排序方式改为按 x 轴值排序,提升图表展示合理性
- 增加对主题变化的监听,自动重渲染图表以适配颜色
- 图表容器使用响应式尺寸 class 替代固定宽高,增强适配性
- 移除调试日志,优化坐标轴和图例颜色变量命名
- 调整 tooltip 中 y 轴数值显示精度为四位小数,提高可读性

同时更新 useLeaderboardData 中数据集名称和表头显示文本:
- 更新多个数据集显示名,如 'FinePDFs-en'、'Ch-FineWeb-Edu' 等
- 修改部分列标题为全大写格式,统一展示风格
- 调整默认排序方式为降序,简化表头点击排序逻辑

新增 Markdown 转 HTML 工具函数,用于解析展示页面头部信息:
- 支持标题、段落、链接、粗体、斜体、行内代码等基础语法
- 自动转义 HTML 字符,防止 XSS 风险
- 添加 Tailwind 样式类以适配深色模式

页面结构优化:
- 使用 ref 滚动到图表区域,提升用户交互体验
- 页面标题与更新时间动态绑定,支持自定义 header 内容
- 引入 logo 图片并设置容器样式,增强视觉呈现
- 优化筛选区域标签文本为英文,统一界面语言风格

构建配置调整:
- 简化 package.json 中的 build 脚本,移除冗余 type-check 和 build-only
- 移除部分未使用的 devDependencies,如 @tsconfig/node22、typescript 等
- 删除 tsconfig.node.json 文件,精简 TypeScript 配置结构

package.json CHANGED
@@ -1,14 +1,12 @@
1
  {
2
  "name": "vue",
3
- "version": "0.0.0",
4
  "private": true,
5
  "type": "module",
6
  "scripts": {
7
  "dev": "vite",
8
- "build": "run-p type-check \"build-only {@}\" --",
9
  "preview": "vite preview",
10
- "build-only": "vite build",
11
- "type-check": "vue-tsc --build",
12
  "lint": "eslint . --fix",
13
  "format": "prettier --write src/"
14
  },
@@ -19,11 +17,8 @@
19
  "vue-router": "^4.5.0"
20
  },
21
  "devDependencies": {
22
- "@tsconfig/node22": "^22.0.1",
23
- "@types/node": "^22.14.0",
24
  "@vitejs/plugin-vue": "^5.2.3",
25
  "@vue/eslint-config-prettier": "^10.2.0",
26
- "@vue/eslint-config-typescript": "^14.5.0",
27
  "@vue/tsconfig": "^0.7.0",
28
  "autoprefixer": "^10.4.21",
29
  "eslint": "^9.22.0",
@@ -33,9 +28,7 @@
33
  "postcss": "^8.5.6",
34
  "prettier": "3.5.3",
35
  "tailwindcss": "^3.3.3",
36
- "typescript": "~5.8.0",
37
  "vite": "^6.2.4",
38
- "vite-plugin-vue-devtools": "^7.7.2",
39
- "vue-tsc": "^2.2.8"
40
  }
41
- }
 
1
  {
2
  "name": "vue",
3
+ "version": "1.0.0",
4
  "private": true,
5
  "type": "module",
6
  "scripts": {
7
  "dev": "vite",
8
+ "build": "vite build",
9
  "preview": "vite preview",
 
 
10
  "lint": "eslint . --fix",
11
  "format": "prettier --write src/"
12
  },
 
17
  "vue-router": "^4.5.0"
18
  },
19
  "devDependencies": {
 
 
20
  "@vitejs/plugin-vue": "^5.2.3",
21
  "@vue/eslint-config-prettier": "^10.2.0",
 
22
  "@vue/tsconfig": "^0.7.0",
23
  "autoprefixer": "^10.4.21",
24
  "eslint": "^9.22.0",
 
28
  "postcss": "^8.5.6",
29
  "prettier": "3.5.3",
30
  "tailwindcss": "^3.3.3",
 
31
  "vite": "^6.2.4",
32
+ "vite-plugin-vue-devtools": "^7.7.2"
 
33
  }
34
+ }
src/components/Chart.vue CHANGED
@@ -6,6 +6,11 @@ import * as echarts from 'echarts'
6
  const chartRef = ref(null)
7
  let chart = null
8
 
 
 
 
 
 
9
  const { leaderboard, selectedDataNameChart, modelTypeGroups, strtSymbolSeries } = useLeaderboardData()
10
 
11
  // 接受一个可选 prop:autoShowSeries(数组),用于指定哪些模型类型在图表加载时自动显示。
@@ -24,19 +29,20 @@ function buildSeriesFromLeaderboard(rows = []) {
24
  const isMoE = !!r._isMoE
25
  const seriesName = isMoE ? `${baseType} (MoE)` : baseType
26
  if (!groups[seriesName]) groups[seriesName] = []
27
- // 数据原本单位为 TFLOPs,转换为 FLOPs(1 TFLOP = 1e12 FLOPs)用于对数尺度显示
28
  const xRaw = parseFloat(r.BF16_TFLOPs) || 0
29
- const x = xRaw > 0 ? xRaw * 1e12 : 0
 
30
  const y = parseFloat(r.ic) || 0
31
  const rank = Number(r.rank) || 0
32
  groups[seriesName].push({ x, y, rank })
33
  })
34
 
35
  return Object.keys(groups).map(name => {
36
- // 过滤掉 x<=0 的点(对数坐标轴不能显示 0 或负值),并按 rank 排序
37
  const data = groups[name]
38
  .filter(p => Number.isFinite(p.x) && p.x > 0 && Number.isFinite(p.y))
39
- .sort((a, b) => a.rank - b.rank)
40
  .map(d => [d.x, d.y])
41
  // 根据该系列的样本点数量适度放大 symbolSize,避免点太小难以点击
42
  const count = groups[name].length || 1
@@ -45,7 +51,13 @@ function buildSeriesFromLeaderboard(rows = []) {
45
  })
46
  }
47
 
48
- function getChartOption(series) {
 
 
 
 
 
 
49
  // 计算数据范围以自动缩放坐标轴,避免留太多空白
50
  const allX = []
51
  const allY = []
@@ -166,7 +178,7 @@ function getChartOption(series) {
166
  const symbolShape = strtSymbolSeries.includes(String(s.name)) ?
167
  'path://M341.5 45.1C337.4 37.1 329.1 32 320.1 32C311.1 32 302.8 37.1 298.7 45.1L225.1 189.3L65.2 214.7C56.3 216.1 48.9 222.4 46.1 231C43.3 239.6 45.6 249 51.9 255.4L166.3 369.9L141.1 529.8C139.7 538.7 143.4 547.7 150.7 553C158 558.3 167.6 559.1 175.7 555L320.1 481.6L464.4 555C472.4 559.1 482.1 558.3 489.4 553C496.7 547.7 500.4 538.8 499 529.8L473.7 369.9L588.1 255.4C594.5 249 596.7 239.6 593.9 231C591.1 222.4 583.8 216.1 574.8 214.7L415 189.3L341.5 45.1z'
168
  : 'circle'
169
- console.log(`Series "${s.name}" uses symbol "${symbolShape}"`)
170
  const baseSize = Number(s.symbolSize) || 8
171
  const emphasisSize = Math.max(12, Math.round(baseSize * 1.6))
172
  // 如果是星形,增大尺寸以匹配视觉效果
@@ -196,7 +208,7 @@ function getChartOption(series) {
196
 
197
  return {
198
  color: colors,
199
- title: { text: 'BF16_TFLOPs vs IC', left: '6%', textStyle: { color: '#cfd8dc' } },
200
  tooltip: {
201
  trigger: 'item',
202
  formatter: params => {
@@ -213,7 +225,7 @@ function getChartOption(series) {
213
  } else {
214
  xStr = String(x)
215
  }
216
- const yStr = (typeof y === 'number' && Number.isFinite(y)) ? Number(y).toFixed(2) : String(y)
217
  return `${params.seriesName}<br/>FLOPs: ${xStr}<br/>IC: ${yStr}`
218
  }
219
  },
@@ -224,7 +236,7 @@ function getChartOption(series) {
224
  align: 'left',
225
  itemWidth: 14,
226
  itemHeight: 10,
227
- textStyle: { color: '#cfd8dc' },
228
  tooltip: { show: true }
229
  }, { selected: legendSelected, data: legendData }),
230
  grid: { left: '6%', right: '20%', bottom: '8%', containLabel: true },
@@ -233,9 +245,9 @@ function getChartOption(series) {
233
  name: 'FLOPs',
234
  nameLocation: 'middle',
235
  nameGap: 30,
236
- axisLine: { lineStyle: { color: '#78909c' } },
237
  axisLabel: {
238
- color: '#b0bec5',
239
  formatter: value => {
240
  if (!Number.isFinite(value) || value <= 0) return String(value)
241
  const exp = Math.floor(Math.log10(value))
@@ -244,16 +256,16 @@ function getChartOption(series) {
244
  return `${mant.toFixed(2)}×10${toSuperscript(exp)}`
245
  }
246
  },
247
- splitLine: { show: true, lineStyle: { type: 'dashed', color: '#37474f' } },
248
  min: xMin,
249
  max: xMax
250
  },
251
  yAxis: {
252
  type: 'value',
253
  name: 'Information capacity',
254
- axisLine: { lineStyle: { color: '#78909c' } },
255
- axisLabel: { color: '#b0bec5', formatter: v => (Number.isFinite(v) ? Number(v).toFixed(2) : String(v)) },
256
- splitLine: { show: true, lineStyle: { type: 'dashed', color: '#37474f' } },
257
  min: yMin,
258
  max: yMax
259
  },
@@ -272,11 +284,13 @@ function renderChart() {
272
  rows = rows.filter(r => String(r.data_name ?? '') === sel)
273
  }
274
  const series = buildSeriesFromLeaderboard(rows)
275
- const option = getChartOption(series)
276
  chart.setOption(option, { notMerge: true })
277
  }
278
 
279
  onMounted(() => {
 
 
280
  if (!chartRef.value) return
281
  chart = echarts.init(chartRef.value)
282
  // 首次渲染(如果数据尚未加载,后续会由 watcher 更新)
@@ -296,14 +310,21 @@ watch(selectedDataNameChart, () => {
296
  renderChart()
297
  })
298
 
 
 
 
 
 
 
299
  onBeforeUnmount(() => {
300
  if (resizeHandler) window.removeEventListener('resize', resizeHandler)
 
301
  if (chart) { chart.dispose(); chart = null }
302
  })
303
  </script>
304
 
305
  <template>
306
- <div ref="chartRef" style="width: 880px; height: 480px;"></div>
307
  </template>
308
 
309
  <style scoped></style>
 
6
  const chartRef = ref(null)
7
  let chart = null
8
 
9
+ // 独立监听主题变化
10
+ const isDarkChart = ref(document.documentElement.classList.contains('dark'))
11
+
12
+ let themeObserver = null
13
+
14
  const { leaderboard, selectedDataNameChart, modelTypeGroups, strtSymbolSeries } = useLeaderboardData()
15
 
16
  // 接受一个可选 prop:autoShowSeries(数组),用于指定哪些模型类型在图表加载时自动显示。
 
29
  const isMoE = !!r._isMoE
30
  const seriesName = isMoE ? `${baseType} (MoE)` : baseType
31
  if (!groups[seriesName]) groups[seriesName] = []
32
+ // 数据原本单位为 FLOPs (G),转换为 FLOPs(1 TFLOP = 1e12 FLOPs)用于对数尺度显示
33
  const xRaw = parseFloat(r.BF16_TFLOPs) || 0
34
+ const x = xRaw > 0 ? xRaw * 1e12 / 1024 : 0
35
+ // const x = xRaw
36
  const y = parseFloat(r.ic) || 0
37
  const rank = Number(r.rank) || 0
38
  groups[seriesName].push({ x, y, rank })
39
  })
40
 
41
  return Object.keys(groups).map(name => {
42
+ // 过滤掉 x<=0 的点(对数坐标轴不能显示 0 或负值),并按 x 轴值排序
43
  const data = groups[name]
44
  .filter(p => Number.isFinite(p.x) && p.x > 0 && Number.isFinite(p.y))
45
+ .sort((a, b) => a.x - b.x)
46
  .map(d => [d.x, d.y])
47
  // 根据该系列的样本点数量适度放大 symbolSize,避免点太小难以点击
48
  const count = groups[name].length || 1
 
51
  })
52
  }
53
 
54
+ function getChartOption(series, isDark = false) {
55
+ // 根据主题设置颜色
56
+ const titleColor = isDark ? '#cfd8dc' : '#333333'
57
+ const legendColor = isDark ? '#cfd8dc' : '#333333'
58
+ const axisLineColor = isDark ? '#78909c' : '#666666'
59
+ const axisLabelColor = isDark ? '#b0bec5' : '#666666'
60
+ const splitLineColor = isDark ? '#37474f' : '#cccccc'
61
  // 计算数据范围以自动缩放坐标轴,避免留太多空白
62
  const allX = []
63
  const allY = []
 
178
  const symbolShape = strtSymbolSeries.includes(String(s.name)) ?
179
  'path://M341.5 45.1C337.4 37.1 329.1 32 320.1 32C311.1 32 302.8 37.1 298.7 45.1L225.1 189.3L65.2 214.7C56.3 216.1 48.9 222.4 46.1 231C43.3 239.6 45.6 249 51.9 255.4L166.3 369.9L141.1 529.8C139.7 538.7 143.4 547.7 150.7 553C158 558.3 167.6 559.1 175.7 555L320.1 481.6L464.4 555C472.4 559.1 482.1 558.3 489.4 553C496.7 547.7 500.4 538.8 499 529.8L473.7 369.9L588.1 255.4C594.5 249 596.7 239.6 593.9 231C591.1 222.4 583.8 216.1 574.8 214.7L415 189.3L341.5 45.1z'
180
  : 'circle'
181
+ // console.log(`Series "${s.name}" uses symbol "${symbolShape}"`)
182
  const baseSize = Number(s.symbolSize) || 8
183
  const emphasisSize = Math.max(12, Math.round(baseSize * 1.6))
184
  // 如果是星形,增大尺寸以匹配视觉效果
 
208
 
209
  return {
210
  color: colors,
211
+ title: { text: 'BF16_TFLOPs vs IC', left: '6%', textStyle: { color: titleColor } },
212
  tooltip: {
213
  trigger: 'item',
214
  formatter: params => {
 
225
  } else {
226
  xStr = String(x)
227
  }
228
+ const yStr = (typeof y === 'number' && Number.isFinite(y)) ? Number(y).toFixed(4) : String(y)
229
  return `${params.seriesName}<br/>FLOPs: ${xStr}<br/>IC: ${yStr}`
230
  }
231
  },
 
236
  align: 'left',
237
  itemWidth: 14,
238
  itemHeight: 10,
239
+ textStyle: { color: legendColor },
240
  tooltip: { show: true }
241
  }, { selected: legendSelected, data: legendData }),
242
  grid: { left: '6%', right: '20%', bottom: '8%', containLabel: true },
 
245
  name: 'FLOPs',
246
  nameLocation: 'middle',
247
  nameGap: 30,
248
+ axisLine: { lineStyle: { color: axisLineColor } },
249
  axisLabel: {
250
+ color: axisLabelColor,
251
  formatter: value => {
252
  if (!Number.isFinite(value) || value <= 0) return String(value)
253
  const exp = Math.floor(Math.log10(value))
 
256
  return `${mant.toFixed(2)}×10${toSuperscript(exp)}`
257
  }
258
  },
259
+ splitLine: { show: true, lineStyle: { type: 'dashed', color: splitLineColor } },
260
  min: xMin,
261
  max: xMax
262
  },
263
  yAxis: {
264
  type: 'value',
265
  name: 'Information capacity',
266
+ axisLine: { lineStyle: { color: axisLineColor } },
267
+ axisLabel: { color: axisLabelColor, formatter: v => (Number.isFinite(v) ? Number(v).toFixed(2) : String(v)) },
268
+ splitLine: { show: true, lineStyle: { type: 'dashed', color: splitLineColor } },
269
  min: yMin,
270
  max: yMax
271
  },
 
284
  rows = rows.filter(r => String(r.data_name ?? '') === sel)
285
  }
286
  const series = buildSeriesFromLeaderboard(rows)
287
+ const option = getChartOption(series, isDarkChart.value)
288
  chart.setOption(option, { notMerge: true })
289
  }
290
 
291
  onMounted(() => {
292
+ isDarkChart.value = window.matchMedia("(prefers-color-scheme: dark)").matches //true | false
293
+
294
  if (!chartRef.value) return
295
  chart = echarts.init(chartRef.value)
296
  // 首次渲染(如果数据尚未加载,后续会由 watcher 更新)
 
310
  renderChart()
311
  })
312
 
313
+ // 当主题变化时重新渲染图表
314
+ watch(isDarkChart, (newVal) => {
315
+ console.log('isDarkChart changed to:', newVal)
316
+ renderChart()
317
+ })
318
+
319
  onBeforeUnmount(() => {
320
  if (resizeHandler) window.removeEventListener('resize', resizeHandler)
321
+ if (themeObserver) themeObserver.disconnect()
322
  if (chart) { chart.dispose(); chart = null }
323
  })
324
  </script>
325
 
326
  <template>
327
+ <div ref="chartRef" class="w-full h-[80vh]"></div>
328
  </template>
329
 
330
  <style scoped></style>
src/composables/useLeaderboardData.js CHANGED
@@ -52,19 +52,19 @@ const headerDisplayMap = reactive({
52
  'model_name': 'MODEL NAME',
53
  'model_series': 'MODEL SERIES',
54
  'model_size (B)': 'MODEL SIZE (B)',
55
- 'constant': 'Text size',
56
- 'conditional_entropy': 'Negative log-likelihood',
57
  'BF16_TFLOPs': 'FLOPs (G)',
58
- 'ic': ' Information Capacity',
59
  })
60
 
61
  // 数据集名称显示映射(raw data_name -> 显示名)
62
  const dataNameDisplayMap = reactive({
63
- 'data_part_0000': 'Data Part 0000',
64
- 'eng_Latn_000_00027_long': 'FLORES-200 English',
65
- 'IndustryCorpus_batch_aa_long': 'Industry Corpus AA',
66
- 'CC-MAIN-2013-20_train-00000-of-00014_long': 'CC-MAIN (2013-20)',
67
- 'NextCoderDataset_v1_long': 'NextCoder v1',
68
  })
69
 
70
 
@@ -131,7 +131,7 @@ const filteredLeaderboard = computed(() => {
131
  return filtered
132
  })
133
 
134
- // 点击表头切换排序:循环 none -> 降序 -> 升序 -> none
135
  function setSortKey(h) {
136
  if (!h) return
137
  if (globalState.sortKey !== h) {
@@ -139,14 +139,8 @@ function setSortKey(h) {
139
  globalState.sortDesc = true
140
  return
141
  }
142
- // same key, toggle
143
- if (globalState.sortDesc) {
144
- globalState.sortDesc = false
145
- return
146
- }
147
- // clear
148
- globalState.sortKey = ''
149
- globalState.sortDesc = true
150
  }
151
 
152
  // 计算当前应该显示的列(不含 rank)
@@ -172,7 +166,7 @@ async function fetchAndLoadCsv(path = globalState.DEFAULT_CSV_PATH) {
172
  // 默认以最后一列升序排序(如果不存在则回退到 scoreKey),不直接在 rows 上预排序
173
  const defaultKey = headers.length > 0 ? headers[headers.length - 1] : scoreKey
174
  globalState.sortKey = defaultKey || ''
175
- globalState.sortDesc = false
176
 
177
  // 特定处理
178
  // 判断模型开头是否在 MoEModelSeries 中,是则在 判断尾部是否为-A{number}B这样的格式
@@ -272,7 +266,11 @@ async function fetchAndLoadCsv(path = globalState.DEFAULT_CSV_PATH) {
272
  const n = Number(raw)
273
  if (h === 'ic') {
274
  row._formatted[h] = Number.isFinite(n) ? n.toFixed(4) : raw
275
- } else {
 
 
 
 
276
  row._formatted[h] = Number.isFinite(n) ? n.toFixed(3) : raw
277
  }
278
  } else {
 
52
  'model_name': 'MODEL NAME',
53
  'model_series': 'MODEL SERIES',
54
  'model_size (B)': 'MODEL SIZE (B)',
55
+ 'constant': 'TEXT SIZE',
56
+ 'conditional_entropy': 'NEGATIVE LOG-LIKELIHOOD',
57
  'BF16_TFLOPs': 'FLOPs (G)',
58
+ 'ic': ' INFORMATION CAPACITY',
59
  })
60
 
61
  // 数据集名称显示映射(raw data_name -> 显示名)
62
  const dataNameDisplayMap = reactive({
63
+ 'data_part_0000': 'Mixed text',
64
+ 'eng_Latn_000_00027_long': 'FinePDFs-en',
65
+ 'IndustryCorpus_batch_aa_long': 'Ch-FineWeb-Edu',
66
+ 'CC-MAIN-2013-20_train-00000-of-00014_long': 'FineWeb-Edu',
67
+ 'NextCoderDataset_v1_long': 'NextCoder',
68
  })
69
 
70
 
 
131
  return filtered
132
  })
133
 
134
+ // 点击表头切换排序:循环 降序 -> 升序
135
  function setSortKey(h) {
136
  if (!h) return
137
  if (globalState.sortKey !== h) {
 
139
  globalState.sortDesc = true
140
  return
141
  }
142
+ // same key, toggle between desc and asc
143
+ globalState.sortDesc = !globalState.sortDesc
 
 
 
 
 
 
144
  }
145
 
146
  // 计算当前应该显示的列(不含 rank)
 
166
  // 默认以最后一列升序排序(如果不存在则回退到 scoreKey),不直接在 rows 上预排序
167
  const defaultKey = headers.length > 0 ? headers[headers.length - 1] : scoreKey
168
  globalState.sortKey = defaultKey || ''
169
+ globalState.sortDesc = true
170
 
171
  // 特定处理
172
  // 判断模型开头是否在 MoEModelSeries 中,是则在 判断尾部是否为-A{number}B这样的格式
 
266
  const n = Number(raw)
267
  if (h === 'ic') {
268
  row._formatted[h] = Number.isFinite(n) ? n.toFixed(4) : raw
269
+ }
270
+ else if (h === 'constant') {
271
+ row._formatted[h] = Number.isFinite(n) ? n.toFixed(2) : raw
272
+ }
273
+ else {
274
  row._formatted[h] = Number.isFinite(n) ? n.toFixed(3) : raw
275
  }
276
  } else {
src/utils/mk2html.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 一个小型且安全的 Markdown -> HTML 解析器(支持标题、段落、链接、粗体、斜体、行内代码)
2
+ function escapeHtml(str) {
3
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
4
+ }
5
+
6
+ function parseInline(md) {
7
+ // code
8
+ md = md.replace(/`([^`]+)`/g, '<code>$1</code>')
9
+ // bold **text** or __text__
10
+ md = md.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
11
+ md = md.replace(/__([^_]+)__/g, '<strong>$1</strong>')
12
+ // italic *text* or _text_
13
+ md = md.replace(/\*([^*]+)\*/g, '<em>$1</em>')
14
+ md = md.replace(/_([^_]+)_/g, '<em>$1</em>')
15
+ // links [text](url)
16
+ md = md.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (m, text, url) => {
17
+ const safeUrl = url.replace(/\"/g, '%22')
18
+ return `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer" class="text-blue-600 underline dark:text-blue-400">${text}</a>`
19
+ })
20
+ return md
21
+ }
22
+
23
+ function parsedHeaderHtml(headerMarkdown){
24
+ const raw = headerMarkdown || ''
25
+ // split into lines and paragraphs
26
+ const blocks = raw.split(/\n{2,}/g)
27
+ const html = blocks.map(block => {
28
+ const line = block.trim()
29
+ if (!line) return ''
30
+ // heading
31
+ const hMatch = line.match(/^(#{1,6})\s+(.*)$/)
32
+ if (hMatch) {
33
+ const level = Math.min(6, hMatch[1].length)
34
+ const content = parseInline(escapeHtml(hMatch[2]))
35
+ // 增加块间距(mb-2)以扩大行之间的视觉间隔
36
+ return `<h${level} class="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-2">${content}</h${level}>`
37
+ }
38
+ // normal paragraph
39
+ // 使用 leading-relaxed 增加行高,并用 mb-2 增加段落间距
40
+ return `<p class="text-sm text-gray-700 dark:text-gray-200 leading-relaxed mb-2">${parseInline(escapeHtml(line))}</p>`
41
+ }).join('\n')
42
+ return html
43
+ }
44
+
45
+ export { parsedHeaderHtml }
src/views/Leaderboard.vue CHANGED
@@ -1,7 +1,9 @@
1
  <script setup>
2
- import { ref, onMounted, watch } from 'vue'
3
  import { useLeaderboardData } from '@/composables/useLeaderboardData.js'
4
  import Chart from '@/components/Chart.vue'
 
 
5
 
6
  // 使用数据管理 composable
7
  const {
@@ -37,11 +39,59 @@ const {
37
  // theme (dark mode) support: sync with document root class and localStorage
38
  const isDark = ref(false)
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  // 当 visibleColumns 改变时持久化
41
  watch(visibleColumns, (v) => {
42
  try { localStorage.setItem('visible_columns', JSON.stringify(v)) } catch (e) { }
43
  }, { deep: true })
44
 
 
 
 
 
 
 
 
 
 
 
45
  onMounted(() => {
46
  init()
47
  })
@@ -68,10 +118,6 @@ watch(isDark, (val) => {
68
  }
69
  })
70
 
71
- function toggleTheme() {
72
- isDark.value = !isDark.value
73
- }
74
-
75
  function clearModelTypeSelection() {
76
  selectAllModelTypes()
77
  }
@@ -81,28 +127,34 @@ function clearModelTypeSelection() {
81
  <div
82
  class="min-h-screen bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:via-gray-800 dark:to-gray-800 py-12 px-4 sm:px-6 lg:px-8">
83
  <div class=" mx-auto">
84
- <header class="mb-6 flex items-start justify-between">
85
  <div>
86
- <h1 class="text-3xl font-extrabold text-gray-900 dark:text-gray-100">LLM Information Capacity</h1>
87
- <p class="mt-1 text-sm text-gray-600 dark:text-gray-300">LLM IC Leaderboard Demo</p>
 
 
 
 
 
 
 
88
  </div>
89
  </header>
90
 
91
 
92
-
93
  <div class="bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
94
  <div
95
  class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
96
  <div class="flex items-center justify-between">
97
  <h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">Leaderboard</h2>
98
- <div class="text-sm text-gray-500 dark:text-gray-300">Last updated on xx/xx/xxxx</div>
99
  </div>
100
  </div>
101
 
102
  <!-- 数据集筛选区域(独立于列筛选) -->
103
  <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
104
  <div class="flex items-center gap-3">
105
- <span class="text-sm font-medium text-gray-700 dark:text-gray-200">数据集筛选</span>
106
  <div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
107
  <!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
108
  <input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
@@ -121,7 +173,7 @@ function clearModelTypeSelection() {
121
  <!-- 模型类型筛选区域 -->
122
  <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
123
  <div class="flex items-center gap-3">
124
- <span class="text-sm font-medium text-gray-700 dark:text-gray-200">模型类型筛选</span>
125
  <div class="flex items-center gap-2">
126
  <button @click="clearModelTypeSelection"
127
  class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">
@@ -148,7 +200,7 @@ function clearModelTypeSelection() {
148
  <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
149
  <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
150
  <div class="flex items-center gap-3">
151
- <span class="text-sm font-medium text-gray-700 dark:text-gray-200">显示列</span>
152
  <div class="flex items-center gap-2">
153
  <button @click.prevent="selectAll"
154
  class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">全选</button>
@@ -179,7 +231,7 @@ function clearModelTypeSelection() {
179
  #</th> -->
180
  <th v-for="h in displayedColumns" :key="h"
181
  @click="setSortKey(h)"
182
- class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer select-none">
183
  <div class="flex items-center gap-2">
184
  <span>{{ headerDisplayMap[h] || h }}</span>
185
  <span v-if="sortKey === h" class="text-xs text-gray-600 dark:text-gray-300">
@@ -207,22 +259,32 @@ function clearModelTypeSelection() {
207
  </div>
208
  </div>
209
 
210
- <Chart :autoShowSeries="autoShowSeries" />
211
- <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
212
- <div class="flex items-center gap-3">
213
- <span class="text-sm font-medium text-gray-700 dark:text-gray-200">数据集筛选</span>
214
- <div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
215
- <!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
216
- <input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
217
- </label> -->
218
- <label v-for="g in dataGroups" :key="g"
219
- class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer dark:text-slate-300"
220
- :class="{ 'bg-gray-100 dark:bg-gray-700 dark:text-white': selectedDataNameChart === g }">
221
- <input type="radio" class="hidden" :value="g" v-model="selectedDataNameChart" /> {{ dataNameDisplayMap[g]
222
- ? dataNameDisplayMap[g] : g }}
223
- </label>
 
 
 
 
 
 
224
  </div>
225
- <div v-else class="text-sm text-gray-500 dark:text-gray-400">(数据集中未检测到 data_name 列)</div>
 
 
 
 
226
  </div>
227
  </div>
228
  </div>
 
1
  <script setup>
2
+ import { ref, onMounted, watch, nextTick } from 'vue'
3
  import { useLeaderboardData } from '@/composables/useLeaderboardData.js'
4
  import Chart from '@/components/Chart.vue'
5
+ import { computed } from 'vue'
6
+ import { parsedHeaderHtml } from '@/utils/mk2html.js'
7
 
8
  // 使用数据管理 composable
9
  const {
 
39
  // theme (dark mode) support: sync with document root class and localStorage
40
  const isDark = ref(false)
41
 
42
+ // 图表区域ref
43
+ const chartSection = ref(null)
44
+
45
+ // 记录上一次选择的图表数据集,用于区分初始加载和用户点击
46
+ const lastSelectedDataNameChart = ref('')
47
+
48
+ // header markdown 内容
49
+ const headerMarkdown = ref(`
50
+
51
+ **Information Capacity — evaluating LLM efficiency via compression and compute**
52
+
53
+ Information Capacity evaluates a model's efficiency by measuring text compression gains relative to computational cost.
54
+ Larger models typically predict next tokens more accurately and thus achieve higher compression gains, but at increased compute expense.
55
+
56
+ By profiling compression performance across a family of models with different sizes, we obtain a stable Information Capacity curve that enables
57
+ fair cross-series comparison, series-internal performance prediction, and dynamic routing of models of different sizes according to
58
+ task difficulty — a capability especially useful in device-edge-cloud hierarchies such as the AI Flow framework.
59
+
60
+ A core difference from many existing efficiency metrics is that Information Capacity explicitly accounts for tokenizer efficiency.
61
+ An effective tokenizer can represent the same text with fewer tokens, reducing both input and output token counts, which lowers compute,
62
+ latency, and storage while enabling longer-context reasoning. As input lengths explode and test-time scaling becomes common,
63
+ tokenizer efficiency matters increasingly but is often overlooked in LLM evaluation.
64
+
65
+ We evaluate 49 models across 5 heterogeneous datasets and consistently observe the impacts of tokenizer design, pretraining data,
66
+ and mixture-of-experts (MoE) architectures on Information Capacity.
67
+
68
+ If you want to add your model to the leaderboard, please submit a PR
69
+ at [TeleAI AI Flow InformationCapacity](https://github.com/TeleAI-AI-Flow/InformationCapacity), and we will assist with evaluation and updates.
70
+
71
+ `)
72
+
73
+ const title = 'Information Capacity Leaderboard'
74
+
75
+ const lastUpdatedTime = '11/12/2025 15:30'
76
+
77
+ const headerInfo = parsedHeaderHtml(headerMarkdown.value)
78
+
79
+
80
  // 当 visibleColumns 改变时持久化
81
  watch(visibleColumns, (v) => {
82
  try { localStorage.setItem('visible_columns', JSON.stringify(v)) } catch (e) { }
83
  }, { deep: true })
84
 
85
+ // 当图表数据集变化时,滚动到图表区域底部(仅当用户点击时)
86
+ watch(selectedDataNameChart, (newVal) => {
87
+ if (lastSelectedDataNameChart.value && lastSelectedDataNameChart.value !== newVal) {
88
+ nextTick(() => {
89
+ chartSection.value?.scrollIntoView({ behavior: 'smooth', block: 'end' })
90
+ })
91
+ }
92
+ lastSelectedDataNameChart.value = newVal
93
+ })
94
+
95
  onMounted(() => {
96
  init()
97
  })
 
118
  }
119
  })
120
 
 
 
 
 
121
  function clearModelTypeSelection() {
122
  selectAllModelTypes()
123
  }
 
127
  <div
128
  class="min-h-screen bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:via-gray-800 dark:to-gray-800 py-12 px-4 sm:px-6 lg:px-8">
129
  <div class=" mx-auto">
130
+ <header class="mb-4 flex items-start justify-between">
131
  <div>
132
+ <h1 class="text-3xl font-extrabold text-gray-900 dark:text-gray-100">{{ title }}</h1>
133
+ <!-- <p class="mt-1 text-sm text-gray-600 dark:text-gray-300">LLM IC Leaderboard Demo</p> -->
134
+ <div v-html="headerInfo" class="py-7"></div>
135
+ </div>
136
+ <div class="flex items-center">
137
+ <!-- 固定正方形容器,内部图片保持纵横比且居中 -->
138
+ <div class="h-32 w-32 flex items-center justify-center rounded-md overflow-hidden p-2">
139
+ <img src="./logo.png" alt="Logo" class="max-h-full max-w-full object-contain" />
140
+ </div>
141
  </div>
142
  </header>
143
 
144
 
 
145
  <div class="bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
146
  <div
147
  class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
148
  <div class="flex items-center justify-between">
149
  <h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">Leaderboard</h2>
150
+ <div class="text-sm text-gray-500 dark:text-gray-300">Last updated on {{ lastUpdatedTime }}</div>
151
  </div>
152
  </div>
153
 
154
  <!-- 数据集筛选区域(独立于列筛选) -->
155
  <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
156
  <div class="flex items-center gap-3">
157
+ <span class="text-sm font-medium text-gray-700 dark:text-gray-200">Dataset</span>
158
  <div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
159
  <!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
160
  <input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
 
173
  <!-- 模型类型筛选区域 -->
174
  <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
175
  <div class="flex items-center gap-3">
176
+ <span class="text-sm font-medium text-gray-700 dark:text-gray-200">Model Series</span>
177
  <div class="flex items-center gap-2">
178
  <button @click="clearModelTypeSelection"
179
  class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">
 
200
  <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
201
  <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
202
  <div class="flex items-center gap-3">
203
+ <span class="text-sm font-medium text-gray-700 dark:text-gray-200">Visible Columns</span>
204
  <div class="flex items-center gap-2">
205
  <button @click.prevent="selectAll"
206
  class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">全选</button>
 
231
  #</th> -->
232
  <th v-for="h in displayedColumns" :key="h"
233
  @click="setSortKey(h)"
234
+ class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300 tracking-wider cursor-pointer select-none">
235
  <div class="flex items-center gap-2">
236
  <span>{{ headerDisplayMap[h] || h }}</span>
237
  <span v-if="sortKey === h" class="text-xs text-gray-600 dark:text-gray-300">
 
259
  </div>
260
  </div>
261
 
262
+ <!-- 图表区域 -->
263
+ <div ref="chartSection" class="mt-8 bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
264
+ <!-- 图表头部 -->
265
+ <div class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
266
+ <h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">Data visualization</h2>
267
+ </div>
268
+
269
+ <!-- 图表数据集筛选区域 -->
270
+ <div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
271
+ <div class="flex items-center gap-3">
272
+ <span class="text-sm font-medium text-gray-700 dark:text-gray-200">Dataset</span>
273
+ <div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
274
+ <label v-for="g in dataGroups" :key="g"
275
+ class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer dark:text-slate-300"
276
+ :class="{ 'bg-gray-100 dark:bg-gray-700 dark:text-white': selectedDataNameChart === g }">
277
+ <input type="radio" class="hidden" :value="g" v-model="selectedDataNameChart" /> {{ dataNameDisplayMap[g]
278
+ ? dataNameDisplayMap[g] : g }}
279
+ </label>
280
+ </div>
281
+ <div v-else class="text-sm text-gray-500 dark:text-gray-400">(数据集中未检测到 data_name 列)</div>
282
  </div>
283
+ </div>
284
+
285
+ <!-- 图表内容 -->
286
+ <div class="p-6">
287
+ <Chart :autoShowSeries="autoShowSeries" />
288
  </div>
289
  </div>
290
  </div>
src/views/logo.png ADDED
tsconfig.node.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "extends": "@tsconfig/node22/tsconfig.json",
3
- "include": [
4
- "vite.config.*",
5
- "vitest.config.*",
6
- "cypress.config.*",
7
- "nightwatch.conf.*",
8
- "playwright.config.*",
9
- "eslint.config.*"
10
- ],
11
- "compilerOptions": {
12
- "noEmit": true,
13
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
14
-
15
- "module": "ESNext",
16
- "moduleResolution": "Bundler",
17
- "types": ["node"]
18
- }
19
- }