Skip to content

Commit 56b6fce

Browse files
committed
♻️ refactor: convert data browser from dynamic to build-time static Markdown
Replace JavaScript-based dynamic data loading with build-time static Markdown generation for better performance, SEO, and native MkDocs integration. ### Major Changes: **Build-time Markdown Generation:** - Add `generateDataMarkdown()` function to build script - Automatically generate native Markdown tables for all providers and models - Include provider documentation links and model capabilities - Generate real-time statistics from API manifest data **Static Content Benefits:** - Remove all custom CSS and JavaScript from data browser page - Eliminate client-side data fetching and DOM manipulation - Full compatibility with MkDocs search and navigation - Better SEO with static, indexable content **Developer Experience:** - Convert `docs/data.md` to placeholder with clear instructions - Prevent accidental manual editing with descriptive comments - Maintain automatic data updates through build process ### Technical Implementation: **New Build Functions:** ```javascript // Generate complete Markdown with provider sections and model tables generateDataMarkdown(allModelsData, providerIndex, modelIndex, manifest) // Write Markdown files with change detection writeMarkdownIfChanged(filePath, content, options) ``` **Content Structure:** - MkDocs Material admonitions for statistics and usage info - Native Markdown tables with pricing and capability data - Provider sections with API/documentation links - Emoji indicators for model capabilities (📎🧠🔧) **Performance Improvements:** - Zero JavaScript runtime overhead - Static content loads instantly - Full MkDocs search integration - Mobile-responsive native table styling This architectural change transforms the data browser from a client-side application to a statically generated documentation page, significantly improving performance while maintaining all functionality and data freshness.
1 parent ab77dc9 commit 56b6fce

File tree

2 files changed

+107
-284
lines changed

2 files changed

+107
-284
lines changed

docs/data.md

Lines changed: 8 additions & 284 deletions
Original file line numberDiff line numberDiff line change
@@ -1,287 +1,11 @@
1-
# 数据浏览
1+
# Data Browser
22

3-
<div id="loading" style="text-align: center; padding: 20px;">
4-
<p>正在加载数据...</p>
5-
</div>
3+
This page is automatically generated during the build process. No manual editing required.
64

7-
<div id="error" style="display: none; text-align: center; padding: 20px; color: red;">
8-
<p>加载数据失败,请稍后重试。</p>
9-
</div>
5+
The build script will populate this file with:
6+
- Provider and model statistics
7+
- Complete provider listings with API documentation links
8+
- Model tables with pricing and capabilities information
9+
- All data sourced from the latest API endpoints
1010

11-
<div id="data-container" style="display: none;">
12-
13-
<!-- 统计信息 -->
14-
<div id="stats" style="margin-bottom: 30px;">
15-
<div style="display: flex; gap: 20px; flex-wrap: wrap;">
16-
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; flex: 1; min-width: 200px;">
17-
<h3 style="margin: 0 0 5px 0; color: #495057;">提供商数量</h3>
18-
<p style="margin: 0; font-size: 24px; font-weight: bold; color: #007bff;" id="provider-count">-</p>
19-
</div>
20-
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; flex: 1; min-width: 200px;">
21-
<h3 style="margin: 0 0 5px 0; color: #495057;">模型数量</h3>
22-
<p style="margin: 0; font-size: 24px; font-weight: bold; color: #28a745;" id="model-count">-</p>
23-
</div>
24-
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; flex: 1; min-width: 200px;">
25-
<h3 style="margin: 0 0 5px 0; color: #495057;">最后更新</h3>
26-
<p style="margin: 0; font-size: 14px; color: #6c757d;" id="last-updated">-</p>
27-
</div>
28-
</div>
29-
</div>
30-
31-
<!-- 搜索和筛选 -->
32-
<div style="margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
33-
<input type="text" id="search-input" placeholder="搜索提供商或模型..."
34-
style="flex: 1; min-width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px;">
35-
<select id="provider-filter" style="padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px;">
36-
<option value="">所有提供商</option>
37-
</select>
38-
<label style="display: flex; align-items: center; gap: 5px;">
39-
<input type="checkbox" id="has-pricing"> 仅显示有定价信息的模型
40-
</label>
41-
</div>
42-
43-
<!-- 提供商列表 -->
44-
<div id="providers-list">
45-
</div>
46-
47-
</div>
48-
49-
<script>
50-
let allData = {};
51-
let filteredData = {};
52-
53-
// 获取正确的 API 基础路径
54-
function getApiBasePath() {
55-
// 检测是否在 GitHub Pages 部署环境
56-
if (window.location.hostname === 'basellm.github.io') {
57-
return '/llm-metadata/api';
58-
}
59-
60-
// 本地开发环境
61-
return './api';
62-
}
63-
64-
// 加载数据
65-
async function loadData() {
66-
try {
67-
const apiBasePath = getApiBasePath();
68-
const [indexResponse, manifestResponse] = await Promise.all([
69-
fetch(`${apiBasePath}/index.json`),
70-
fetch(`${apiBasePath}/manifest.json`)
71-
]);
72-
73-
const indexData = await indexResponse.json();
74-
const manifestData = await manifestResponse.json();
75-
76-
// 加载完整数据
77-
const allResponse = await fetch(`${apiBasePath}/all.json`);
78-
const allModelsData = await allResponse.json();
79-
80-
allData = {
81-
index: indexData,
82-
manifest: manifestData,
83-
models: allModelsData
84-
};
85-
86-
showData();
87-
} catch (error) {
88-
console.error('Failed to load data:', error);
89-
document.getElementById('loading').style.display = 'none';
90-
document.getElementById('error').style.display = 'block';
91-
}
92-
}
93-
94-
// 显示数据
95-
function showData() {
96-
document.getElementById('loading').style.display = 'none';
97-
document.getElementById('data-container').style.display = 'block';
98-
99-
// 更新统计信息
100-
const stats = allData.manifest.stats;
101-
document.getElementById('provider-count').textContent = stats.providers;
102-
document.getElementById('model-count').textContent = stats.models;
103-
document.getElementById('last-updated').textContent = new Date(allData.manifest.generatedAt).toLocaleString('zh-CN');
104-
105-
// 填充提供商筛选器
106-
const providerFilter = document.getElementById('provider-filter');
107-
allData.index.providers.forEach(provider => {
108-
const option = document.createElement('option');
109-
option.value = provider.id;
110-
option.textContent = `${provider.name} (${provider.modelCount})`;
111-
providerFilter.appendChild(option);
112-
});
113-
114-
// 初始显示所有数据
115-
filterAndDisplayData();
116-
117-
// 绑定事件监听器
118-
document.getElementById('search-input').addEventListener('input', filterAndDisplayData);
119-
document.getElementById('provider-filter').addEventListener('change', filterAndDisplayData);
120-
document.getElementById('has-pricing').addEventListener('change', filterAndDisplayData);
121-
}
122-
123-
// 筛选和显示数据
124-
function filterAndDisplayData() {
125-
const searchTerm = document.getElementById('search-input').value.toLowerCase();
126-
const selectedProvider = document.getElementById('provider-filter').value;
127-
const hasPricing = document.getElementById('has-pricing').checked;
128-
129-
const providersContainer = document.getElementById('providers-list');
130-
providersContainer.innerHTML = '';
131-
132-
// 筛选提供商
133-
const filteredProviders = allData.index.providers.filter(provider => {
134-
if (selectedProvider && provider.id !== selectedProvider) return false;
135-
if (searchTerm && !provider.name.toLowerCase().includes(searchTerm) && !provider.id.toLowerCase().includes(searchTerm)) return false;
136-
return true;
137-
});
138-
139-
filteredProviders.forEach(provider => {
140-
const providerData = allData.models[provider.id];
141-
if (!providerData || !providerData.models) return;
142-
143-
// 筛选模型
144-
const models = Object.entries(providerData.models).filter(([modelId, model]) => {
145-
if (searchTerm && !model.name.toLowerCase().includes(searchTerm) && !modelId.toLowerCase().includes(searchTerm)) return false;
146-
if (hasPricing && (!model.cost || !model.cost.input)) return false;
147-
return true;
148-
});
149-
150-
if (models.length === 0) return;
151-
152-
// 创建提供商卡片
153-
const providerCard = createProviderCard(provider, providerData, models);
154-
providersContainer.appendChild(providerCard);
155-
});
156-
157-
if (filteredProviders.length === 0) {
158-
providersContainer.innerHTML = '<p style="text-align: center; color: #6c757d; padding: 40px;">没有找到匹配的数据</p>';
159-
}
160-
}
161-
162-
// 创建提供商卡片
163-
function createProviderCard(provider, providerData, models) {
164-
const card = document.createElement('div');
165-
card.style.cssText = 'border: 1px solid #ddd; border-radius: 8px; margin-bottom: 20px; overflow: hidden; background: white;';
166-
167-
const header = document.createElement('div');
168-
header.style.cssText = 'background: #f8f9fa; padding: 15px; border-bottom: 1px solid #ddd;';
169-
170-
const title = document.createElement('h3');
171-
title.style.cssText = 'margin: 0; color: #333;';
172-
title.innerHTML = `${provider.name} <span style="color: #6c757d; font-size: 14px; font-weight: normal;">(${models.length} 个模型)</span>`;
173-
174-
const links = document.createElement('div');
175-
links.style.cssText = 'margin-top: 8px;';
176-
if (providerData.api) {
177-
links.innerHTML += `<a href="${providerData.api}" target="_blank" style="margin-right: 15px; color: #007bff; text-decoration: none;">API 文档</a>`;
178-
}
179-
if (providerData.doc) {
180-
links.innerHTML += `<a href="${providerData.doc}" target="_blank" style="color: #007bff; text-decoration: none;">官方文档</a>`;
181-
}
182-
183-
header.appendChild(title);
184-
header.appendChild(links);
185-
186-
const body = document.createElement('div');
187-
body.style.cssText = 'padding: 15px;';
188-
189-
// 创建模型表格
190-
const table = createModelsTable(models);
191-
body.appendChild(table);
192-
193-
card.appendChild(header);
194-
card.appendChild(body);
195-
196-
return card;
197-
}
198-
199-
// 创建模型表格
200-
function createModelsTable(models) {
201-
const table = document.createElement('table');
202-
table.style.cssText = 'width: 100%; border-collapse: collapse; font-size: 14px;';
203-
204-
// 表头
205-
const thead = document.createElement('thead');
206-
thead.innerHTML = `
207-
<tr style="background: #f8f9fa;">
208-
<th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">模型名称</th>
209-
<th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">描述</th>
210-
<th style="padding: 8px; text-align: center; border-bottom: 1px solid #ddd;">定价</th>
211-
<th style="padding: 8px; text-align: center; border-bottom: 1px solid #ddd;">能力</th>
212-
</tr>
213-
`;
214-
215-
// 表体
216-
const tbody = document.createElement('tbody');
217-
models.forEach(([modelId, model]) => {
218-
const row = document.createElement('tr');
219-
row.style.cssText = 'border-bottom: 1px solid #f1f1f1;';
220-
221-
// 模型名称
222-
const nameCell = document.createElement('td');
223-
nameCell.style.cssText = 'padding: 8px; font-weight: 600;';
224-
nameCell.textContent = model.name || modelId;
225-
226-
// 描述
227-
const descCell = document.createElement('td');
228-
descCell.style.cssText = 'padding: 8px; max-width: 300px; word-wrap: break-word;';
229-
descCell.textContent = model.description || '-';
230-
231-
// 定价
232-
const priceCell = document.createElement('td');
233-
priceCell.style.cssText = 'padding: 8px; text-align: center; font-family: monospace;';
234-
if (model.cost && model.cost.input) {
235-
priceCell.innerHTML = `
236-
<div style="font-size: 12px;">
237-
<div>输入: $${model.cost.input}/1M</div>
238-
<div>输出: $${model.cost.output || '-'}/1M</div>
239-
</div>
240-
`;
241-
} else {
242-
priceCell.textContent = '-';
243-
}
244-
245-
// 能力
246-
const capabilityCell = document.createElement('td');
247-
capabilityCell.style.cssText = 'padding: 8px; text-align: center;';
248-
const capabilities = [];
249-
if (model.attachment) capabilities.push('📎');
250-
if (model.reasoning) capabilities.push('🧠');
251-
if (model.tool_call) capabilities.push('🔧');
252-
capabilityCell.innerHTML = capabilities.length > 0 ? capabilities.join(' ') : '-';
253-
254-
row.appendChild(nameCell);
255-
row.appendChild(descCell);
256-
row.appendChild(priceCell);
257-
row.appendChild(capabilityCell);
258-
259-
tbody.appendChild(row);
260-
});
261-
262-
table.appendChild(thead);
263-
table.appendChild(tbody);
264-
265-
return table;
266-
}
267-
268-
// 页面加载完成后执行
269-
document.addEventListener('DOMContentLoaded', loadData);
270-
</script>
271-
272-
<style>
273-
/* 响应式样式 */
274-
@media (max-width: 768px) {
275-
table {
276-
font-size: 12px !important;
277-
}
278-
279-
th, td {
280-
padding: 4px !important;
281-
}
282-
283-
.stats-container > div {
284-
min-width: 150px !important;
285-
}
286-
}
287-
</style>
11+
To regenerate this page, run: `npm run build`

0 commit comments

Comments
 (0)