@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.
@@ -0,0 +1,427 @@
1
+ export const DICT = {
2
+ zh: {
3
+ 'nav': '模型显示',
4
+ 'page.title': '模型显示',
5
+ 'page.description':
6
+ '关闭的模型不会出现在聊天输入框的模型下拉列表中;这里的改动立即生效,之后仍可随时重新打开。每个模型标注真实能力:适配器声明优先,其次读中转站的模型列表,再用内置目录补全;生图、生视频等生成类模型单独标记。',
7
+ 'page.summary': '已显示 {visible} / {total} 个模型',
8
+ 'page.hiddenSummary': '已关闭 {count} 个',
9
+ 'page.kindSummary': '{count} 个生图/生视频模型',
10
+ 'page.loading': '正在加载模型列表…',
11
+ 'page.empty': '暂无可用模型。',
12
+ 'page.filterEmpty': '当前筛选下没有模型。',
13
+ 'page.error': '模型列表加载失败:{message}',
14
+ 'page.saveFailed': '保存失败,请重试。',
15
+ 'page.refresh': '刷新',
16
+ 'page.refreshing': '刷新中…',
17
+ 'page.reload': '重新获取能力',
18
+ 'page.reloading': '正在获取…',
19
+ 'page.reloadTitle':
20
+ '清空本插件的能力缓存和正在进行的读取,重新询问适配器与所有中转站;不会改动任何模型开关。适配器没有公布的思考强度仍不会凭空出现。',
21
+ 'page.reloadUnavailable': 'Host 半不支持强制重新获取;重启 DSH 后可用。',
22
+ 'page.collapseGroups': '折叠全部分组',
23
+ 'page.expandGroups': '展开全部分组',
24
+ 'page.collapseGroupsTitle': '只保留分组标题,方便跳到其他分组',
25
+ 'page.expandGroupsTitle': '展开所有被折叠的分组',
26
+ 'page.showAll': '全部显示',
27
+ 'page.legend':
28
+ '标签说明:推理 = 可调推理等级;上下文 = 输入窗口;多模态 = 可输入图像。实线标签来自适配器、中转站或厂商文档,虚线标签来自内置目录或模型名推断。生图 / 生视频 / 嵌入等模型只标类型,不显示思考强度与文本标签。',
29
+ 'page.metaFallback':
30
+ '模型能力信息暂不可用:上下文与模态显示为未知,推理档位仍来自官方目录。',
31
+ 'page.hostStale':
32
+ 'Host 半仍是旧版本:颜色与开关已更新,但「放行图像」和官方校正要等 DSH 重启(或 profile 重组)后才会生效。',
33
+ 'page.providerFailures': '部分提供方暂时读不到模型:{names}',
34
+ 'page.probeSummary': '中转站探测:{ok} 个已读取模型列表,{caps} 个公布了能力字段',
35
+ 'page.probeSummaryNone': '中转站探测:暂无可读取的路由配置',
36
+ 'page.catalogNote': '内置目录:pi-ai {version} 快照({date}),共 {models} 个模型',
37
+ 'page.learnedNote': '学习快照:联网校验({date}),缓存 {models} 个模型的可验证事实',
38
+ 'usage.title': 'token 用量',
39
+ 'usage.computing': '统计中…',
40
+ 'usage.none': '—',
41
+ 'usage.range': '输入 {input} · 输出 {output}(合计 {total})',
42
+ 'usage.today': '今天',
43
+ 'usage.todayTitle': '本地自然日(今天 0 点起)',
44
+ 'usage.week': '本周',
45
+ 'usage.weekTitle': '本周一 0 点起',
46
+ 'usage.month': '本月',
47
+ 'usage.monthTitle': '本月 1 日 0 点起',
48
+ 'usage.total': '总计',
49
+ 'usage.totalTitle': '全部历史(更早的按会话创建日粗算)',
50
+ 'usage.exportUsage': '导出用量 CSV',
51
+ 'usage.exportUsageTitle': '下载按天分桶、含每模型 token 分解的 CSV',
52
+ 'usage.exportModels': '导出模型清单 CSV',
53
+ 'usage.exportModelsTitle': '下载全部提供商模型的可见性与能力表',
54
+ 'usage.hostStale': '宿主半尚未提供用量统计,重启 DSH 即可启用。',
55
+ 'checker.title': '联网校验模型能力',
56
+ 'checker.description':
57
+ '选定一个模型作为校验员:先把两个公开目录(models.dev / OpenRouter)拉下来做基准,再把其余模型分批交给它核对上下文窗口、输入模态、思考档位与类型;结果回填本页并(在开关打开时)写入对应提供商配置,同时存进本地快照 learned.json,之后同名模型直接复用缓存。校验请求会消耗所选模型的 API 额度,其 token 消耗会计入上方用量统计。',
58
+ 'checker.provider': '校验员提供商',
59
+ 'checker.model': '校验员模型',
60
+ 'checker.start': '开始校验',
61
+ 'checker.startTitle': '在宿主侧后台跑一轮全模型校验',
62
+ 'checker.cancel': '取消',
63
+ 'checker.cancelTitle': '中止正在进行的校验',
64
+ 'checker.writeBack': '校验通过即写入提供商配置',
65
+ 'checker.writeBackTitle':
66
+ '开启:判定到的 input / contextWindow / 推理档位直接写进各提供商该模型条目(learned.json 只留写不进去的字段);关闭:只更新本页与快照,需要时再用「编辑能力」手动写入。',
67
+ 'checker.idle': '未运行',
68
+ 'checker.running': '进行中 {phase} · {done}/{total} · 已耗 {tokens}',
69
+ 'checker.finished': '已完成 {done}/{total} · 校验耗 token {tokens}',
70
+ 'checker.progress': '{done}/{total} 已核对 · 校验本身消耗 {tokens} token',
71
+ 'checker.errors': '{count} 个批次失败(悬停看详情)',
72
+ 'checker.pickNeeded': '请先选择校验员提供商与模型。',
73
+ 'checker.failed': '校验任务未能启动。',
74
+ 'checker.hostStale': '宿主半不支持联网校验,请重启 DSH 后使用。',
75
+ 'checker.phase.public': '(读取公开目录)',
76
+ 'checker.phase.checker': '(读取公开目录)',
77
+ 'checker.phase.verifying': '(分批校验)',
78
+ 'source.snapshot': '已校验',
79
+ 'page.applyInput': '让输入框按能力标注放行图像',
80
+ 'page.applyInputHint':
81
+ '开启后,被标注为「多模态」的中转站对话模型会在发送请求时自动补上图像输入能力;关闭则完全按 Provider 配置里的 input 声明。只影响中转站(pi-ai)路由,不改任何配置文件;若某个上游实际拒绝图像,关闭此开关即可。',
82
+ 'page.kindLegend':
83
+ '颜色区分模型类型:生图 蓝 · 生视频 绿 · 嵌入 橙 · 音频 紫 · 重排 红 · 审核 玫红 · 翻译 灰。「官方校正」表示内置目录与厂商文档不一致时以文档为准。',
84
+ 'page.filterLabel': '筛选',
85
+ 'page.searchPlaceholder': '搜索模型名称或 ID',
86
+ 'page.capabilityLabel': '能力',
87
+ 'page.capability.multimodal': '多模态',
88
+ 'page.capability.reasoning': '支持推理',
89
+ 'page.contextLabel': '上下文至少',
90
+ 'page.context.any': '不限',
91
+ 'page.context.128k': '128K+',
92
+ 'page.context.256k': '256K+',
93
+ 'page.context.512k': '512K+',
94
+ 'page.context.1m': '1M+',
95
+ 'page.filter.all': '全部',
96
+ 'page.filter.chat': '对话',
97
+ 'page.filter.image': '生图',
98
+ 'page.filter.video': '生视频',
99
+ 'page.filter.other': '其它',
100
+ 'group.showAll': '全组显示',
101
+ 'group.hideAll': '全组隐藏',
102
+ 'group.showFiltered': '显示筛选结果',
103
+ 'group.hideFiltered': '隐藏筛选结果',
104
+ 'group.collapseNamed': '折叠 {name} 分组',
105
+ 'group.expandNamed': '展开 {name} 分组',
106
+ 'group.capabilities': '{chat} 对话 · {image} 生图 · {video} 生视频 · {other} 其它',
107
+ 'group.probeOk': '中转站 {models} 个模型 · {caps} 个带能力字段',
108
+ 'group.probeNoCaps': '中转站 {models} 个模型 · 未公布能力字段',
109
+ 'group.probeFailed': '中转站读取失败',
110
+ 'group.probeSkipped': '未探测中转站',
111
+ 'tag.reasoning': '支持推理',
112
+ 'tag.noReasoning': '不支持推理',
113
+ 'tag.reasoningUnknown': '推理未知',
114
+ 'tag.effort': '思考强度',
115
+ 'tag.context': '上下文',
116
+ 'tag.unknown': '未知',
117
+ 'tag.multimodal': '多模态',
118
+ 'tag.textOnly': '纯文本',
119
+ 'tag.modalityUnknown': '模态未知',
120
+ 'tag.kind.image': '生图',
121
+ 'tag.kind.video': '生视频',
122
+ 'tag.kind.embedding': '嵌入',
123
+ 'tag.kind.rerank': '重排',
124
+ 'tag.kind.audio': '音频',
125
+ 'tag.kind.classifier': '审核',
126
+ 'tag.kind.translation': '翻译',
127
+ 'tag.kind.unknown': '模型',
128
+ 'tag.kindTitle': '模型类型:{kind};类型来源:{source}',
129
+ 'tag.kindImageTitle': '图像生成模型(输出图像);类型来源:{source}',
130
+ 'tag.kindVideoTitle': '视频生成模型(输出视频);类型来源:{source}',
131
+ 'tag.correction': '官方校正',
132
+ 'tag.correctionTitle': '{note}\n来源:{source}',
133
+ 'tag.resolution': '分辨率',
134
+ 'tag.resolutionTitle': '模型名标注了 {value} 输出档位',
135
+ 'tag.imageInput': '可改图',
136
+ 'tag.imageInputTitle': '支持图像输入(图生图 / 参考图)',
137
+ 'tag.reasoningTitle': '支持推理,可用档位:{levels}(来源:{source})',
138
+ 'tag.reasoningCatalogTitle': '支持推理;适配器未公布档位,此结论来自{source}',
139
+ 'tag.effortTitle': '可用档位:{levels};默认:{fallback}(来源:{source})',
140
+ 'tag.effortTitleNoFallback': '可用档位:{levels}(来源:{source})',
141
+ 'tag.contextTitle': '上下文窗口 {tokens} tokens;来源:{source}',
142
+ 'tag.modalityTitle': '输入模态:{modalities};来源:{source}',
143
+ 'tag.outputTitle': '输出模态:{modalities};来源:{source}',
144
+ 'tag.unknownTitle': '适配器与中转站都没有公布这项信息',
145
+ 'tag.infoErrorTitle': '模型能力读取失败:{message}',
146
+ 'source.config': '适配器配置',
147
+ 'source.adapter': '适配器',
148
+ 'source.provider': '中转站模型列表',
149
+ 'source.catalog': '内置模型目录',
150
+ 'source.inferred': '模型名推断',
151
+ 'source.official': '厂商官方文档',
152
+ 'source.manual': '手动设置',
153
+ 'source.unknown': '未知',
154
+ 'effort.off': '关闭',
155
+ 'effort.minimal': '极低',
156
+ 'effort.low': '低',
157
+ 'effort.medium': '中',
158
+ 'effort.high': '高',
159
+ 'effort.max': '最高',
160
+ 'effort.xhigh': '极高',
161
+ 'tag.manual': '手动',
162
+ 'tag.manualTitle': '这项能力由你手动指定,优先于适配器、中转站与内置目录。',
163
+ 'row.edit': '编辑能力',
164
+ 'row.editTitle': '手动指定这个模型的类型、上下文、输入模态与思考强度',
165
+ 'override.title': '手动能力设置',
166
+ 'override.hint': '这里填写的项会覆盖自动探测结果;留空或选「自动」的项继续按原规则判断。',
167
+ 'override.kind': '模型类型',
168
+ 'override.kindAuto': '自动判断',
169
+ 'override.context': '上下文窗口',
170
+ 'override.contextPlaceholder': '留空 = 自动',
171
+ 'override.contextInvalid': '上下文窗口必须是大于 0 的整数。',
172
+ 'override.input': '输入模态',
173
+ 'override.efforts': '思考强度档位',
174
+ 'override.defaultEffort': '默认档位',
175
+ 'override.defaultEffortAuto': '不指定',
176
+ 'override.effortHint':
177
+ '勾选「不支持推理」会隐藏思考强度;勾选档位后,这些档位会直接显示为该模型的可用强度。',
178
+ 'override.save': '保存',
179
+ 'override.saving': '保存中…',
180
+ 'override.reset': '恢复自动',
181
+ 'override.cancel': '取消',
182
+ 'override.failed': '保存失败:{message}',
183
+ 'override.unavailable': 'Host 半仍不支持手动能力设置;重启 DSH 后可用。',
184
+ 'override.provider': '同时写入模型提供商设置',
185
+ 'override.providerHint':
186
+ '勾选后,输入模态、上下文窗口与思考强度会写进「设置 → 模型提供商」里这个模型的配置,其他界面也能看到;只在本页可表达的项(模型类型、默认档位、仅 off 的档位)仍留在本插件。',
187
+ 'override.providerWritten': '已同时写入提供商设置:{fields}',
188
+ 'override.providerSkipped': '已保存在本插件;提供商设置未写入({reason})。',
189
+ 'override.resetTitle': '只删除本插件的覆盖;已写入提供商设置的值不会被清除。',
190
+ 'override.field.input': '输入模态',
191
+ 'override.field.contextWindow': '上下文窗口',
192
+ 'override.field.reasoningEfforts': '思考强度',
193
+ 'override.reason.nothing': '没有可写入的字段',
194
+ 'override.reason.unsupported': '当前 Host 不支持写入提供商设置',
195
+ 'override.reason.provider-missing': '提供商设置里找不到该提供商',
196
+ 'override.reason.models-missing': '该提供商没有显式模型列表',
197
+ 'override.reason.model-missing': '该提供商的模型列表里没有这个模型',
198
+ 'override.reason.describe-failed': '读取提供商设置失败',
199
+ 'override.reason.settings-failed': '设置服务拒绝了写入',
200
+ 'override.reason.skipped': '未请求写入',
201
+ 'page.manualNote':
202
+ '「手动」标签表示这项能力来自你在这个模型上的手动设置,优先于适配器、中转站与内置目录。',
203
+ 'correction.glm-5.3-text-only':
204
+ '官方文档:GLM-5.3 仅支持文本模态;原生多模态的是 GLM-5.3-Flash / FlashX。',
205
+ 'modality.text': '文本',
206
+ 'modality.image': '图像',
207
+ 'modality.audio': '音频',
208
+ 'modality.video': '视频',
209
+ 'modality.file': '文件',
210
+ 'provider.account': 'DeepSeek 账号',
211
+ },
212
+ en: {
213
+ 'nav': 'Model visibility',
214
+ 'page.title': 'Model visibility',
215
+ 'page.description':
216
+ 'Models turned off here disappear from the chat composer model list. Changes apply immediately and can be turned back on at any time. Every model shows its real capabilities: adapter declarations first, then the relay model list, then a built-in catalog; image, video and other generation models are labelled separately.',
217
+ 'page.summary': 'Showing {visible} of {total} models',
218
+ 'page.hiddenSummary': '{count} hidden',
219
+ 'page.kindSummary': '{count} image/video models',
220
+ 'page.loading': 'Loading model list…',
221
+ 'page.empty': 'No models available.',
222
+ 'page.filterEmpty': 'No models match this filter.',
223
+ 'page.error': 'Could not load the model list: {message}',
224
+ 'page.saveFailed': 'Could not save this change. Try again.',
225
+ 'page.refresh': 'Refresh',
226
+ 'page.refreshing': 'Refreshing…',
227
+ 'page.reload': 'Re-fetch capabilities',
228
+ 'page.reloading': 'Re-fetching…',
229
+ 'page.reloadTitle':
230
+ 'Drops this plugin’s capability caches and any in-flight read, then asks the adapter and every relay again. Model switches are untouched; effort levels the adapter does not publish still cannot appear.',
231
+ 'page.reloadUnavailable': 'The Host half does not support a hard re-fetch; restart DSH to enable it.',
232
+ 'page.collapseGroups': 'Collapse all groups',
233
+ 'page.expandGroups': 'Expand all groups',
234
+ 'page.collapseGroupsTitle': 'Keep only group headers so other groups are easy to reach',
235
+ 'page.expandGroupsTitle': 'Expand every collapsed group',
236
+ 'page.showAll': 'Show all',
237
+ 'page.legend':
238
+ 'Tags: Reasoning = adjustable levels; Context = input window; Multimodal = accepts images. Solid tags come from the adapter, the relay or vendor documentation; dashed tags come from the built-in catalog or the model name. Image, video, embedding and other generation models show only their kind, never effort or text tags.',
239
+ 'page.metaFallback':
240
+ 'Model capability details are unavailable right now: context and modalities show as unknown, while effort levels still come from the shipped catalog.',
241
+ 'page.hostStale':
242
+ 'The Host half is still the previous version: the colours and switch are live, but image admission and vendor corrections take effect after DSH restarts (or the profile recomposes).',
243
+ 'page.providerFailures': 'Some providers could not be read: {names}',
244
+ 'page.probeSummary': 'Relay probe: {ok} model lists read, {caps} published capability fields',
245
+ 'page.probeSummaryNone': 'Relay probe: no readable route configuration',
246
+ 'page.catalogNote': 'Built-in catalog: pi-ai {version} snapshot ({date}), {models} models',
247
+ 'page.learnedNote': 'Learned snapshot: online verification ({date}), {models} verified models',
248
+ 'usage.title': 'Token usage',
249
+ 'usage.computing': 'scanning…',
250
+ 'usage.none': '—',
251
+ 'usage.range': 'in {input} · out {output} ({total} total)',
252
+ 'usage.today': 'Today',
253
+ 'usage.todayTitle': 'Local calendar day',
254
+ 'usage.week': 'This week',
255
+ 'usage.weekTitle': 'Since Monday 00:00 local',
256
+ 'usage.month': 'This month',
257
+ 'usage.monthTitle': 'Since the 1st local',
258
+ 'usage.total': 'Total',
259
+ 'usage.totalTitle': 'All history (older months attributed by session start day)',
260
+ 'usage.exportUsage': 'Export usage CSV',
261
+ 'usage.exportUsageTitle': 'Download a day-bucketed CSV with per-model breakdown',
262
+ 'usage.exportModels': 'Export model list CSV',
263
+ 'usage.exportModelsTitle': 'Download visibility and capability table of every provider model',
264
+ 'usage.hostStale': 'The Host half does not provide usage stats yet; restart DSH to enable it.',
265
+ 'checker.title': 'Verify model capabilities online',
266
+ 'checker.description':
267
+ 'Pick one model as the checker: two public catalogs (models.dev / OpenRouter) are fetched first, then the remaining models are verified in batches — context window, input modalities, effort ladder and kind. Confirmed facts fill this page and, with the switch on, are written into the provider entries; they are also stored in the local learned.json snapshot so a same-named model on another route reuses the cache. Verification spends the checker route quota and its tokens count in the usage strip above.',
268
+ 'checker.provider': 'Checker provider',
269
+ 'checker.model': 'Checker model',
270
+ 'checker.start': 'Verify all',
271
+ 'checker.startTitle': 'Run a background verification sweep in the Host',
272
+ 'checker.cancel': 'Cancel',
273
+ 'checker.cancelTitle': 'Abort the running sweep',
274
+ 'checker.writeBack': 'Write confirmed facts into provider settings',
275
+ 'checker.writeBackTitle':
276
+ 'On: the judged input / contextWindow / efforts are written into each provider entry (learned.json keeps only what cannot be mapped). Off: only this page and the snapshot update; use Edit capabilities to write manually.',
277
+ 'checker.idle': 'not run yet',
278
+ 'checker.running': 'running {phase} · {done}/{total} · {tokens} spent',
279
+ 'checker.finished': 'finished {done}/{total} · checker spent {tokens} tokens',
280
+ 'checker.progress': '{done}/{total} verified · checker spent {tokens} tokens',
281
+ 'checker.errors': '{count} failed batches (hover for details)',
282
+ 'checker.pickNeeded': 'Pick a checker provider and model first.',
283
+ 'checker.failed': 'The verify sweep could not start.',
284
+ 'checker.hostStale': 'The Host half does not support online verification yet; restart DSH.',
285
+ 'checker.phase.public': '(fetching public catalogs)',
286
+ 'checker.phase.checker': '(fetching public catalogs)',
287
+ 'checker.phase.verifying': '(verifying in batches)',
288
+ 'source.snapshot': 'verified',
289
+ 'page.applyInput': 'Let the composer admit images shown as supported',
290
+ 'page.applyInputHint':
291
+ 'When on, relay chat models labelled Multimodal advertise image input to the request path; when off, only the Provider input declaration counts. Relay (pi-ai) routes only — no configuration file is modified. Turn it off if a particular upstream still rejects images.',
292
+ 'page.kindLegend':
293
+ 'Colours separate model kinds: image blue · video green · embedding orange · audio purple · rerank red · moderation rose · translation grey. “Official correction” means the vendor documentation overrides a wrong catalog row.',
294
+ 'page.filterLabel': 'Filter',
295
+ 'page.searchPlaceholder': 'Search model name or ID',
296
+ 'page.capabilityLabel': 'Capabilities',
297
+ 'page.capability.multimodal': 'Multimodal',
298
+ 'page.capability.reasoning': 'Reasoning',
299
+ 'page.contextLabel': 'Context at least',
300
+ 'page.context.any': 'Any',
301
+ 'page.context.128k': '128K+',
302
+ 'page.context.256k': '256K+',
303
+ 'page.context.512k': '512K+',
304
+ 'page.context.1m': '1M+',
305
+ 'page.filter.all': 'All',
306
+ 'page.filter.chat': 'Chat',
307
+ 'page.filter.image': 'Image',
308
+ 'page.filter.video': 'Video',
309
+ 'page.filter.other': 'Other',
310
+ 'group.showAll': 'Show all',
311
+ 'group.hideAll': 'Hide all',
312
+ 'group.showFiltered': 'Show filtered',
313
+ 'group.hideFiltered': 'Hide filtered',
314
+ 'group.collapseNamed': 'Collapse the {name} group',
315
+ 'group.expandNamed': 'Expand the {name} group',
316
+ 'group.capabilities': '{chat} chat · {image} image · {video} video · {other} other',
317
+ 'group.probeOk': 'relay: {models} models · {caps} with capability fields',
318
+ 'group.probeNoCaps': 'relay: {models} models · no capability fields',
319
+ 'group.probeFailed': 'relay read failed',
320
+ 'group.probeSkipped': 'relay not probed',
321
+ 'tag.reasoning': 'Reasoning',
322
+ 'tag.noReasoning': 'No reasoning',
323
+ 'tag.reasoningUnknown': 'Reasoning unknown',
324
+ 'tag.effort': 'Effort',
325
+ 'tag.context': 'Context',
326
+ 'tag.unknown': 'unknown',
327
+ 'tag.multimodal': 'Multimodal',
328
+ 'tag.textOnly': 'Text only',
329
+ 'tag.modalityUnknown': 'Modality unknown',
330
+ 'tag.kind.image': 'Image',
331
+ 'tag.kind.video': 'Video',
332
+ 'tag.kind.embedding': 'Embedding',
333
+ 'tag.kind.rerank': 'Rerank',
334
+ 'tag.kind.audio': 'Audio',
335
+ 'tag.kind.classifier': 'Moderation',
336
+ 'tag.kind.translation': 'Translation',
337
+ 'tag.kind.unknown': 'Model',
338
+ 'tag.kindTitle': 'Model kind: {kind}; kind source: {source}',
339
+ 'tag.kindImageTitle': 'Image generation model (outputs images); kind source: {source}',
340
+ 'tag.kindVideoTitle': 'Video generation model (outputs video); kind source: {source}',
341
+ 'tag.correction': 'Official correction',
342
+ 'tag.correctionTitle': '{note}\nSource: {source}',
343
+ 'tag.resolution': 'Resolution',
344
+ 'tag.resolutionTitle': 'The model name advertises a {value} output tier',
345
+ 'tag.imageInput': 'Image edit',
346
+ 'tag.imageInputTitle': 'Accepts image input (image-to-image / reference images)',
347
+ 'tag.reasoningTitle': 'Reasoning supported; levels: {levels} (source: {source})',
348
+ 'tag.reasoningCatalogTitle': 'Reasoning supported; the adapter publishes no levels, so this comes from {source}',
349
+ 'tag.effortTitle': 'Available levels: {levels}; default: {fallback} (source: {source})',
350
+ 'tag.effortTitleNoFallback': 'Available levels: {levels} (source: {source})',
351
+ 'tag.contextTitle': 'Context window {tokens} tokens; source: {source}',
352
+ 'tag.modalityTitle': 'Input modalities: {modalities}; source: {source}',
353
+ 'tag.outputTitle': 'Output modalities: {modalities}; source: {source}',
354
+ 'tag.unknownTitle': 'Neither the adapter nor the relay publishes this fact',
355
+ 'tag.infoErrorTitle': 'Could not read model capabilities: {message}',
356
+ 'source.config': 'adapter configuration',
357
+ 'source.adapter': 'adapter',
358
+ 'source.provider': 'relay model list',
359
+ 'source.catalog': 'built-in model catalog',
360
+ 'source.inferred': 'model name inference',
361
+ 'source.official': 'vendor documentation',
362
+ 'source.manual': 'manual override',
363
+ 'source.unknown': 'unknown',
364
+ 'effort.off': 'Off',
365
+ 'effort.minimal': 'Minimal',
366
+ 'effort.low': 'Low',
367
+ 'effort.medium': 'Medium',
368
+ 'effort.high': 'High',
369
+ 'effort.max': 'Max',
370
+ 'effort.xhigh': 'XHigh',
371
+ 'tag.manual': 'Manual',
372
+ 'tag.manualTitle':
373
+ 'You set this capability by hand; it outranks the adapter, the relay and the built-in catalog.',
374
+ 'row.edit': 'Edit',
375
+ 'row.editTitle':
376
+ 'Set this model’s kind, context window, input modalities and effort levels by hand',
377
+ 'override.title': 'Manual capabilities',
378
+ 'override.hint':
379
+ 'Anything filled in here overrides automatic detection; blank fields and “Automatic” keep the normal rules.',
380
+ 'override.kind': 'Model kind',
381
+ 'override.kindAuto': 'Automatic',
382
+ 'override.context': 'Context window',
383
+ 'override.contextPlaceholder': 'Blank = automatic',
384
+ 'override.contextInvalid': 'The context window must be a whole number greater than 0.',
385
+ 'override.input': 'Input modalities',
386
+ 'override.efforts': 'Effort levels',
387
+ 'override.defaultEffort': 'Default level',
388
+ 'override.defaultEffortAuto': 'Unspecified',
389
+ 'override.effortHint':
390
+ '“No reasoning” hides effort levels; checked levels are shown as this model’s available effort levels.',
391
+ 'override.save': 'Save',
392
+ 'override.saving': 'Saving…',
393
+ 'override.reset': 'Use automatic',
394
+ 'override.cancel': 'Cancel',
395
+ 'override.failed': 'Could not save: {message}',
396
+ 'override.unavailable':
397
+ 'The Host half does not support manual capabilities yet; restart DSH to enable it.',
398
+ 'override.provider': 'Also write to the model provider settings',
399
+ 'override.providerHint':
400
+ 'When checked, input modalities, context window and effort levels are written into this model’s entry under Settings → Model providers, so other views see them too. Fields that only exist here (kind, default level, an off-only ladder) stay in this plugin.',
401
+ 'override.providerWritten': 'Also written to the provider settings: {fields}',
402
+ 'override.providerSkipped': 'Saved in this plugin; the provider settings were not written ({reason}).',
403
+ 'override.resetTitle':
404
+ 'Removes this plugin’s override only; values already written to the provider settings are kept.',
405
+ 'override.field.input': 'input modalities',
406
+ 'override.field.contextWindow': 'context window',
407
+ 'override.field.reasoningEfforts': 'effort levels',
408
+ 'override.reason.nothing': 'nothing to write',
409
+ 'override.reason.unsupported': 'this Host cannot write provider settings',
410
+ 'override.reason.provider-missing': 'the provider is missing from the settings document',
411
+ 'override.reason.models-missing': 'the provider has no explicit model list',
412
+ 'override.reason.model-missing': 'the model is missing from the provider model list',
413
+ 'override.reason.describe-failed': 'the settings document could not be read',
414
+ 'override.reason.settings-failed': 'the settings service rejected the write',
415
+ 'override.reason.skipped': 'not requested',
416
+ 'page.manualNote':
417
+ 'A “Manual” tag means you set that capability on this model; it outranks the adapter, the relay and the built-in catalog.',
418
+ 'correction.glm-5.3-text-only':
419
+ 'Official docs: GLM-5.3 accepts text only; GLM-5.3-Flash / FlashX is the native multimodal variant.',
420
+ 'modality.text': 'text',
421
+ 'modality.image': 'image',
422
+ 'modality.audio': 'audio',
423
+ 'modality.video': 'video',
424
+ 'modality.file': 'file',
425
+ 'provider.account': 'DeepSeek Account',
426
+ },
427
+ }
@@ -0,0 +1,154 @@
1
+ import { FIELD } from './constants.mjs'
2
+
3
+ export function keyOf(provider, model) {
4
+ return provider + '/' + model
5
+ }
6
+
7
+ export function hiddenSetOf(snapshot) {
8
+ const value = snapshot && snapshot.value
9
+ const list = value && Array.isArray(value[FIELD]) ? value[FIELD] : []
10
+ return new Set(list)
11
+ }
12
+
13
+ export function providerLabel(group, t) {
14
+ return group.id === 'deepseek-account' ? t('provider.account') : group.name
15
+ }
16
+
17
+ export const COLLAPSE_KEY = 'dsh-model-visibility:collapsed-groups:v1'
18
+
19
+ /** Group ids whose model lists the page keeps collapsed. */
20
+ export function readCollapsedGroups() {
21
+ try {
22
+ const raw = window.localStorage.getItem(COLLAPSE_KEY)
23
+ if (raw === null) return new Set()
24
+ const parsed = JSON.parse(raw)
25
+ return new Set(Array.isArray(parsed) ? parsed.filter((id) => typeof id === 'string') : [])
26
+ } catch (_error) {
27
+ return new Set()
28
+ }
29
+ }
30
+
31
+ export function persistCollapsedGroups(collapsed) {
32
+ try {
33
+ window.localStorage.setItem(COLLAPSE_KEY, JSON.stringify(Array.from(collapsed)))
34
+ } catch (_error) {
35
+ // Storage can be unavailable (private mode, quota); collapsing still
36
+ // works for this page session.
37
+ }
38
+ }
39
+
40
+ export function countModels(groups) {
41
+ let total = 0
42
+ for (const group of groups || []) total += (group.models || []).length
43
+ return total
44
+ }
45
+
46
+ /** Human context size: 1048576 → "1M", 128000 → "128K". */
47
+ export function formatContext(tokens) {
48
+ if (!Number.isFinite(tokens) || tokens <= 0) return null
49
+ if (tokens >= 1_000_000) return Math.round(tokens / 100_000) / 10 + 'M'
50
+ if (tokens >= 1_000) return Math.round(tokens / 100) / 10 + 'K'
51
+ return String(tokens)
52
+ }
53
+
54
+ /** Effort display names in catalog order. */
55
+ export function effortNames(reasoning) {
56
+ const efforts = reasoning && Array.isArray(reasoning.efforts) ? reasoning.efforts : []
57
+ const names = []
58
+ for (const effort of efforts) {
59
+ if (effort === null || typeof effort !== 'object') continue
60
+ const label = effort.name || effort.id
61
+ if (typeof label === 'string' && label !== '') names.push(label)
62
+ }
63
+ return names
64
+ }
65
+
66
+ /** Display name of the default effort, or null when none is published. */
67
+ export function defaultEffortName(reasoning) {
68
+ if (!reasoning || typeof reasoning.defaultEffort !== 'string') return null
69
+ const efforts = Array.isArray(reasoning.efforts) ? reasoning.efforts : []
70
+ const found = efforts.find((effort) => effort !== null && effort.id === reasoning.defaultEffort)
71
+ return found && typeof found.name === 'string' && found.name !== ''
72
+ ? found.name
73
+ : reasoning.defaultEffort
74
+ }
75
+
76
+ /** Localized name of one input modality. */
77
+ export function modalityLabel(id, t) {
78
+ const key = 'modality.' + id
79
+ const label = t(key)
80
+ return label === key ? id : label
81
+ }
82
+
83
+ /** Localized provenance label ("adapter", "relay model list", …). */
84
+ export function sourceLabel(source, t) {
85
+ const key = 'source.' + (source || 'unknown')
86
+ const label = t(key)
87
+ return label === key ? t('source.unknown') : label
88
+ }
89
+
90
+ /** Per-field provenance recorded by the Host. */
91
+ export function sourceOf(model, field) {
92
+ const sources = model && model.sources
93
+ return sources && typeof sources[field] === 'string' ? sources[field] : 'unknown'
94
+ }
95
+
96
+ /** Catalog and name heuristics are completed values, not declarations. */
97
+ export function isInferredSource(source) {
98
+ return source === 'catalog' || source === 'inferred'
99
+ }
100
+
101
+ /** Kind with a safe fallback for records from an older Host. */
102
+ export function kindOf(model) {
103
+ return model && typeof model.kind === 'string' && model.kind !== '' ? model.kind : 'chat'
104
+ }
105
+
106
+ /** Filter bucket: chat / image / video / other. */
107
+ export function bucketOf(kind) {
108
+ if (kind === 'image') return 'image'
109
+ if (kind === 'video') return 'video'
110
+ if (kind === 'chat') return 'chat'
111
+ return 'other'
112
+ }
113
+
114
+ export function isGenerationKind(kind) {
115
+ return kind === 'image' || kind === 'video' || kind === 'audio'
116
+ }
117
+
118
+ /** Resolution tier advertised in the model id ("-4k", "2K"). */
119
+ export function resolutionOf(model) {
120
+ const text = String((model && model.id) || '')
121
+ const match = /(?:^|[-_/])(4k|2k|8k|uhd|hd)(?:$|[-_/])/i.exec(text)
122
+ return match === null ? null : match[1].toUpperCase()
123
+ }
124
+
125
+ /** "文本 / 图像" for one modality array. */
126
+ export function modalityNames(modalities, t) {
127
+ return (Array.isArray(modalities) ? modalities : []).map((id) => modalityLabel(id, t)).join(' / ')
128
+ }
129
+
130
+ /** Input modalities the manual editor can declare, in display order. */
131
+ export const OVERRIDE_MODALITIES = ['text', 'image', 'audio', 'video', 'file']
132
+
133
+ /** Effort levels the manual editor can declare, in display order. */
134
+ export const OVERRIDE_EFFORTS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
135
+
136
+ /** Localized name of one effort id. */
137
+ export function effortLabel(id, t) {
138
+ const key = 'effort.' + id
139
+ const label = t(key)
140
+ return label === key ? id : label
141
+ }
142
+
143
+ /** Identity of a manual override entry; same shape as the row key. */
144
+ export function overrideKeyOf(entry) {
145
+ return entry ? keyOf(entry.provider, entry.model) : null
146
+ }
147
+
148
+ /** Model fields whose provenance can be manual. */
149
+ export function manualFieldsOf(model) {
150
+ const sources = model && model.sources ? model.sources : {}
151
+ return ['kind', 'reasoning', 'context', 'modalities'].filter(
152
+ (field) => sources[field] === 'manual',
153
+ )
154
+ }
@@ -0,0 +1,25 @@
1
+ const React = require('react')
2
+
3
+ import { hiddenSetOf } from './format.mjs'
4
+
5
+ function subscribeOf(source) {
6
+ return (fn) => source.subscribe(fn)
7
+ }
8
+
9
+ function snapshotOf(source) {
10
+ return () => source.getSnapshot()
11
+ }
12
+
13
+ function useSource(source) {
14
+ const subscribe = React.useCallback(subscribeOf(source), [source])
15
+ const getSnapshot = React.useCallback(snapshotOf(source), [source])
16
+ return React.useSyncExternalStore(subscribe, getSnapshot)
17
+ }
18
+
19
+ function useHidden(form) {
20
+ const snapshot = useSource(form)
21
+ const hidden = React.useMemo(() => hiddenSetOf(snapshot), [snapshot])
22
+ return { hidden, snapshot }
23
+ }
24
+
25
+ export { subscribeOf, snapshotOf, useSource, useHidden }