@goodandready/dsh-model-sync 0.5.16 → 0.5.18
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 +21 -0
- package/README.ru.md +21 -0
- package/README.zh.md +21 -0
- package/lib/adapter-registry.js +1 -0
- package/lib/capability-prober.js +339 -0
- package/lib/client.js +318 -9
- package/lib/http.js +206 -0
- package/lib/inventory.js +1 -0
- package/lib/models.js +114 -0
- package/lib/ollama-manager.js +330 -0
- package/lib/pricing-database.js +150 -17
- package/lib/provider-service.js +43 -0
- package/lib/reconcile.js +9 -2
- package/lib/scheduler.js +7 -0
- package/lib/synchronizer-prober.js +117 -35
- package/lib/synchronizer.js +92 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -65,6 +65,14 @@ window.__ModuleLoader__.load({
|
|
|
65
65
|
'.dms-alert-ok{background:var(--dsw-alias-state-success-secondary,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-success-primary)}' +
|
|
66
66
|
'.dms-alert-bad{background:var(--dsw-alias-state-error-secondary,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-error-primary)}' +
|
|
67
67
|
'.dms-probe-result{padding:2px 6px;border-radius:4px;font-family:monospace;font-size:11px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px}' +
|
|
68
|
+
'.dms-pill{display:inline-flex;align-items:center;gap:4px;font-size:11px;padding:3px 9px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}' +
|
|
69
|
+
'.dms-pill:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary)}' +
|
|
70
|
+
'.dms-pill-active{background:var(--dsw-alias-label-primary)!important;color:var(--dsw-alias-bg-layer-3)!important;border-color:transparent!important;font-weight:600}' +
|
|
71
|
+
'.dms-tier-lightning{border-color:#a855f7;color:#a855f7;background:rgba(168,85,247,0.1)}' +
|
|
72
|
+
'.dms-tier-fast{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-secondary,rgba(34,197,94,0.1))}' +
|
|
73
|
+
'.dms-tier-moderate{border-color:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary,rgba(234,179,8,0.1))}' +
|
|
74
|
+
'.dms-tier-slow{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-secondary,rgba(239,68,68,0.1))}' +
|
|
75
|
+
'.dms-sparkline{font-family:monospace;letter-spacing:-1px;margin-left:3px;font-weight:bold}' +
|
|
68
76
|
'@media (max-width:540px){.dms-provider{grid-template-columns:1fr!important}.dms-provider .dms-btn{justify-self:start}.dms-toolbar>div:first-child{align-items:stretch;flex-direction:column}.dms-toolbar>div:first-child select{width:100%}.dms-toolbar,.dms-section,.dms-section-card,.dms-editor{padding:11px 12px}}'
|
|
69
77
|
|
|
70
78
|
function createErrorBoundary() {
|
|
@@ -228,6 +236,31 @@ window.__ModuleLoader__.load({
|
|
|
228
236
|
notifyOnPriceChanged: 'Notify on price changes',
|
|
229
237
|
saveWebhook: 'Save Webhook Settings',
|
|
230
238
|
testWebhook: 'Test Webhook',
|
|
239
|
+
probeCapabilities: '🔍 Verify Capabilities',
|
|
240
|
+
probeAll: '🔍 Verify All Models',
|
|
241
|
+
probeNewOnly: '⚡ Verify New Only',
|
|
242
|
+
probingInProgress: 'Verifying capabilities…',
|
|
243
|
+
verifiedBadge: 'Verified',
|
|
244
|
+
ollamaHubTitle: '🦙 Ollama Model Hub',
|
|
245
|
+
ollamaPullPlaceholder: 'e.g. llama3.2:3b or qwen2.5-coder:7b',
|
|
246
|
+
ollamaPullBtn: '⬇ Pull Model',
|
|
247
|
+
ollamaUnloadBtn: '⏏ Unload from VRAM',
|
|
248
|
+
ollamaDeleteBtn: '🗑 Delete',
|
|
249
|
+
ollamaRunningVram: 'Active in VRAM (GPU)',
|
|
250
|
+
ollamaNoRunning: 'No models currently loaded in VRAM',
|
|
251
|
+
ollamaInstalled: 'Installed Local Models',
|
|
252
|
+
selectCurated: '⭐ Curated Best',
|
|
253
|
+
tagAll: 'All',
|
|
254
|
+
tagCurated: '⭐ Curated',
|
|
255
|
+
tagReasoning: '🧠 Reasoning',
|
|
256
|
+
tagCoding: '💻 Coding',
|
|
257
|
+
tagFast: '⚡ Fast',
|
|
258
|
+
tagVision: '👁️ Vision',
|
|
259
|
+
tagLongContext: '📜 128k+',
|
|
260
|
+
speedRadar: 'Latency Radar',
|
|
261
|
+
ttft: 'TTFT',
|
|
262
|
+
pricingSync: 'Sync Prices',
|
|
263
|
+
pricingSyncing: 'Syncing prices…',
|
|
231
264
|
webhookTesting: 'Testing...',
|
|
232
265
|
webhookSuccess: 'Webhook test succeeded',
|
|
233
266
|
webhookFailed: 'Webhook test failed: %s',
|
|
@@ -400,6 +433,31 @@ window.__ModuleLoader__.load({
|
|
|
400
433
|
notifyOnPriceChanged: '模型价格变更时通知',
|
|
401
434
|
saveWebhook: '保存 Webhook 设置',
|
|
402
435
|
testWebhook: '测试 Webhook',
|
|
436
|
+
probeCapabilities: '🔍 验证模型能力',
|
|
437
|
+
probeAll: '🔍 验证所有模型',
|
|
438
|
+
probeNewOnly: '⚡ 仅验证新模型',
|
|
439
|
+
probingInProgress: '正在验证模型能力…',
|
|
440
|
+
verifiedBadge: '已验证',
|
|
441
|
+
ollamaHubTitle: '🦙 Ollama 本地模型中心',
|
|
442
|
+
ollamaPullPlaceholder: '例如 llama3.2:3b 或 qwen2.5-coder:7b',
|
|
443
|
+
ollamaPullBtn: '⬇ 下载/拉取模型',
|
|
444
|
+
ollamaUnloadBtn: '⏏ 释放显存 (VRAM)',
|
|
445
|
+
ollamaDeleteBtn: '🗑 删除模型',
|
|
446
|
+
ollamaRunningVram: '当前显存占用 (VRAM)',
|
|
447
|
+
ollamaNoRunning: '显存中无运行模型',
|
|
448
|
+
ollamaInstalled: '已安装的本地模型',
|
|
449
|
+
selectCurated: '⭐ 精选最佳',
|
|
450
|
+
tagAll: '全部',
|
|
451
|
+
tagCurated: '⭐ 精选',
|
|
452
|
+
tagReasoning: '🧠 深度思考',
|
|
453
|
+
tagCoding: '💻 编程优化',
|
|
454
|
+
tagFast: '⚡ 极速轻量',
|
|
455
|
+
tagVision: '👁️ 多模态视觉',
|
|
456
|
+
tagLongContext: '📜 长上下文',
|
|
457
|
+
speedRadar: '延迟雷达',
|
|
458
|
+
ttft: '首字延迟',
|
|
459
|
+
pricingSync: '同步价格',
|
|
460
|
+
pricingSyncing: '正在同步价格…',
|
|
403
461
|
webhookTesting: '正在测试...',
|
|
404
462
|
webhookSuccess: 'Webhook 测试成功',
|
|
405
463
|
webhookFailed: 'Webhook 测试失败:%s',
|
|
@@ -473,6 +531,81 @@ window.__ModuleLoader__.load({
|
|
|
473
531
|
}
|
|
474
532
|
|
|
475
533
|
|
|
534
|
+
const SPARKLINE_BARS = [' ', '▂', '▃', '▄', '▅', '▆', '▇', '█']
|
|
535
|
+
function generateSparkline(values) {
|
|
536
|
+
if (!Array.isArray(values) || values.length === 0) return ''
|
|
537
|
+
const nums = values.filter((v) => typeof v === 'number' && Number.isFinite(v))
|
|
538
|
+
if (nums.length === 0) return ''
|
|
539
|
+
if (nums.length === 1) return '▄'
|
|
540
|
+
const min = Math.min(...nums)
|
|
541
|
+
const max = Math.max(...nums)
|
|
542
|
+
if (min === max) return '▄'.repeat(nums.length)
|
|
543
|
+
const range = max - min
|
|
544
|
+
return nums.map((v) => SPARKLINE_BARS[Math.min(7, Math.max(0, Math.floor(((v - min) / range) * 7)))]).join('')
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function classifySpeedTier(latencyMs, isOk = true) {
|
|
548
|
+
if (!isOk) return 'degraded'
|
|
549
|
+
if (typeof latencyMs !== 'number' || !Number.isFinite(latencyMs) || latencyMs <= 0) return 'unknown'
|
|
550
|
+
if (latencyMs < 300) return 'lightning'
|
|
551
|
+
if (latencyMs < 1200) return 'fast'
|
|
552
|
+
if (latencyMs <= 3000) return 'moderate'
|
|
553
|
+
return 'slow'
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function deriveModelTags(model) {
|
|
557
|
+
if (!model || typeof model !== 'object') return []
|
|
558
|
+
const tags = new Set(Array.isArray(model.tags) ? model.tags.filter((t) => typeof t === 'string' && t.trim()).map((t) => t.trim().toLowerCase()) : [])
|
|
559
|
+
const idLower = String(model.id || '').toLowerCase()
|
|
560
|
+
const nameLower = String(model.name || '').toLowerCase()
|
|
561
|
+
const provLower = String(model.provider || '').toLowerCase()
|
|
562
|
+
const capabilities = model.capabilities || {}
|
|
563
|
+
|
|
564
|
+
if (capabilities.code === true || idLower.includes('coder') || idLower.includes('codestral') || idLower.includes('starcoder') || idLower.includes('deepseek-coder') || idLower.includes('claude-3-7-sonnet') || idLower.includes('claude-3-5-sonnet') || idLower.includes('kimi-k2.7-code') || idLower.includes('qwen-2.5-coder') || idLower.includes('qwen2.5-coder') || (/(^|[_-])(code|dev|prog)($|[_-])/i.test(idLower)) || (/(^|[_-])(code|coder)($|[_-])/i.test(nameLower))) {
|
|
565
|
+
tags.add('coding')
|
|
566
|
+
}
|
|
567
|
+
if (model.reasoningConfig?.supportsReasoning === true || capabilities.reasoning === true || /(^|[_-])(o1|o3|r1|qwq)($|[_-])/i.test(idLower) || idLower.includes('reasoner') || idLower.includes('deepseek-r1') || idLower.includes('thinking') || idLower.includes('-r1')) {
|
|
568
|
+
tags.add('reasoning')
|
|
569
|
+
}
|
|
570
|
+
if (provLower === 'groq' || provLower === 'cerebras' || idLower.includes('flash') || idLower.includes('mini') || idLower.includes('instant') || idLower.includes('haiku') || idLower.includes('small') || idLower.includes('8b') || idLower.includes('3b') || idLower.includes('1b') || idLower.includes('turbo') || idLower.includes('lite') || idLower.includes('nano')) {
|
|
571
|
+
tags.add('fast')
|
|
572
|
+
}
|
|
573
|
+
if (capabilities.vision === true || (Array.isArray(model.input) && model.input.includes('image')) || idLower.includes('vision') || idLower.includes('vl') || idLower.includes('pixtral') || idLower.includes('gemini') || idLower.includes('claude-3') || idLower.includes('4o')) {
|
|
574
|
+
tags.add('vision')
|
|
575
|
+
}
|
|
576
|
+
if (typeof model.contextWindow === 'number' && model.contextWindow >= 128000) {
|
|
577
|
+
tags.add('long-context')
|
|
578
|
+
}
|
|
579
|
+
return [...tags].sort()
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function isCuratedModel(model) {
|
|
583
|
+
if (!model || typeof model !== 'object') return false
|
|
584
|
+
if (model.lifecycle?.deprecated) return false
|
|
585
|
+
const id = String(model.id || '').toLowerCase()
|
|
586
|
+
if (/-(202\d{5}|202\d-\d{2}-\d{2}|\d{4})$/.test(id) && !id.includes('claude-3-7') && !id.includes('claude-3-5')) {
|
|
587
|
+
return false
|
|
588
|
+
}
|
|
589
|
+
if (id.includes('-0314') || id.includes('-0613') || id.includes('-1106') || id.includes('-0125')) {
|
|
590
|
+
return false
|
|
591
|
+
}
|
|
592
|
+
const curatedIds = [
|
|
593
|
+
'claude-3-7-sonnet', 'claude-3-5-sonnet', 'claude-3-5-haiku',
|
|
594
|
+
'gpt-4o', 'gpt-4o-mini', 'o1', 'o3-mini',
|
|
595
|
+
'deepseek-chat', 'deepseek-reasoner', 'deepseek-v3', 'deepseek-r1',
|
|
596
|
+
'gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash',
|
|
597
|
+
'llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'llama-3.1-70b-versatile',
|
|
598
|
+
'qwen-2.5-coder-32b', 'qwen-2.5-72b', 'qwq-32b',
|
|
599
|
+
'mistral-large', 'codestral', 'pixtral-large',
|
|
600
|
+
'kimi-k2.7-code'
|
|
601
|
+
]
|
|
602
|
+
for (const c of curatedIds) {
|
|
603
|
+
if (id === c || id.endsWith('/' + c) || id.includes(c)) return true
|
|
604
|
+
}
|
|
605
|
+
const tags = model.tags || deriveModelTags(model)
|
|
606
|
+
return tags.includes('coding') && tags.includes('reasoning') && tags.includes('long-context')
|
|
607
|
+
}
|
|
608
|
+
|
|
476
609
|
function formatTokens(num) {
|
|
477
610
|
if (typeof num !== "number" || !Number.isFinite(num) || num <= 0) return "?";
|
|
478
611
|
if (num >= 1000000) {
|
|
@@ -680,6 +813,7 @@ window.__ModuleLoader__.load({
|
|
|
680
813
|
})
|
|
681
814
|
const [pageSize, setPageSize] = React.useState(50)
|
|
682
815
|
const [quickFilter, setQuickFilter] = React.useState({ context100k: false, cheap: false })
|
|
816
|
+
const [selectedTag, setSelectedTag] = React.useState('all')
|
|
683
817
|
const [tryState, setTryState] = React.useState({})
|
|
684
818
|
const [batchTrying, setBatchTrying] = React.useState(false)
|
|
685
819
|
const [batchSummary, setBatchSummary] = React.useState(null)
|
|
@@ -1127,6 +1261,84 @@ window.__ModuleLoader__.load({
|
|
|
1127
1261
|
.finally(() => setBusy(false))
|
|
1128
1262
|
}
|
|
1129
1263
|
|
|
1264
|
+
|
|
1265
|
+
const [probingProvider, setProbingProvider] = React.useState(null)
|
|
1266
|
+
const [probingAll, setProbingAll] = React.useState(false)
|
|
1267
|
+
const [verifiedCapabilities, setVerifiedCapabilities] = React.useState({})
|
|
1268
|
+
const [ollamaPs, setOllamaPs] = React.useState([])
|
|
1269
|
+
const [ollamaTags, setOllamaTags] = React.useState([])
|
|
1270
|
+
const [ollamaPullInput, setOllamaPullInput] = React.useState('')
|
|
1271
|
+
const [ollamaPullState, setOllamaPullState] = React.useState(null)
|
|
1272
|
+
|
|
1273
|
+
const loadVerified = () => {
|
|
1274
|
+
fetch(`${API}/probe/verified`)
|
|
1275
|
+
.then(safeJson)
|
|
1276
|
+
.then((d) => { if (d?.verified) setVerifiedCapabilities(d.verified) })
|
|
1277
|
+
.catch(() => {})
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const probeProviderCaps = (targetProvider, onlyNew = false) => {
|
|
1281
|
+
setProbingProvider(targetProvider)
|
|
1282
|
+
fetch(`${API}/probe/provider`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider, onlyNew }) })
|
|
1283
|
+
.then(safeJson)
|
|
1284
|
+
.then(() => { loadVerified(); load() })
|
|
1285
|
+
.catch((e) => setError(String(e?.message ?? e)))
|
|
1286
|
+
.finally(() => setProbingProvider(null))
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const probeAllCaps = (onlyNew = false) => {
|
|
1290
|
+
setProbingAll(true)
|
|
1291
|
+
fetch(`${API}/probe/all`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ onlyNew }) })
|
|
1292
|
+
.then(safeJson)
|
|
1293
|
+
.then(() => { loadVerified(); load() })
|
|
1294
|
+
.catch((e) => setError(String(e?.message ?? e)))
|
|
1295
|
+
.finally(() => setProbingAll(false))
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
const loadOllama = () => {
|
|
1299
|
+
fetch(`${API}/ollama/ps`).then(safeJson).then((d) => { if (d?.running) setOllamaPs(d.running) }).catch(() => {})
|
|
1300
|
+
fetch(`${API}/ollama/tags`).then(safeJson).then((d) => { if (d?.models) setOllamaTags(d.models) }).catch(() => {})
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const pullOllamaModel = (name) => {
|
|
1304
|
+
if (!name) return
|
|
1305
|
+
fetch(`${API}/ollama/pull`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ model: name }) })
|
|
1306
|
+
.then(safeJson)
|
|
1307
|
+
.then((res) => {
|
|
1308
|
+
setOllamaPullState(res.status || { model: name, status: 'pulling', percent: 0 })
|
|
1309
|
+
const poll = setInterval(() => {
|
|
1310
|
+
fetch(`${API}/ollama/pull/status?model=${encodeURIComponent(name)}`)
|
|
1311
|
+
.then(safeJson)
|
|
1312
|
+
.then((st) => {
|
|
1313
|
+
if (st?.status) {
|
|
1314
|
+
setOllamaPullState(st.status)
|
|
1315
|
+
if (st.status.done) {
|
|
1316
|
+
clearInterval(poll)
|
|
1317
|
+
loadOllama()
|
|
1318
|
+
load()
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
})
|
|
1322
|
+
.catch(() => clearInterval(poll))
|
|
1323
|
+
}, 1000)
|
|
1324
|
+
})
|
|
1325
|
+
.catch((e) => setError(String(e?.message ?? e)))
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
const unloadOllama = (name) => {
|
|
1329
|
+
fetch(`${API}/ollama/unload`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ model: name }) })
|
|
1330
|
+
.then(safeJson)
|
|
1331
|
+
.then(() => loadOllama())
|
|
1332
|
+
.catch((e) => setError(String(e?.message ?? e)))
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
const deleteOllama = (name) => {
|
|
1336
|
+
fetch(`${API}/ollama/delete`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ model: name }) })
|
|
1337
|
+
.then(safeJson)
|
|
1338
|
+
.then(() => { loadOllama(); load() })
|
|
1339
|
+
.catch((e) => setError(String(e?.message ?? e)))
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1130
1342
|
const openSelection = (row) => {
|
|
1131
1343
|
const available = row.availableModels ?? row.models ?? []
|
|
1132
1344
|
const defaults = row.selectableModels ?? available
|
|
@@ -1273,7 +1485,15 @@ window.__ModuleLoader__.load({
|
|
|
1273
1485
|
const haystack = (m.id + ' ' + (m.name || '') + ' ' + (m.aliases || []).join(' ') + ' ' + (m.tags || []).join(' ') + ' ' + (m.description || '')).toLowerCase()
|
|
1274
1486
|
return tokens.every((tok) => haystack.includes(tok))
|
|
1275
1487
|
}) : filteredChoicesBase
|
|
1276
|
-
const
|
|
1488
|
+
const taggedChoices = selectedTag === 'all'
|
|
1489
|
+
? searchedChoices
|
|
1490
|
+
: selectedTag === 'curated'
|
|
1491
|
+
? searchedChoices.filter((m) => isCuratedModel(m))
|
|
1492
|
+
: searchedChoices.filter((m) => {
|
|
1493
|
+
const tags = (m.tags && m.tags.length > 0) ? m.tags : deriveModelTags(m)
|
|
1494
|
+
return tags.includes(selectedTag)
|
|
1495
|
+
})
|
|
1496
|
+
const quickFilteredChoices = taggedChoices.filter((m) => {
|
|
1277
1497
|
if (quickFilter.context100k && (!m.contextWindow || m.contextWindow < 100000)) return false
|
|
1278
1498
|
if (quickFilter.cheap) {
|
|
1279
1499
|
const price = m.pricing?.inputPerToken
|
|
@@ -1296,7 +1516,7 @@ window.__ModuleLoader__.load({
|
|
|
1296
1516
|
}
|
|
1297
1517
|
return (a.name || a.id || '').localeCompare(b.name || b.id || '')
|
|
1298
1518
|
})
|
|
1299
|
-
}, [choices, capabilityFilter, debouncedSearchQuery, quickFilter, sortBy])
|
|
1519
|
+
}, [choices, capabilityFilter, debouncedSearchQuery, selectedTag, quickFilter, sortBy])
|
|
1300
1520
|
|
|
1301
1521
|
const maxContext = React.useMemo(() => filteredChoices.reduce((max, m) => Math.max(max, m.contextWindow || 0), 0), [filteredChoices])
|
|
1302
1522
|
const minPrice = React.useMemo(() => filteredChoices.reduce((min, m) => m.pricing?.inputPerToken !== undefined ? Math.min(min, m.pricing.inputPerToken) : min, Infinity), [filteredChoices])
|
|
@@ -1585,7 +1805,9 @@ window.__ModuleLoader__.load({
|
|
|
1585
1805
|
button(t.refreshAll, () => execute(undefined)),
|
|
1586
1806
|
button(dryRun ? t.run : t.apply, () => execute(provider || undefined), dryRun ? undefined : 'primary'),
|
|
1587
1807
|
button(t.health, () => checkHealth(provider || undefined)),
|
|
1588
|
-
button(t.checkCredentials, () => checkCredentials(provider || undefined))
|
|
1808
|
+
button(t.checkCredentials, () => checkCredentials(provider || undefined)),
|
|
1809
|
+
button(probingAll ? '⏳ ' + (t.probingInProgress || 'Verifying…') : (t.probeAll || '🔍 Verify All'), () => probeAllCaps(false)),
|
|
1810
|
+
button(t.probeNewOnly || '⚡ Verify New', () => probeAllCaps(true))
|
|
1589
1811
|
),
|
|
1590
1812
|
busy ? React.createElement('span', null, t.running) : null,
|
|
1591
1813
|
error ? React.createElement('div', { className: 'dms-alert dms-alert-bad' }, error) : null,
|
|
@@ -1631,7 +1853,8 @@ window.__ModuleLoader__.load({
|
|
|
1631
1853
|
),
|
|
1632
1854
|
button(t.selectModels, () => openSelection(row)),
|
|
1633
1855
|
button(t.policy, () => openPolicy(row)),
|
|
1634
|
-
button(t.providerSettings || 'Configure', () => openProviderConfig(row))
|
|
1856
|
+
button(t.providerSettings || 'Configure', () => openProviderConfig(row)),
|
|
1857
|
+
button(probingProvider === row.provider ? '⏳' : (t.probeCapabilities || '🔍 Capabilities'), () => probeProviderCaps(row.provider))
|
|
1635
1858
|
)
|
|
1636
1859
|
|
|
1637
1860
|
const providerConfigEditor = providerConfigOpen === row.provider ? React.createElement('div', { className: 'dms-editor' },
|
|
@@ -1675,6 +1898,34 @@ window.__ModuleLoader__.load({
|
|
|
1675
1898
|
)
|
|
1676
1899
|
) : null
|
|
1677
1900
|
|
|
1901
|
+
|
|
1902
|
+
const ollamaPanel = (row.provider === 'ollama') ? React.createElement('div', { style: { marginTop: 10, padding: '10px 12px', background: 'var(--dsw-alias-bg-layer-2)', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l1)' } },
|
|
1903
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: 8 } }, t.ollamaHubTitle || '🦙 Ollama Local Models Hub'),
|
|
1904
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, marginBottom: 8 } },
|
|
1905
|
+
React.createElement('input', {
|
|
1906
|
+
type: 'text',
|
|
1907
|
+
placeholder: t.ollamaPullPlaceholder || 'e.g. llama3.2:3b',
|
|
1908
|
+
value: ollamaPullInput,
|
|
1909
|
+
onChange: (e) => setOllamaPullInput(e.target.value),
|
|
1910
|
+
style: { flex: 1, padding: '4px 8px', borderRadius: 4, border: '1px solid var(--dsw-alias-border-l2)' }
|
|
1911
|
+
}),
|
|
1912
|
+
button(t.ollamaPullBtn || '⬇ Pull', () => pullOllamaModel(ollamaPullInput.trim()), 'primary')
|
|
1913
|
+
),
|
|
1914
|
+
ollamaPullState && !ollamaPullState.done ? React.createElement('div', { style: { margin: '6px 0', fontSize: 12 } },
|
|
1915
|
+
`Downloading ${ollamaPullState.model}: ${ollamaPullState.status || ''} ${ollamaPullState.percent || 0}% (${ollamaPullState.completedFormatted || ''} / ${ollamaPullState.totalFormatted || ''})`,
|
|
1916
|
+
React.createElement('div', { style: { width: '100%', height: 6, background: 'var(--dsw-alias-border-l2)', borderRadius: 3, overflow: 'hidden', marginTop: 4 } },
|
|
1917
|
+
React.createElement('div', { style: { width: `${ollamaPullState.percent || 0}%`, height: '100%', background: 'var(--dsw-alias-state-business-primary)' } })
|
|
1918
|
+
)
|
|
1919
|
+
) : null,
|
|
1920
|
+
ollamaPs.length > 0 ? React.createElement('div', { style: { marginTop: 8 } },
|
|
1921
|
+
React.createElement('small', { style: { fontWeight: 600, display: 'block', marginBottom: 4 } }, t.ollamaRunningVram || 'Active in VRAM (GPU):'),
|
|
1922
|
+
ollamaPs.map((rm) => React.createElement('div', { key: rm.name, style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', margin: '3px 0', fontSize: 12 } },
|
|
1923
|
+
React.createElement('span', null, `⚡ ${rm.name} · VRAM: ${rm.sizeVramFormatted}`),
|
|
1924
|
+
button(t.ollamaUnloadBtn || '⏏ Unload', () => unloadOllama(rm.name))
|
|
1925
|
+
))
|
|
1926
|
+
) : React.createElement('small', { style: { display: 'block', color: 'var(--dsw-alias-label-secondary)', margin: '4px 0' } }, t.ollamaNoRunning || 'No models currently in VRAM')
|
|
1927
|
+
) : null
|
|
1928
|
+
|
|
1678
1929
|
const selectionEditor = selectionProvider === row.provider ? React.createElement('div', { className: 'dms-editor' },
|
|
1679
1930
|
React.createElement('strong', null, `${t.selectModels}: ${selectionProvider}`),
|
|
1680
1931
|
React.createElement('div', { style: { display: 'flex', gap: 8, margin: '8px 0', flexWrap: 'wrap' } },
|
|
@@ -1700,6 +1951,13 @@ window.__ModuleLoader__.load({
|
|
|
1700
1951
|
))
|
|
1701
1952
|
) : React.createElement('small', { style: { display: 'block', marginBottom: 8 } }, t.noCapabilities),
|
|
1702
1953
|
React.createElement('div', { style: { display: 'flex', gap: 6, margin: '6px 0', flexWrap: 'wrap' } },
|
|
1954
|
+
button(t.selectCurated || '⭐ Curated Best', () => {
|
|
1955
|
+
const ids = new Set(selectionDraft)
|
|
1956
|
+
for (const m of choices) {
|
|
1957
|
+
if (isCuratedModel(m)) ids.add(m.id)
|
|
1958
|
+
}
|
|
1959
|
+
setSelectionDraft([...ids])
|
|
1960
|
+
}, 'primary'),
|
|
1703
1961
|
button(t.selectAllFiltered, () => { const ids = new Set(selectionDraft); for (const m of filteredChoices) ids.add(m.id); setSelectionDraft([...ids]) }),
|
|
1704
1962
|
button(t.deselectAllFiltered, () => { const filterSet = new Set(filteredChoices.map((m) => m.id)); setSelectionDraft(selectionDraft.filter((id) => !filterSet.has(id))) }),
|
|
1705
1963
|
button(t.invertFiltered, () => { const current = new Set(selectionDraft); for (const m of filteredChoices) { if (current.has(m.id)) current.delete(m.id); else current.add(m.id); } setSelectionDraft([...current]) })
|
|
@@ -1710,6 +1968,22 @@ window.__ModuleLoader__.load({
|
|
|
1710
1968
|
batchSummary ? React.createElement('small', { style: { color: 'var(--dsw-alias-label-secondary)' } }, t.batchTryResults.replace('%d', batchSummary.reachable).replace('%d', batchSummary.total)) : null
|
|
1711
1969
|
),
|
|
1712
1970
|
React.createElement('small', { style: { display: 'block', marginBottom: 8 } }, `${filteredChoices.length}/${choices.length} ${t.capabilityCount}`),
|
|
1971
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, margin: '8px 0', flexWrap: 'wrap', alignItems: 'center' } },
|
|
1972
|
+
[
|
|
1973
|
+
{ id: 'all', label: t.tagAll || 'All' },
|
|
1974
|
+
{ id: 'curated', label: t.tagCurated || '⭐ Curated' },
|
|
1975
|
+
{ id: 'reasoning', label: t.tagReasoning || '🧠 Reasoning' },
|
|
1976
|
+
{ id: 'coding', label: t.tagCoding || '💻 Coding' },
|
|
1977
|
+
{ id: 'fast', label: t.tagFast || '⚡ Fast' },
|
|
1978
|
+
{ id: 'vision', label: t.tagVision || '👁️ Vision' },
|
|
1979
|
+
{ id: 'long-context', label: t.tagLongContext || '📜 128k+' },
|
|
1980
|
+
].map((pill) => React.createElement('button', {
|
|
1981
|
+
key: pill.id,
|
|
1982
|
+
type: 'button',
|
|
1983
|
+
className: 'dms-pill' + (selectedTag === pill.id ? ' dms-pill-active' : ''),
|
|
1984
|
+
onClick: () => setSelectedTag(pill.id),
|
|
1985
|
+
}, pill.label))
|
|
1986
|
+
),
|
|
1713
1987
|
visibleChoices.map((model) => {
|
|
1714
1988
|
const deprecationDays = (() => {
|
|
1715
1989
|
const d = model.lifecycle?.deprecationDate
|
|
@@ -1717,7 +1991,8 @@ window.__ModuleLoader__.load({
|
|
|
1717
1991
|
const diff = new Date(d).getTime() - Date.now()
|
|
1718
1992
|
return diff > 0 ? Math.ceil(diff / (1000 * 60 * 60 * 24)) : 0
|
|
1719
1993
|
})()
|
|
1720
|
-
const
|
|
1994
|
+
const radar = status?.latencyRadar?.radar?.find((r) => r.provider === row.provider && r.model === model.id)
|
|
1995
|
+
const probe = tryState[`${row.provider}:${model.id}`] || (radar ? { ok: radar.ok, latency: radar.latencyMs, ttftMs: radar.ttftMs, sparkline: radar.sparkline, status: radar.status } : null)
|
|
1721
1996
|
return React.createElement('label', { key: model.id, style: { display: 'block', margin: '4px 0', padding: '4px 6px', borderRadius: 6, background: isNewModel(model, row) ? 'var(--dsw-alias-bg-layer-2)' : 'transparent' } },
|
|
1722
1997
|
React.createElement('input', { type: 'checkbox', checked: selectionDraft.includes(model.id), onChange: (e) => setSelectionDraft((current) => e.target.checked ? [...current, model.id] : current.filter((id) => id !== model.id)) }),
|
|
1723
1998
|
` ${model.name ?? model.id} (${model.id})`,
|
|
@@ -1749,12 +2024,46 @@ window.__ModuleLoader__.load({
|
|
|
1749
2024
|
}, `🧠 ${model.reasoningConfig?.paramFormat || 'reasoning'}`) : null,
|
|
1750
2025
|
(model.contextWindow && maxContext > 0 && model.contextWindow >= maxContext) ? React.createElement('span', { style: { marginLeft: 4, fontSize: 10, padding: '1px 4px', borderRadius: 3, background: 'var(--dsw-alias-state-warn-primary)', color: 'white' } }, '★') : null,
|
|
1751
2026
|
(model.pricing && minPrice !== Infinity && model.pricing.inputPerToken === minPrice) ? React.createElement('span', { style: { marginLeft: 4, fontSize: 10, padding: '1px 4px', borderRadius: 3, background: 'var(--dsw-alias-state-success-primary)', color: 'white' } }, '$') : null,
|
|
1752
|
-
|
|
2027
|
+
(() => {
|
|
2028
|
+
const modelTags = (model.tags && model.tags.length > 0) ? model.tags : deriveModelTags(model)
|
|
2029
|
+
if (!modelTags || modelTags.length === 0) return null
|
|
2030
|
+
return React.createElement('span', { style: { marginLeft: 6, display: 'inline-flex', gap: 3, verticalAlign: 'middle' } },
|
|
2031
|
+
modelTags.map((tag) => React.createElement('span', {
|
|
2032
|
+
key: tag,
|
|
2033
|
+
style: {
|
|
2034
|
+
fontSize: 10,
|
|
2035
|
+
padding: '0 4px',
|
|
2036
|
+
borderRadius: 3,
|
|
2037
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
2038
|
+
border: '1px solid var(--dsw-alias-border-l1)',
|
|
2039
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
2040
|
+
}
|
|
2041
|
+
}, tag === 'coding' ? '💻 coding' : tag === 'reasoning' ? '🧠 reasoning' : tag === 'fast' ? '⚡ fast' : tag === 'vision' ? '👁️ vision' : tag === 'long-context' ? '📜 128k+' : tag))
|
|
2042
|
+
)
|
|
2043
|
+
})(),
|
|
2044
|
+
(() => {
|
|
2045
|
+
const ver = verifiedCapabilities?.[`${row.provider}:${model.id}`]?.verified
|
|
2046
|
+
if (!ver) return null
|
|
2047
|
+
return React.createElement('span', { style: { marginLeft: 6, display: 'inline-flex', gap: 3, verticalAlign: 'middle' } },
|
|
2048
|
+
ver.tools ? React.createElement('span', { style: { fontSize: 10, padding: '0 4px', borderRadius: 3, background: 'rgba(34, 197, 94, 0.15)', color: '#22c55e', border: '1px solid rgba(34, 197, 94, 0.3)' } }, '✔ Tools') : null,
|
|
2049
|
+
ver.vision ? React.createElement('span', { style: { fontSize: 10, padding: '0 4px', borderRadius: 3, background: 'rgba(34, 197, 94, 0.15)', color: '#22c55e', border: '1px solid rgba(34, 197, 94, 0.3)' } }, '✔ Vision') : null,
|
|
2050
|
+
ver.jsonSchema ? React.createElement('span', { style: { fontSize: 10, padding: '0 4px', borderRadius: 3, background: 'rgba(34, 197, 94, 0.15)', color: '#22c55e', border: '1px solid rgba(34, 197, 94, 0.3)' } }, '✔ JSON') : null,
|
|
2051
|
+
ver.developerRole ? React.createElement('span', { style: { fontSize: 10, padding: '0 4px', borderRadius: 3, background: 'rgba(34, 197, 94, 0.15)', color: '#22c55e', border: '1px solid rgba(34, 197, 94, 0.3)' } }, '✔ DevRole') : null
|
|
2052
|
+
)
|
|
2053
|
+
})(),
|
|
1753
2054
|
model.pricing ? React.createElement('small', { style: { marginLeft: 6, color: 'var(--dsw-alias-label-secondary)', fontSize: 11 } }, `in $${(model.pricing.inputPerToken * 1000000).toFixed(2)}/1M` + (model.pricing.cacheReadPerToken !== undefined ? ` / cache $${(model.pricing.cacheReadPerToken * 1000000).toFixed(2)}` : '') + (model.pricing.outputPerToken !== undefined ? ` / out $${(model.pricing.outputPerToken * 1000000).toFixed(2)}` : '')) : null,
|
|
1754
2055
|
model.description ? React.createElement('small', { title: model.description, style: { display: 'block', marginLeft: 22, color: 'var(--dsw-alias-label-secondary)', fontSize: 11 } }, model.description.slice(0, 120)) : null,
|
|
1755
2056
|
row.baseURL || row.api ? React.createElement('a', { href: (row.baseURL || '') + '/models/' + encodeURIComponent(model.id), target: '_blank', rel: 'noopener', style: { marginLeft: 8, fontSize: 11 } }, 'docs') : null,
|
|
1756
2057
|
React.createElement('button', { type: 'button', onClick: (e) => { e.preventDefault(); doTry(row.provider, model.id) }, style: { marginLeft: 8, fontSize: 11, padding: '1px 6px', borderRadius: 4 } }, (probe && probe.loading) ? '…' : '▶'),
|
|
1757
|
-
probe && !probe.loading ? React.createElement('span', {
|
|
2058
|
+
probe && !probe.loading ? React.createElement('span', {
|
|
2059
|
+
className: 'dms-badge ' + (probe.ok ? (classifySpeedTier(probe.latency, probe.ok) === 'lightning' ? 'dms-tier-lightning' : classifySpeedTier(probe.latency, probe.ok) === 'fast' ? 'dms-tier-fast' : 'dms-tier-moderate') : 'dms-badge-bad'),
|
|
2060
|
+
style: { marginLeft: 6 },
|
|
2061
|
+
title: probe.ttftMs ? `TTFT: ${probe.ttftMs}ms | Latency: ${probe.latency}ms` : (probe.latency ? `${probe.latency}ms` : undefined)
|
|
2062
|
+
},
|
|
2063
|
+
(classifySpeedTier(probe.latency, probe.ok) === 'lightning' ? '⚡ ' : classifySpeedTier(probe.latency, probe.ok) === 'fast' ? '🟢 ' : classifySpeedTier(probe.latency, probe.ok) === 'moderate' ? '🟡 ' : '🔴 ') +
|
|
2064
|
+
(probe.ok ? `${probe.latency}ms` : (probe.error || probe.status || 'offline')) +
|
|
2065
|
+
(probe.sparkline ? ` ${probe.sparkline}` : '')
|
|
2066
|
+
) : null
|
|
1758
2067
|
)
|
|
1759
2068
|
}),
|
|
1760
2069
|
filteredChoices.length > pageSize ? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, margin: '8px 0' } },
|
|
@@ -1808,7 +2117,7 @@ window.__ModuleLoader__.load({
|
|
|
1808
2117
|
)
|
|
1809
2118
|
) : null
|
|
1810
2119
|
|
|
1811
|
-
return React.createElement('div', { key: row.provider, className: 'dms-provider-wrap' }, providerCard, providerConfigEditor, selectionEditor, policyEditor)
|
|
2120
|
+
return React.createElement('div', { key: row.provider, className: 'dms-provider-wrap' }, providerCard, providerConfigEditor, ollamaPanel, selectionEditor, policyEditor)
|
|
1812
2121
|
}),
|
|
1813
2122
|
React.createElement('div', { className: 'dms-section-card dms-section' },
|
|
1814
2123
|
React.createElement('div', { className: 'dms-section-title' }, React.createElement('strong', null, t.modelAliases)),
|
|
@@ -1916,7 +2225,7 @@ window.__ModuleLoader__.load({
|
|
|
1916
2225
|
trySlot('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', key: NS, locale: NS, inject: () => ({ ctx }) }, (props) => React.createElement(ErrorBoundary, null, React.createElement(ModelSyncCard, { ...props, ctx }))))
|
|
1917
2226
|
}
|
|
1918
2227
|
|
|
1919
|
-
module.exports = { apply, inject: ['slots', 'locale', 'configForms'], filterModelsByCapabilities }
|
|
2228
|
+
module.exports = { apply, inject: ['slots', 'locale', 'configForms'], filterModelsByCapabilities, deriveModelTags, isCuratedModel, generateSparkline, classifySpeedTier }
|
|
1920
2229
|
return module.exports
|
|
1921
2230
|
},
|
|
1922
2231
|
})
|