Skip to content

Commit c8a0958

Browse files
committed
✨ feat(docs): add "Recent Releases" page and update zh nav label for "Data Browser" to "模型库"
- Navigation: - Add top-level "Recent Releases" (zh: "最新发布") to mkdocs nav - Update zh translation for "Data Browser": "数据浏览器" → "模型库" - Documentation generation: - Implement generateReleasesMarkdown to list all models sorted by release_date (desc) - Robust date parsing (YYYY / YYYY-MM / YYYY-MM-DD); unknown dates sorted last - Build now writes docs/<locale>/releases.md for each locale - i18n: - Add keys: title.releases, intro.releases, table.released, table.provider - zh: set title.data → "模型库" (en remains "Data Browser") - Types/Lint: - Adjust types to satisfy exactOptionalPropertyTypes in releases generator Affected files: - mkdocs.yml - i18n/docs/en.json - i18n/docs/zh.json - src/services/docs-generator.ts - src/build.ts No breaking changes. Site nav shows zh "模型库" and a new "Recent Releases" page.
1 parent 53ede6a commit c8a0958

5 files changed

Lines changed: 177 additions & 5 deletions

File tree

i18n/docs/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"legend.temperature": "Temperature",
1212
"legend.attachment": "Attachment",
1313
"table.model": "Model",
14+
"table.provider": "Provider",
1415
"table.modelId": "Model ID",
1516
"table.context": "Context",
1617
"table.output": "Output",
@@ -22,4 +23,8 @@
2223
"table.details": "Details",
2324
"link.api": "📖 API Address",
2425
"link.doc": "📚 Official Documentation"
26+
,
27+
"title.releases": "Recent Releases",
28+
"intro.releases": "Models across all providers sorted by Released date (desc).",
29+
"table.released": "Released"
2530
}

i18n/docs/zh.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"title.data": "数据浏览器",
2+
"title.data": "模型库",
33
"intro.data": "本页根据 API 数据自动生成,展示所有大模型提供商与模型的综合信息。",
44
"stats.title": "统计",
55
"stats.providers": "提供商数量",
@@ -11,6 +11,7 @@
1111
"legend.temperature": "温度",
1212
"legend.attachment": "附件",
1313
"table.model": "模型",
14+
"table.provider": "提供商",
1415
"table.modelId": "模型 ID",
1516
"table.context": "上下文",
1617
"table.output": "输出",
@@ -22,4 +23,8 @@
2223
"table.details": "详情",
2324
"link.api": "📖 API 地址",
2425
"link.doc": "📚 官方文档"
26+
,
27+
"title.releases": "最新发布",
28+
"intro.releases": "按发布时间(Released)降序展示全站模型。",
29+
"table.released": "发布于"
2530
}

mkdocs.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ plugins:
7575
site_name: 'LLM 元数据'
7676
nav_translations:
7777
Home: 首页
78-
Data Browser: 数据浏览器
78+
Data Browser: 模型库
79+
Recent Releases: 最新发布
7980
Submit Model: 提交模型
8081
- locale: ja
8182
name: 日本語
@@ -169,4 +170,5 @@ markdown_extensions:
169170
nav:
170171
- Home: index.md
171172
- Data Browser: data.md
173+
- Recent Releases: releases.md
172174
- Submit Model: submit.md

src/build.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -503,14 +503,24 @@ class Builder {
503503
console.log('Generating documentation...');
504504
const locales = this.i18nService.getLocales().map((l: { locale: string }) => l.locale);
505505
for (const locale of locales) {
506-
const md = this.docsGenerator.generateDataMarkdown(
506+
const dataMd = this.docsGenerator.generateDataMarkdown(
507507
allModelsData,
508508
indexes.providers,
509509
manifest,
510510
locale,
511511
);
512-
const outPath = join(this.ROOT, 'docs', locale, 'data.md');
513-
if (writeTextIfChanged(outPath, md, { dryRun })) {
512+
const dataOut = join(this.ROOT, 'docs', locale, 'data.md');
513+
if (writeTextIfChanged(dataOut, dataMd, { dryRun })) {
514+
changes++;
515+
}
516+
517+
const releasesMd = this.docsGenerator.generateReleasesMarkdown(
518+
this.dataProcessor.localizeNormalizedData(allModelsData, overrides, locale),
519+
manifest,
520+
locale,
521+
);
522+
const releasesOut = join(this.ROOT, 'docs', locale, 'releases.md');
523+
if (writeTextIfChanged(releasesOut, releasesMd, { dryRun })) {
514524
changes++;
515525
}
516526
}

src/services/docs-generator.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ export class DocumentationGenerator {
2121
this.i18n = new I18nService(rootDir);
2222
}
2323

24+
/** 将 YYYY 或 YYYY-MM 或 YYYY-MM-DD 字符串解析为时间戳(不可解析返回 null) */
25+
private parseDateToTimestamp(dateStr?: string): number | null {
26+
if (!dateStr || typeof dateStr !== 'string') return null;
27+
// 规范化为完整日期,优先使用原字符串可被 Date 解析
28+
const direct = Date.parse(dateStr);
29+
if (!Number.isNaN(direct)) return direct;
30+
// 补全到月初/日:支持 "YYYY"、"YYYY-MM"
31+
if (/^\d{4}$/.test(dateStr)) {
32+
const ts = Date.parse(`${dateStr}-01-01`);
33+
return Number.isNaN(ts) ? null : ts;
34+
}
35+
if (/^\d{4}-\d{2}$/.test(dateStr)) {
36+
const ts = Date.parse(`${dateStr}-01`);
37+
return Number.isNaN(ts) ? null : ts;
38+
}
39+
return null;
40+
}
41+
2442
/** 计算 NewAPI 比率(文档用) */
2543
private calculateNewApiRatios(cost?: {
2644
input?: number;
@@ -173,4 +191,136 @@ ${tr('intro.data')}
173191

174192
return markdown;
175193
}
194+
195+
/** 生成“最新发布” Markdown(全站按 release_date 降序) */
196+
generateReleasesMarkdown(
197+
allModelsData: NormalizedData,
198+
manifest: BuildManifest,
199+
locale: string = 'en',
200+
): string {
201+
const { stats } = manifest;
202+
const lastUpdated = new Date(manifest.generatedAt).toLocaleString(
203+
this.i18n.getDateLocale(locale),
204+
{ timeZone: this.i18n.getTimeZone(locale) },
205+
);
206+
207+
const messages = this.i18n.getDocMessages(locale);
208+
const tr = (key: string): string => messages[key] || key;
209+
210+
let markdown = `---
211+
hide:
212+
- navigation
213+
---
214+
215+
# ${tr('title.releases')}
216+
217+
${tr('intro.releases')}
218+
219+
!!! info "${tr('stats.title')}"
220+
- **${tr('stats.providers')}**: ${stats.providers}
221+
- **${tr('stats.models')}**: ${stats.models}
222+
- **${tr('stats.updated')}**: ${lastUpdated}
223+
224+
`;
225+
226+
type Row = {
227+
providerId: string;
228+
providerName: string;
229+
modelId: string;
230+
modelName: string;
231+
releaseRaw?: string | undefined;
232+
releaseTs: number | null;
233+
context?: number;
234+
output?: number;
235+
pricing: string;
236+
ratios: string;
237+
capabilities: string;
238+
knowledge?: string | undefined;
239+
modalities: string;
240+
details: string;
241+
};
242+
243+
const rows: Row[] = [];
244+
for (const [providerId, provider] of Object.entries(allModelsData.providers)) {
245+
const providerName = provider.name || providerId;
246+
for (const [modelId, model] of Object.entries(provider.models || {})) {
247+
const releaseRaw = model.release_date || undefined;
248+
const releaseTs = this.parseDateToTimestamp(releaseRaw);
249+
const row: Row = {
250+
providerId,
251+
providerName,
252+
modelId,
253+
modelName: model.name || modelId,
254+
releaseRaw,
255+
releaseTs,
256+
pricing: formatPricing(model.cost),
257+
ratios: this.formatNewApiRatios(this.calculateNewApiRatios(model.cost)),
258+
capabilities: formatCapabilities(model),
259+
knowledge: model.knowledge,
260+
modalities: formatModalities(model.modalities),
261+
details: formatDetails(model),
262+
};
263+
if (typeof model.limit?.context === 'number') {
264+
row.context = model.limit.context;
265+
}
266+
if (typeof model.limit?.output === 'number') {
267+
row.output = model.limit.output;
268+
}
269+
rows.push(row);
270+
}
271+
}
272+
273+
rows.sort((a, b) => {
274+
const at = a.releaseTs ?? -Infinity;
275+
const bt = b.releaseTs ?? -Infinity;
276+
return bt - at; // 降序(新 → 旧 → 未知)
277+
});
278+
279+
const headers = [
280+
tr('table.model'),
281+
tr('table.provider'),
282+
tr('table.modelId'),
283+
tr('table.released'),
284+
tr('table.pricing'),
285+
tr('table.ratios'),
286+
tr('table.capabilities'),
287+
tr('table.knowledge'),
288+
tr('table.modalities'),
289+
tr('table.details'),
290+
];
291+
const separators = [
292+
'-------',
293+
'--------',
294+
'--------',
295+
'--------',
296+
'----------------',
297+
'---------------',
298+
'--------------',
299+
'-----------',
300+
'------------',
301+
'----------',
302+
];
303+
304+
markdown += `| ${headers.join(' | ')} |\n`;
305+
markdown += `|${separators.join('|')}|\n`;
306+
307+
for (const r of rows) {
308+
const fields = [
309+
`**${escapeMarkdownPipes(r.modelName)}**`,
310+
escapeMarkdownPipes(r.providerName),
311+
escapeMarkdownPipes(r.modelId),
312+
escapeMarkdownPipes(r.releaseRaw || '-'),
313+
r.pricing,
314+
r.ratios,
315+
r.capabilities,
316+
r.knowledge || '-',
317+
r.modalities,
318+
r.details,
319+
];
320+
markdown += `| ${fields.join(' | ')} |\n`;
321+
}
322+
323+
markdown += '\n';
324+
return markdown;
325+
}
176326
}

0 commit comments

Comments
 (0)