@tzyc/dsh-model-visibility 1.10.2 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -15
- package/client-src/catalog-store.mjs +95 -18
- package/client-src/checker-view.mjs +158 -65
- package/client-src/css.mjs +153 -221
- package/client-src/dict.mjs +119 -81
- package/client-src/format.mjs +9 -2
- package/client-src/pure.mjs +24 -7
- package/client-src/settings-section.mjs +242 -137
- package/client-src/usage-view.mjs +69 -65
- package/client.js +869 -596
- package/index.js +4 -2
- package/lib/check-catalog.js +868 -108
- package/lib/export-csv.js +48 -14
- package/lib/learned-catalog.js +10 -2
- package/lib/model-info.js +4 -2
- package/lib/model-overrides.js +9 -4
- package/lib/provider-probe.js +138 -22
- package/lib/provider-write.js +31 -7
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const React = require('react')
|
|
2
2
|
const h = React.createElement
|
|
3
3
|
|
|
4
|
+
import { FIELD } from './constants.mjs'
|
|
4
5
|
import { keyOf, kindOf, isGenerationKind, providerLabel, countModels, readCollapsedGroups, persistCollapsedGroups, overrideKeyOf } from './format.mjs'
|
|
5
6
|
import { computeFilteredGroups, countKinds, probeOf } from './pure.mjs'
|
|
6
7
|
import { capabilityTags, tagClassOf } from './tags.mjs'
|
|
@@ -9,6 +10,33 @@ import { useHidden, useSource } from './hooks.mjs'
|
|
|
9
10
|
import { UsageToolbar } from './usage-view.mjs'
|
|
10
11
|
import { VerifyPanel } from './checker-view.mjs'
|
|
11
12
|
|
|
13
|
+
/** Browser-local key for the "about this page" disclosure. */
|
|
14
|
+
const ABOUT_KEY = 'dsh-model-visibility:about-open:v1'
|
|
15
|
+
|
|
16
|
+
function readAboutOpen() {
|
|
17
|
+
try {
|
|
18
|
+
return window.localStorage.getItem(ABOUT_KEY) === '1'
|
|
19
|
+
} catch (_error) {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function persistAboutOpen(open) {
|
|
25
|
+
try {
|
|
26
|
+
window.localStorage.setItem(ABOUT_KEY, open ? '1' : '0')
|
|
27
|
+
} catch (_error) {
|
|
28
|
+
// Storage is optional for a preference like this.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The settings page.
|
|
34
|
+
*
|
|
35
|
+
* Layout, in the order a user needs it: a one-line summary with the two
|
|
36
|
+
* primary actions, the token strip, the filter bar, the models, then the
|
|
37
|
+
* diagnostics — and behind a disclosure the long explanation that used to
|
|
38
|
+
* occupy a third of the viewport before the first model row.
|
|
39
|
+
*/
|
|
12
40
|
function makeSection(form, catalog, overrides, usage, verify) {
|
|
13
41
|
return function ModelVisibilitySection({ t }) {
|
|
14
42
|
const { hidden, snapshot } = useHidden(form)
|
|
@@ -19,6 +47,7 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
19
47
|
const [refreshing, setRefreshing] = React.useState(false)
|
|
20
48
|
const [reloading, setReloading] = React.useState(false)
|
|
21
49
|
const [collapsed, setCollapsed] = React.useState(readCollapsedGroups)
|
|
50
|
+
const [aboutOpen, setAboutOpen] = React.useState(readAboutOpen)
|
|
22
51
|
const [editing, setEditing] = React.useState(null)
|
|
23
52
|
const [providerNote, setProviderNote] = React.useState(null)
|
|
24
53
|
|
|
@@ -68,16 +97,18 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
68
97
|
if (disabled) return
|
|
69
98
|
setBusy(true)
|
|
70
99
|
setError(null)
|
|
71
|
-
Promise.resolve(form.set(field, value))
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
Promise.resolve(form.set(field, value))
|
|
101
|
+
.then(
|
|
102
|
+
(accepted) => {
|
|
103
|
+
if (accepted === false) setError(t('page.saveFailed'))
|
|
104
|
+
},
|
|
105
|
+
() => {
|
|
106
|
+
setError(t('page.saveFailed'))
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
.finally(() => {
|
|
110
|
+
setBusy(false)
|
|
111
|
+
})
|
|
81
112
|
}
|
|
82
113
|
|
|
83
114
|
const commit = (next) => writeField(FIELD, Array.from(next))
|
|
@@ -118,7 +149,7 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
118
149
|
// A hard reload may supersede a soft refresh that is still running;
|
|
119
150
|
// catalog.load() gives it a new run and discards the older result.
|
|
120
151
|
const reloadCatalog = () => {
|
|
121
|
-
if (busy || reloading) return
|
|
152
|
+
if (busy || reloading || refreshing) return
|
|
122
153
|
runCatalogAction(() => catalog.reload(), setReloading)
|
|
123
154
|
}
|
|
124
155
|
|
|
@@ -141,24 +172,31 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
141
172
|
})
|
|
142
173
|
}
|
|
143
174
|
|
|
175
|
+
const toggleAbout = () => {
|
|
176
|
+
setAboutOpen((previous) => {
|
|
177
|
+
persistAboutOpen(!previous)
|
|
178
|
+
return !previous
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ------------------------------------------------------------- groups */
|
|
183
|
+
|
|
144
184
|
const rows = []
|
|
145
185
|
for (const group of filteredGroups) {
|
|
146
186
|
const allGroupModels =
|
|
147
187
|
(groups.find((candidate) => candidate.id === group.id) || group).models || []
|
|
148
188
|
const models = group.models || []
|
|
149
|
-
const shown = allGroupModels.filter(
|
|
150
|
-
(model) => !hidden.has(keyOf(group.id, model.id)),
|
|
151
|
-
).length
|
|
189
|
+
const shown = allGroupModels.filter((model) => !hidden.has(keyOf(group.id, model.id))).length
|
|
152
190
|
const allShown = models.every((model) => !hidden.has(keyOf(group.id, model.id)))
|
|
153
191
|
const kinds = countKinds(allGroupModels)
|
|
154
192
|
const kindParts = []
|
|
155
|
-
if (kinds.chat > 0) kindParts.push(
|
|
156
|
-
if (kinds.image > 0) kindParts.push(
|
|
157
|
-
if (kinds.video > 0) kindParts.push(
|
|
158
|
-
if (kinds.other > 0) kindParts.push(
|
|
193
|
+
if (kinds.chat > 0) kindParts.push('chat')
|
|
194
|
+
if (kinds.image > 0) kindParts.push('image')
|
|
195
|
+
if (kinds.video > 0) kindParts.push('video')
|
|
196
|
+
if (kinds.other > 0) kindParts.push('other')
|
|
159
197
|
const kindNodes = []
|
|
160
|
-
for (const [index,
|
|
161
|
-
if (index > 0) kindNodes.push('
|
|
198
|
+
for (const [index, kindKey] of kindParts.entries()) {
|
|
199
|
+
if (index > 0) kindNodes.push(h('span', { key: 'sep-' + kindKey, className: 'mvp_sep' }, '·'))
|
|
162
200
|
kindNodes.push(
|
|
163
201
|
h(
|
|
164
202
|
'span',
|
|
@@ -167,8 +205,9 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
167
205
|
className:
|
|
168
206
|
'mvp_kindCount' +
|
|
169
207
|
(kindKey === 'chat' ? '' : ' mvp_kindCount--' + kindKey),
|
|
208
|
+
title: t('page.filter.' + kindKey) + ' ' + kinds[kindKey],
|
|
170
209
|
},
|
|
171
|
-
|
|
210
|
+
kinds[kindKey],
|
|
172
211
|
),
|
|
173
212
|
)
|
|
174
213
|
}
|
|
@@ -186,34 +225,37 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
186
225
|
'div',
|
|
187
226
|
{ className: 'mvp_groupHead' },
|
|
188
227
|
h(
|
|
189
|
-
'
|
|
190
|
-
{
|
|
191
|
-
|
|
192
|
-
'
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
onClick: () => toggleGroupCollapsed(group.id),
|
|
200
|
-
},
|
|
201
|
-
collapsedNow ? '▸' : '▾',
|
|
202
|
-
),
|
|
228
|
+
'button',
|
|
229
|
+
{
|
|
230
|
+
type: 'button',
|
|
231
|
+
className: 'mvp_groupTitle',
|
|
232
|
+
'aria-expanded': !collapsedNow,
|
|
233
|
+
'aria-label': collapseLabel,
|
|
234
|
+
title: collapseLabel,
|
|
235
|
+
onClick: () => toggleGroupCollapsed(group.id),
|
|
236
|
+
},
|
|
237
|
+
h('span', { className: 'mvp_chevron' }, collapsedNow ? '▸' : '▾'),
|
|
203
238
|
h('span', { className: 'mvp_groupName' }, groupName),
|
|
204
239
|
h('span', { className: 'mvp_groupCount' }, shown + '/' + allGroupModels.length),
|
|
205
240
|
kindParts.length > 0
|
|
206
241
|
? h('span', { className: 'mvp_groupCap' }, kindNodes)
|
|
207
242
|
: null,
|
|
208
243
|
probe !== null
|
|
209
|
-
? h(
|
|
244
|
+
? h(
|
|
245
|
+
'span',
|
|
246
|
+
{
|
|
247
|
+
className: 'mvp_groupProbe' + (probe.failed === true ? ' mvp_groupProbe--warn' : ''),
|
|
248
|
+
title: probe.title || undefined,
|
|
249
|
+
},
|
|
250
|
+
probe.label,
|
|
251
|
+
)
|
|
210
252
|
: null,
|
|
211
253
|
),
|
|
212
254
|
h(
|
|
213
255
|
'button',
|
|
214
256
|
{
|
|
215
257
|
type: 'button',
|
|
216
|
-
className: 'mvp_button',
|
|
258
|
+
className: 'mvp_button mvp_button--quiet',
|
|
217
259
|
disabled: disabled,
|
|
218
260
|
onClick: () => setGroup(group, models, !allShown),
|
|
219
261
|
},
|
|
@@ -245,15 +287,20 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
245
287
|
className:
|
|
246
288
|
'mvp_row' +
|
|
247
289
|
(generation ? ' mvp_row--gen' : '') +
|
|
290
|
+
(shownModel ? '' : ' mvp_row--off') +
|
|
248
291
|
kindTint,
|
|
249
292
|
},
|
|
250
293
|
h(
|
|
251
294
|
'div',
|
|
252
295
|
{ className: 'mvp_rowText' },
|
|
253
|
-
h(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
296
|
+
h(
|
|
297
|
+
'div',
|
|
298
|
+
{ className: 'mvp_rowHead' },
|
|
299
|
+
h('span', { className: 'mvp_rowTitle' }, model.name),
|
|
300
|
+
model.id === model.name
|
|
301
|
+
? null
|
|
302
|
+
: h('span', { className: 'mvp_rowMeta' }, model.id),
|
|
303
|
+
),
|
|
257
304
|
h(
|
|
258
305
|
'div',
|
|
259
306
|
{ className: 'mvp_tags' },
|
|
@@ -278,7 +325,7 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
278
325
|
'button',
|
|
279
326
|
{
|
|
280
327
|
type: 'button',
|
|
281
|
-
className: 'mvp_button mvp_editButton',
|
|
328
|
+
className: 'mvp_button mvp_button--quiet mvp_editButton',
|
|
282
329
|
'aria-pressed': editing === rowKey,
|
|
283
330
|
'aria-label': t('row.edit') + ': ' + model.name,
|
|
284
331
|
title: t('row.editTitle'),
|
|
@@ -326,9 +373,13 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
326
373
|
)
|
|
327
374
|
}
|
|
328
375
|
|
|
376
|
+
/* ------------------------------------------------------------- status */
|
|
377
|
+
|
|
329
378
|
const loading = state.status === 'loading' || state.status === 'idle'
|
|
330
379
|
const providerList = Array.isArray(state.providers) ? state.providers : []
|
|
331
|
-
const probeOk = providerList.filter(
|
|
380
|
+
const probeOk = providerList.filter(
|
|
381
|
+
(provider) => provider && (provider.status === 'ok' || provider.status === 'partial'),
|
|
382
|
+
).length
|
|
332
383
|
const probeCaps = providerList.reduce(
|
|
333
384
|
(count, provider) => count + (Number(provider && provider.capabilities) || 0),
|
|
334
385
|
0,
|
|
@@ -339,10 +390,7 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
339
390
|
? t('page.probeSummary', { ok: probeOk, caps: probeCaps })
|
|
340
391
|
: t('page.probeSummaryNone')
|
|
341
392
|
: null
|
|
342
|
-
// A page refresh can deliver this bundle while the Host half is still
|
|
343
|
-
// the previous version; the switch would look live but do nothing.
|
|
344
393
|
const hostStale = state.source === 'host' && !hostFeatures.includes('applyInput')
|
|
345
|
-
// A hard reload is only meaningful when the Host half understands it.
|
|
346
394
|
const canReload = state.source === 'host' && hostFeatures.includes('reload')
|
|
347
395
|
const anyCollapsed = groups.some((group) => collapsed.has(group.id))
|
|
348
396
|
const learnedDate =
|
|
@@ -367,54 +415,52 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
367
415
|
]
|
|
368
416
|
const hasActiveFilters =
|
|
369
417
|
filter !== 'all' || normalizedQuery !== '' || multimodalOnly || reasoningOnly || minContext > 0
|
|
418
|
+
// A relay that answered with a diagnostic is worth surfacing next to the
|
|
419
|
+
// group, but only failed routes belong in the page-level warning list.
|
|
420
|
+
const failedRoutes = providerList.filter(
|
|
421
|
+
(provider) => provider && (provider.status === 'failed' || provider.status === 'unauthorized'),
|
|
422
|
+
)
|
|
423
|
+
const summaryText =
|
|
424
|
+
total === 0
|
|
425
|
+
? loading
|
|
426
|
+
? t('page.loading')
|
|
427
|
+
: t('page.empty')
|
|
428
|
+
: t('page.summary', { visible: total - hiddenCount, total })
|
|
429
|
+
|
|
430
|
+
/* ------------------------------------------------------------- render */
|
|
370
431
|
|
|
371
432
|
return h(
|
|
372
433
|
'div',
|
|
373
434
|
{ className: 'mvp_page' },
|
|
374
|
-
|
|
435
|
+
// Title + summary + the two actions that are worth a permanent slot.
|
|
375
436
|
h(
|
|
376
437
|
'div',
|
|
377
438
|
{ className: 'mvp_head' },
|
|
378
|
-
h('h2', { className: 'mvp_title' }, t('page.title')),
|
|
379
|
-
h('p', { className: 'mvp_desc' }, t('page.description')),
|
|
380
|
-
),
|
|
381
|
-
h(
|
|
382
|
-
'div',
|
|
383
|
-
{ className: 'mvp_toolbar' },
|
|
384
439
|
h(
|
|
385
440
|
'div',
|
|
386
|
-
{ className: '
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
:
|
|
392
|
-
|
|
393
|
-
|
|
441
|
+
{ className: 'mvp_headMain' },
|
|
442
|
+
h('h2', { className: 'mvp_title' }, t('page.title')),
|
|
443
|
+
h('span', { className: 'mvp_summary' }, summaryText),
|
|
444
|
+
hiddenCount > 0
|
|
445
|
+
? h('span', { className: 'mvp_summary mvp_summary--dim' }, t('page.hiddenSummary', { count: hiddenCount }))
|
|
446
|
+
: null,
|
|
447
|
+
generationCount > 0
|
|
448
|
+
? h('span', { className: 'mvp_summary mvp_summary--dim' }, t('page.kindSummary', { count: generationCount }))
|
|
449
|
+
: null,
|
|
394
450
|
),
|
|
395
451
|
h(
|
|
396
452
|
'div',
|
|
397
|
-
{ className: '
|
|
453
|
+
{ className: 'mvp_headActions' },
|
|
398
454
|
h(
|
|
399
455
|
'button',
|
|
400
456
|
{
|
|
401
457
|
type: 'button',
|
|
402
|
-
className: 'mvp_button',
|
|
403
|
-
|
|
404
|
-
|
|
458
|
+
className: 'mvp_button mvp_button--quiet',
|
|
459
|
+
'aria-pressed': aboutOpen,
|
|
460
|
+
title: t('page.aboutTitle'),
|
|
461
|
+
onClick: toggleAbout,
|
|
405
462
|
},
|
|
406
|
-
t('page.
|
|
407
|
-
),
|
|
408
|
-
h(
|
|
409
|
-
'button',
|
|
410
|
-
{
|
|
411
|
-
type: 'button',
|
|
412
|
-
className: 'mvp_button',
|
|
413
|
-
disabled: groups.length === 0,
|
|
414
|
-
title: t(anyCollapsed ? 'page.expandGroupsTitle' : 'page.collapseGroupsTitle'),
|
|
415
|
-
onClick: toggleAllCollapsed,
|
|
416
|
-
},
|
|
417
|
-
t(anyCollapsed ? 'page.expandGroups' : 'page.collapseGroups'),
|
|
463
|
+
t('page.about') + (aboutOpen ? ' ▴' : ' ▾'),
|
|
418
464
|
),
|
|
419
465
|
h(
|
|
420
466
|
'button',
|
|
@@ -422,6 +468,7 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
422
468
|
type: 'button',
|
|
423
469
|
className: 'mvp_button',
|
|
424
470
|
disabled: busy || refreshing || reloading,
|
|
471
|
+
title: t('page.refreshTitle'),
|
|
425
472
|
onClick: refreshCatalog,
|
|
426
473
|
},
|
|
427
474
|
t(refreshing ? 'page.refreshing' : 'page.refresh'),
|
|
@@ -439,63 +486,77 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
439
486
|
),
|
|
440
487
|
),
|
|
441
488
|
),
|
|
489
|
+
aboutOpen
|
|
490
|
+
? h(
|
|
491
|
+
'div',
|
|
492
|
+
{ className: 'mvp_about' },
|
|
493
|
+
h('p', { className: 'mvp_aboutP' }, t('page.description')),
|
|
494
|
+
h('p', { className: 'mvp_aboutP' }, t('page.legend')),
|
|
495
|
+
h('p', { className: 'mvp_aboutP' }, t('page.kindLegend')),
|
|
496
|
+
h('p', { className: 'mvp_aboutP' }, t('page.manualNote')),
|
|
497
|
+
)
|
|
498
|
+
: null,
|
|
499
|
+
h(UsageToolbar, { usage, t, features: state.source === 'host' ? hostFeatures : [] }),
|
|
442
500
|
h(
|
|
443
501
|
'div',
|
|
444
502
|
{ className: 'mvp_filters' },
|
|
445
|
-
h('
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
503
|
+
h('input', {
|
|
504
|
+
className: 'mvp_searchInput',
|
|
505
|
+
type: 'search',
|
|
506
|
+
value: query,
|
|
507
|
+
placeholder: t('page.searchPlaceholder'),
|
|
508
|
+
'aria-label': t('page.searchPlaceholder'),
|
|
509
|
+
onInput: (event) => setQuery(event.target.value),
|
|
510
|
+
}),
|
|
511
|
+
h(
|
|
512
|
+
'div',
|
|
513
|
+
{ className: 'mvp_filterGroup' },
|
|
514
|
+
filterOptions.map(([value, label, count]) =>
|
|
515
|
+
h(
|
|
516
|
+
'button',
|
|
517
|
+
{
|
|
518
|
+
key: value,
|
|
519
|
+
type: 'button',
|
|
520
|
+
className: 'mvp_filter',
|
|
521
|
+
'aria-pressed': filter === value,
|
|
522
|
+
onClick: () => setFilter(value),
|
|
523
|
+
},
|
|
524
|
+
label + ' ' + count,
|
|
525
|
+
),
|
|
526
|
+
),
|
|
455
527
|
),
|
|
456
|
-
h(
|
|
457
|
-
|
|
528
|
+
h(
|
|
529
|
+
'div',
|
|
530
|
+
{ className: 'mvp_filterGroup' },
|
|
458
531
|
h(
|
|
459
532
|
'button',
|
|
460
533
|
{
|
|
461
|
-
key: value,
|
|
462
534
|
type: 'button',
|
|
463
535
|
className: 'mvp_filter',
|
|
464
|
-
'aria-pressed':
|
|
465
|
-
|
|
536
|
+
'aria-pressed': multimodalOnly,
|
|
537
|
+
title: t('page.capability.multimodalTitle'),
|
|
538
|
+
onClick: () => setMultimodalOnly(!multimodalOnly),
|
|
466
539
|
},
|
|
467
|
-
|
|
540
|
+
t('page.capability.multimodal'),
|
|
541
|
+
),
|
|
542
|
+
h(
|
|
543
|
+
'button',
|
|
544
|
+
{
|
|
545
|
+
type: 'button',
|
|
546
|
+
className: 'mvp_filter',
|
|
547
|
+
'aria-pressed': reasoningOnly,
|
|
548
|
+
title: t('page.capability.reasoningTitle'),
|
|
549
|
+
onClick: () => setReasoningOnly(!reasoningOnly),
|
|
550
|
+
},
|
|
551
|
+
t('page.capability.reasoning'),
|
|
468
552
|
),
|
|
469
|
-
),
|
|
470
|
-
h('span', { className: 'mvp_filterLabel' }, t('page.capabilityLabel')),
|
|
471
|
-
h(
|
|
472
|
-
'button',
|
|
473
|
-
{
|
|
474
|
-
type: 'button',
|
|
475
|
-
className: 'mvp_filter',
|
|
476
|
-
'aria-pressed': multimodalOnly,
|
|
477
|
-
onClick: () => setMultimodalOnly(!multimodalOnly),
|
|
478
|
-
},
|
|
479
|
-
t('page.capability.multimodal'),
|
|
480
|
-
),
|
|
481
|
-
h(
|
|
482
|
-
'button',
|
|
483
|
-
{
|
|
484
|
-
type: 'button',
|
|
485
|
-
className: 'mvp_filter',
|
|
486
|
-
'aria-pressed': reasoningOnly,
|
|
487
|
-
onClick: () => setReasoningOnly(!reasoningOnly),
|
|
488
|
-
},
|
|
489
|
-
t('page.capability.reasoning'),
|
|
490
|
-
),
|
|
491
|
-
h('label', { className: 'mvp_context' },
|
|
492
|
-
h('span', { className: 'mvp_filterLabel' }, t('page.contextLabel')),
|
|
493
553
|
h(
|
|
494
554
|
'select',
|
|
495
555
|
{
|
|
496
556
|
className: 'mvp_contextSelect',
|
|
497
557
|
value: String(minContext),
|
|
498
558
|
'aria-label': t('page.contextLabel'),
|
|
559
|
+
title: t('page.contextLabel'),
|
|
499
560
|
onChange: (event) => setMinContext(Number(event.target.value)),
|
|
500
561
|
},
|
|
501
562
|
[
|
|
@@ -507,6 +568,31 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
507
568
|
].map(([value, label]) => h('option', { key: value, value: String(value) }, t(label))),
|
|
508
569
|
),
|
|
509
570
|
),
|
|
571
|
+
h(
|
|
572
|
+
'div',
|
|
573
|
+
{ className: 'mvp_filterGroup mvp_filterGroup--push' },
|
|
574
|
+
h(
|
|
575
|
+
'button',
|
|
576
|
+
{
|
|
577
|
+
type: 'button',
|
|
578
|
+
className: 'mvp_button mvp_button--quiet',
|
|
579
|
+
disabled: disabled || hiddenCount === 0,
|
|
580
|
+
onClick: () => commit(new Set()),
|
|
581
|
+
},
|
|
582
|
+
t('page.showAll'),
|
|
583
|
+
),
|
|
584
|
+
h(
|
|
585
|
+
'button',
|
|
586
|
+
{
|
|
587
|
+
type: 'button',
|
|
588
|
+
className: 'mvp_button mvp_button--quiet',
|
|
589
|
+
disabled: groups.length === 0,
|
|
590
|
+
title: t(anyCollapsed ? 'page.expandGroupsTitle' : 'page.collapseGroupsTitle'),
|
|
591
|
+
onClick: toggleAllCollapsed,
|
|
592
|
+
},
|
|
593
|
+
t(anyCollapsed ? 'page.expandGroups' : 'page.collapseGroups'),
|
|
594
|
+
),
|
|
595
|
+
),
|
|
510
596
|
),
|
|
511
597
|
h(
|
|
512
598
|
'div',
|
|
@@ -519,6 +605,7 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
519
605
|
className: 'mvp_switch',
|
|
520
606
|
'aria-checked': applyInput,
|
|
521
607
|
'aria-label': t('page.applyInput'),
|
|
608
|
+
'aria-describedby': 'mvp-apply-hint',
|
|
522
609
|
disabled: disabled,
|
|
523
610
|
onClick: () => commitApply(!applyInput),
|
|
524
611
|
},
|
|
@@ -527,29 +614,33 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
527
614
|
h(
|
|
528
615
|
'div',
|
|
529
616
|
{ className: 'mvp_applyText' },
|
|
530
|
-
h('
|
|
531
|
-
h('
|
|
617
|
+
h('span', { className: 'mvp_applyTitle' }, t('page.applyInput')),
|
|
618
|
+
h('span', { className: 'mvp_applyHint', id: 'mvp-apply-hint' }, t('page.applyInputShort')),
|
|
532
619
|
),
|
|
533
620
|
),
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
621
|
+
failedRoutes.length > 0
|
|
622
|
+
? h(
|
|
623
|
+
'div',
|
|
624
|
+
{
|
|
625
|
+
className: 'mvp_banner mvp_banner--warn',
|
|
626
|
+
title: failedRoutes
|
|
627
|
+
.map((route) => (route.name || route.id) + ': ' + (route.message || route.status))
|
|
628
|
+
.join('\n'),
|
|
629
|
+
},
|
|
630
|
+
t('page.probeFailures', {
|
|
631
|
+
names: failedRoutes.map((route) => route.name || route.id).join(' · '),
|
|
632
|
+
}),
|
|
633
|
+
)
|
|
543
634
|
: null,
|
|
635
|
+
hostStale ? h('div', { className: 'mvp_banner' }, t('page.hostStale')) : null,
|
|
544
636
|
state.detailsError !== null && state.source === 'catalog'
|
|
545
|
-
? h('div', { className: '
|
|
637
|
+
? h('div', { className: 'mvp_banner' }, t('page.metaFallback'))
|
|
546
638
|
: null,
|
|
547
|
-
hostStale ? h('div', { className: 'mvp_fallback' }, t('page.hostStale')) : null,
|
|
548
639
|
state.failures.length > 0
|
|
549
640
|
? h(
|
|
550
641
|
'div',
|
|
551
642
|
{
|
|
552
|
-
className: '
|
|
643
|
+
className: 'mvp_banner mvp_banner--warn',
|
|
553
644
|
title: state.failures
|
|
554
645
|
.map((failure) => (failure.name || failure.id) + ': ' + failure.message)
|
|
555
646
|
.join('\n'),
|
|
@@ -561,20 +652,34 @@ function makeSection(form, catalog, overrides, usage, verify) {
|
|
|
561
652
|
}),
|
|
562
653
|
)
|
|
563
654
|
: null,
|
|
564
|
-
|
|
655
|
+
overrideState.available === false
|
|
656
|
+
? h('div', { className: 'mvp_banner' }, t('override.unavailable'))
|
|
657
|
+
: null,
|
|
658
|
+
error !== null ? h('div', { className: 'mvp_banner mvp_banner--error', role: 'alert' }, error) : null,
|
|
565
659
|
state.error !== null
|
|
566
|
-
? h(
|
|
660
|
+
? h(
|
|
661
|
+
'div',
|
|
662
|
+
{ className: 'mvp_banner mvp_banner--error', role: 'alert' },
|
|
663
|
+
t('page.error', { message: state.error }),
|
|
664
|
+
)
|
|
567
665
|
: null,
|
|
568
|
-
|
|
666
|
+
h('div', { className: 'mvp_groups' }, rows),
|
|
569
667
|
total > 0 && hasActiveFilters && rows.length === 0
|
|
570
668
|
? h('div', { className: 'mvp_note' }, t('page.filterEmpty'))
|
|
571
669
|
: null,
|
|
572
670
|
rows.length === 0 && !loading && state.error === null && total === 0
|
|
573
|
-
? h('div', { className: '
|
|
671
|
+
? h('div', { className: 'mvp_note' }, t('page.empty'))
|
|
574
672
|
: null,
|
|
575
|
-
|
|
673
|
+
h(
|
|
674
|
+
'div',
|
|
675
|
+
{ className: 'mvp_footer' },
|
|
676
|
+
probeNote !== null
|
|
677
|
+
? h('span', { className: 'mvp_footerItem', title: t('page.probeSummaryTitle') }, probeNote)
|
|
678
|
+
: null,
|
|
679
|
+
catalogNote !== null ? h('span', { className: 'mvp_footerItem' }, catalogNote) : null,
|
|
680
|
+
),
|
|
576
681
|
providerNote !== null ? h('div', { className: 'mvp_note' }, providerNote) : null,
|
|
577
|
-
|
|
682
|
+
h(VerifyPanel, { verify, catalog: state, t, canUse: canVerify }),
|
|
578
683
|
)
|
|
579
684
|
}
|
|
580
685
|
}
|