@tzyc/dsh-model-visibility 1.9.0 → 1.10.0
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 +50 -30
- package/client-src/catalog-store.mjs +464 -0
- package/client-src/checker-view.mjs +221 -0
- package/client-src/composer-filter.mjs +153 -0
- package/client-src/constants.mjs +20 -0
- package/client-src/css.mjs +248 -0
- package/client-src/dict.mjs +427 -0
- package/client-src/format.mjs +154 -0
- package/client-src/hooks.mjs +25 -0
- package/client-src/index.mjs +101 -0
- package/client-src/override-editor.mjs +286 -0
- package/client-src/pure.mjs +136 -0
- package/client-src/settings-section.mjs +581 -0
- package/client-src/tags.mjs +197 -0
- package/client-src/usage-view.mjs +112 -0
- package/client.js +3228 -2258
- package/index.js +178 -0
- package/lib/capability-catalog.js +5 -69
- package/lib/capability-corrections.js +1 -1
- package/lib/check-catalog.js +575 -0
- package/lib/export-csv.js +69 -0
- package/lib/learned-catalog.js +294 -0
- package/lib/model-id.js +50 -0
- package/lib/model-info.js +94 -52
- package/lib/model-overrides.js +1 -1
- package/lib/usage-ledger.js +574 -0
- package/package.json +65 -64
- package/lib/model-catalog.js +0 -670
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { kindOf, sourceOf, sourceLabel, isInferredSource, formatContext, effortNames, defaultEffortName, modalityNames, modalityLabel, resolutionOf, manualFieldsOf } from './format.mjs'
|
|
2
|
+
|
|
3
|
+
function manualTag(model, t) {
|
|
4
|
+
return manualFieldsOf(model).length === 0
|
|
5
|
+
? null
|
|
6
|
+
: {
|
|
7
|
+
key: 'manual',
|
|
8
|
+
tone: 'accent',
|
|
9
|
+
label: t('tag.manual'),
|
|
10
|
+
title: t('tag.manualTitle'),
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Tags for image / video / embedding / rerank / audio / moderation models. */
|
|
15
|
+
function generationTags(model, t) {
|
|
16
|
+
const kind = kindOf(model)
|
|
17
|
+
const kindSource = sourceOf(model, 'kind')
|
|
18
|
+
const kindLabel = t('tag.kind.' + kind) === 'tag.kind.' + kind ? t('tag.kind.unknown') : t('tag.kind.' + kind)
|
|
19
|
+
const titleKey = kind === 'image' ? 'tag.kindImageTitle' : kind === 'video' ? 'tag.kindVideoTitle' : 'tag.kindTitle'
|
|
20
|
+
const tags = [
|
|
21
|
+
{
|
|
22
|
+
key: 'kind',
|
|
23
|
+
tone: 'kind-' + kind,
|
|
24
|
+
label: kindLabel,
|
|
25
|
+
title: t(titleKey, { kind: kindLabel, source: sourceLabel(kindSource, t) }),
|
|
26
|
+
inferred: isInferredSource(kindSource),
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
const resolution = resolutionOf(model)
|
|
30
|
+
if (resolution !== null && (kind === 'image' || kind === 'video')) {
|
|
31
|
+
tags.push({
|
|
32
|
+
key: 'resolution',
|
|
33
|
+
tone: 'neutral',
|
|
34
|
+
label: resolution,
|
|
35
|
+
title: t('tag.resolutionTitle', { value: resolution }),
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
// Image editing capability is the one input fact worth showing on a
|
|
39
|
+
// generation row; it never turns into the chat "multimodal" tag.
|
|
40
|
+
if (kind === 'image' && Array.isArray(model.inputModalities) && model.inputModalities.includes('image')) {
|
|
41
|
+
tags.push({
|
|
42
|
+
key: 'image-input',
|
|
43
|
+
tone: 'neutral',
|
|
44
|
+
label: t('tag.imageInput'),
|
|
45
|
+
title: t('tag.imageInputTitle'),
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
const manual = manualTag(model, t)
|
|
49
|
+
if (manual !== null) tags.push(manual)
|
|
50
|
+
return tags
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Per-model capability tags. The same model may know only part of its
|
|
55
|
+
* capabilities: an adapter that cannot answer produces `infoError`, and the
|
|
56
|
+
* unknown facts are shown as such instead of being guessed. Each tag knows
|
|
57
|
+
* whether its value is a declaration (solid border) or a completion from the
|
|
58
|
+
* built-in catalog / name heuristics (dashed border).
|
|
59
|
+
*/
|
|
60
|
+
function capabilityTags(model, t) {
|
|
61
|
+
const kind = kindOf(model)
|
|
62
|
+
if (kind !== 'chat') return generationTags(model, t)
|
|
63
|
+
|
|
64
|
+
const tags = []
|
|
65
|
+
const reasoning = model.reasoning
|
|
66
|
+
const levels = effortNames(reasoning)
|
|
67
|
+
const reasoningSource = sourceOf(model, 'reasoning')
|
|
68
|
+
const errorTitle =
|
|
69
|
+
typeof model.infoError === 'string' && model.infoError !== ''
|
|
70
|
+
? t('tag.infoErrorTitle', { message: model.infoError })
|
|
71
|
+
: t('tag.unknownTitle')
|
|
72
|
+
|
|
73
|
+
if (levels.length > 0) {
|
|
74
|
+
tags.push({
|
|
75
|
+
key: 'reasoning',
|
|
76
|
+
tone: 'accent',
|
|
77
|
+
label: t('tag.reasoning'),
|
|
78
|
+
title: t('tag.reasoningTitle', {
|
|
79
|
+
levels: levels.join(' / '),
|
|
80
|
+
source: sourceLabel(reasoningSource, t),
|
|
81
|
+
}),
|
|
82
|
+
})
|
|
83
|
+
const fallback = defaultEffortName(reasoning)
|
|
84
|
+
tags.push({
|
|
85
|
+
key: 'effort',
|
|
86
|
+
tone: 'neutral',
|
|
87
|
+
label: t('tag.effort') + ' ' + levels.join('/'),
|
|
88
|
+
title:
|
|
89
|
+
fallback === null
|
|
90
|
+
? t('tag.effortTitleNoFallback', {
|
|
91
|
+
levels: levels.join(' / '),
|
|
92
|
+
source: sourceLabel(reasoningSource, t),
|
|
93
|
+
})
|
|
94
|
+
: t('tag.effortTitle', {
|
|
95
|
+
levels: levels.join(' / '),
|
|
96
|
+
fallback,
|
|
97
|
+
source: sourceLabel(reasoningSource, t),
|
|
98
|
+
}),
|
|
99
|
+
})
|
|
100
|
+
} else if (model.reasoningSupported === true) {
|
|
101
|
+
tags.push({
|
|
102
|
+
key: 'reasoning',
|
|
103
|
+
tone: 'accent',
|
|
104
|
+
label: t('tag.reasoning'),
|
|
105
|
+
title: t('tag.reasoningCatalogTitle', { source: sourceLabel(reasoningSource, t) }),
|
|
106
|
+
inferred: isInferredSource(reasoningSource),
|
|
107
|
+
})
|
|
108
|
+
} else if (model.reasoningSupported === false) {
|
|
109
|
+
tags.push({
|
|
110
|
+
key: 'reasoning',
|
|
111
|
+
tone: 'muted',
|
|
112
|
+
label: t('tag.noReasoning'),
|
|
113
|
+
title: sourceLabel(reasoningSource, t),
|
|
114
|
+
inferred: isInferredSource(reasoningSource),
|
|
115
|
+
})
|
|
116
|
+
} else {
|
|
117
|
+
tags.push({ key: 'reasoning', tone: 'muted', label: t('tag.reasoningUnknown'), title: errorTitle })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const contextSource = sourceOf(model, 'context')
|
|
121
|
+
const context = formatContext(model.contextWindow)
|
|
122
|
+
tags.push({
|
|
123
|
+
key: 'context',
|
|
124
|
+
tone: context === null ? 'muted' : 'neutral',
|
|
125
|
+
label: t('tag.context') + ' ' + (context === null ? t('tag.unknown') : context),
|
|
126
|
+
title:
|
|
127
|
+
context === null
|
|
128
|
+
? errorTitle
|
|
129
|
+
: t('tag.contextTitle', {
|
|
130
|
+
tokens: Number(model.contextWindow).toLocaleString(),
|
|
131
|
+
source: sourceLabel(contextSource, t),
|
|
132
|
+
}),
|
|
133
|
+
inferred: context !== null && isInferredSource(contextSource),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const modalities = Array.isArray(model.inputModalities) ? model.inputModalities : null
|
|
137
|
+
const modalitySource = sourceOf(model, 'modalities')
|
|
138
|
+
if (modalities === null || modalities.length === 0) {
|
|
139
|
+
tags.push({ key: 'modality', tone: 'muted', label: t('tag.modalityUnknown'), title: errorTitle })
|
|
140
|
+
} else {
|
|
141
|
+
const vision = modalities.includes('image')
|
|
142
|
+
tags.push({
|
|
143
|
+
key: 'modality',
|
|
144
|
+
tone: vision ? 'vision' : 'neutral',
|
|
145
|
+
label: vision ? t('tag.multimodal') : t('tag.textOnly'),
|
|
146
|
+
title: t('tag.modalityTitle', {
|
|
147
|
+
modalities: modalityNames(modalities, t),
|
|
148
|
+
source: sourceLabel(modalitySource, t),
|
|
149
|
+
}),
|
|
150
|
+
inferred: isInferredSource(modalitySource),
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// A vendor-documented correction is the one non-dashed value that is not
|
|
155
|
+
// an adapter declaration; keep it visible so "why is this text-only?"
|
|
156
|
+
// has an answer next to the tag.
|
|
157
|
+
const correction = model.correction
|
|
158
|
+
if (correction !== null && typeof correction === 'object' && typeof correction.code === 'string') {
|
|
159
|
+
const noteKey = 'correction.' + correction.code
|
|
160
|
+
const localized = t(noteKey)
|
|
161
|
+
const note = localized === noteKey
|
|
162
|
+
? typeof correction.note === 'string' && correction.note !== ''
|
|
163
|
+
? correction.note
|
|
164
|
+
: correction.code
|
|
165
|
+
: localized
|
|
166
|
+
tags.push({
|
|
167
|
+
key: 'correction',
|
|
168
|
+
tone: 'muted',
|
|
169
|
+
label: t('tag.correction'),
|
|
170
|
+
title: t('tag.correctionTitle', {
|
|
171
|
+
note,
|
|
172
|
+
source: typeof correction.source === 'string' && correction.source !== '' ? correction.source : '—',
|
|
173
|
+
}),
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
const manual = manualTag(model, t)
|
|
177
|
+
if (manual !== null) tags.push(manual)
|
|
178
|
+
return tags
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function tagClassOf(tag) {
|
|
182
|
+
const tone =
|
|
183
|
+
tag.tone === 'accent'
|
|
184
|
+
? ' mvp_tag--accent'
|
|
185
|
+
: tag.tone === 'vision'
|
|
186
|
+
? ' mvp_tag--vision'
|
|
187
|
+
: tag.tone === 'kind'
|
|
188
|
+
? ' mvp_tag--kind'
|
|
189
|
+
: typeof tag.tone === 'string' && tag.tone.startsWith('kind-')
|
|
190
|
+
? ' mvp_tag--kind mvp_tag--' + tag.tone
|
|
191
|
+
: tag.tone === 'muted'
|
|
192
|
+
? ' mvp_tag--muted'
|
|
193
|
+
: ''
|
|
194
|
+
return 'mvp_tag' + tone + (tag.inferred === true ? ' mvp_tag--inferred' : '')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { capabilityTags, tagClassOf }
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-usage strip with the one-click exports.
|
|
3
|
+
*
|
|
4
|
+
* The strip renders the ledger's today/week/month/total buckets and links the
|
|
5
|
+
* two CSV endpoints. It is an inner piece of the settings section: the section
|
|
6
|
+
* supplies the translator and the usage store instance.
|
|
7
|
+
*
|
|
8
|
+
* @module client-src/usage-view
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const React = require('react')
|
|
12
|
+
const h = React.createElement
|
|
13
|
+
|
|
14
|
+
import { API } from './constants.mjs'
|
|
15
|
+
import { formatTokens } from './pure.mjs'
|
|
16
|
+
import { useSource } from './hooks.mjs'
|
|
17
|
+
|
|
18
|
+
/** Render one [in, out] window as "输入 x · 输出 y". */
|
|
19
|
+
function rangeText(range, t) {
|
|
20
|
+
if (!Array.isArray(range) || range.every((value) => value === 0)) return t('usage.none')
|
|
21
|
+
const input = (range[0] ?? 0) + (range[2] ?? 0) + (range[3] ?? 0)
|
|
22
|
+
const output = range[1] ?? 0
|
|
23
|
+
return t('usage.range', {
|
|
24
|
+
input: formatTokens(input),
|
|
25
|
+
output: formatTokens(output),
|
|
26
|
+
total: formatTokens(input + output),
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Trigger a same-origin download without leaving the page. */
|
|
31
|
+
function download(href) {
|
|
32
|
+
const anchor = document.createElement('a')
|
|
33
|
+
anchor.href = href
|
|
34
|
+
anchor.rel = 'noopener'
|
|
35
|
+
document.body.appendChild(anchor)
|
|
36
|
+
anchor.click()
|
|
37
|
+
anchor.remove()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function UsageToolbar({ usage, t, features }) {
|
|
41
|
+
const state = useSource(usage)
|
|
42
|
+
const supported = features.length === 0 || features.includes('usage')
|
|
43
|
+
const ranges = state.snapshot?.ranges
|
|
44
|
+
const computing = state.snapshot?.computing === true
|
|
45
|
+
return h(
|
|
46
|
+
'div',
|
|
47
|
+
{ className: 'mvp_usage' + (supported ? '' : ' mvp_usage--off') },
|
|
48
|
+
h(
|
|
49
|
+
'div',
|
|
50
|
+
{ className: 'mvp_usageHead' },
|
|
51
|
+
h('span', { className: 'mvp_usageTitle' }, t('usage.title')),
|
|
52
|
+
computing ? h('span', { className: 'mvp_usageComputing' }, t('usage.computing')) : null,
|
|
53
|
+
h(
|
|
54
|
+
'span',
|
|
55
|
+
{ className: 'mvp_usageActions' },
|
|
56
|
+
h(
|
|
57
|
+
'button',
|
|
58
|
+
{
|
|
59
|
+
type: 'button',
|
|
60
|
+
className: 'mvp_button',
|
|
61
|
+
title: t('usage.exportUsageTitle'),
|
|
62
|
+
onClick: () => download(`${API}/export?type=usage`),
|
|
63
|
+
},
|
|
64
|
+
t('usage.exportUsage'),
|
|
65
|
+
),
|
|
66
|
+
h(
|
|
67
|
+
'button',
|
|
68
|
+
{
|
|
69
|
+
type: 'button',
|
|
70
|
+
className: 'mvp_button',
|
|
71
|
+
title: t('usage.exportModelsTitle'),
|
|
72
|
+
onClick: () => download(`${API}/export?type=models`),
|
|
73
|
+
},
|
|
74
|
+
t('usage.exportModels'),
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
supported
|
|
79
|
+
? h(
|
|
80
|
+
'div',
|
|
81
|
+
{ className: 'mvp_usageRanges' },
|
|
82
|
+
h(
|
|
83
|
+
'span',
|
|
84
|
+
{ className: 'mvp_stat', title: t('usage.todayTitle') },
|
|
85
|
+
h('span', { className: 'mvp_statLabel' }, t('usage.today')),
|
|
86
|
+
h('span', { className: 'mvp_statValue' }, rangeText(ranges?.today, t)),
|
|
87
|
+
),
|
|
88
|
+
h(
|
|
89
|
+
'span',
|
|
90
|
+
{ className: 'mvp_stat', title: t('usage.weekTitle') },
|
|
91
|
+
h('span', { className: 'mvp_statLabel' }, t('usage.week')),
|
|
92
|
+
h('span', { className: 'mvp_statValue' }, rangeText(ranges?.week, t)),
|
|
93
|
+
),
|
|
94
|
+
h(
|
|
95
|
+
'span',
|
|
96
|
+
{ className: 'mvp_stat', title: t('usage.monthTitle') },
|
|
97
|
+
h('span', { className: 'mvp_statLabel' }, t('usage.month')),
|
|
98
|
+
h('span', { className: 'mvp_statValue' }, rangeText(ranges?.month, t)),
|
|
99
|
+
),
|
|
100
|
+
h(
|
|
101
|
+
'span',
|
|
102
|
+
{ className: 'mvp_stat mvp_stat--total', title: t('usage.totalTitle') },
|
|
103
|
+
h('span', { className: 'mvp_statLabel' }, t('usage.total')),
|
|
104
|
+
h('span', { className: 'mvp_statValue' }, rangeText(ranges?.total, t)),
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
: h('div', { className: 'mvp_usageHint' }, t('usage.hostStale')),
|
|
108
|
+
state.error !== null
|
|
109
|
+
? h('div', { className: 'mvp_usageHint mvp_usageHint--warn' }, state.error)
|
|
110
|
+
: null,
|
|
111
|
+
)
|
|
112
|
+
}
|