feat(webui): Add Models tab and refactor model configuration

- Add standalone Models tab with real-time quota/status display
- Move model identity table from Dashboard to Models tab
- Slim down Dashboard to KPI cards and charts only
- Dashboard charts now use unfiltered data (independent of Models filters)

Settings > Models improvements:
- Remove redundant Alias column (only Mapping is functional)
- Fix column misalignment bug (empty td)
- Add column widths and hidden row opacity styling
- Single row edit constraint (only one Mapping editable at a time)
- showHiddenModels toggle now only affects Settings (not Models tab)
- Update description text to match current functionality

i18n:
- Add 'models' and 'modelsPageDesc' keys (EN/ZH)
- Add 'modelMappingHint' for Claude CLI guidance
- Update 'modelsDesc' to reflect new functionality
This commit is contained in:
Wha1eChai
2026-01-09 04:39:05 +08:00
parent a4814b8c34
commit 40a766ded6
10 changed files with 1182 additions and 832 deletions

View File

@@ -439,7 +439,7 @@ window.Components.dashboard = () => ({
padding: 10,
displayColors: true,
callbacks: {
label: function(context) {
label: function (context) {
return context.dataset.label + ': ' + context.parsed.y;
}
}
@@ -531,7 +531,8 @@ window.Components.dashboard = () => ({
this.charts.quotaDistribution.destroy();
}
const rows = Alpine.store('data').quotaRows;
// Use UNFILTERED data for global health chart
const rows = Alpine.store('data').getUnfilteredQuotaData();
// Dynamic family aggregation (supports any model family)
const familyStats = {};
@@ -580,12 +581,12 @@ window.Components.dashboard = () => ({
// Labels using translations if possible
const activeLabel = family === 'claude' ? store.t('claudeActive') :
family === 'gemini' ? store.t('geminiActive') :
`${familyName} ${store.t('activeSuffix')}`;
family === 'gemini' ? store.t('geminiActive') :
`${familyName} ${store.t('activeSuffix')}`;
const depletedLabel = family === 'claude' ? store.t('claudeEmpty') :
family === 'gemini' ? store.t('geminiEmpty') :
`${familyName} ${store.t('depleted')}`;
family === 'gemini' ? store.t('geminiEmpty') :
`${familyName} ${store.t('depleted')}`;
// Active segment
data.push(activeVal);