@goodandready/dsh-model-sync 0.5.11 → 0.5.12
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 +17 -9
- package/README.ru.md +17 -9
- package/README.zh.md +17 -9
- package/lib/client.js +416 -32
- package/lib/models.js +25 -0
- package/lib/reconcile.js +2 -0
- package/lib/synchronizer.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,23 +137,31 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
|
|
|
137
137
|
```yaml
|
|
138
138
|
dsh-model-sync:
|
|
139
139
|
enabled: true
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
scheduleEnabled: false
|
|
141
|
+
intervalMinutes: 60
|
|
142
|
+
autoApply: false
|
|
143
|
+
requestTimeoutMs: 15000
|
|
144
|
+
retryAttempts: 3
|
|
145
|
+
concurrency: 4
|
|
143
146
|
providers:
|
|
144
|
-
commandcode
|
|
147
|
+
- provider: commandcode
|
|
145
148
|
enabled: true
|
|
146
|
-
|
|
149
|
+
autoApply: false
|
|
150
|
+
- provider: clinebot
|
|
147
151
|
enabled: true
|
|
152
|
+
autoApply: false
|
|
148
153
|
```
|
|
149
154
|
|
|
150
155
|
| Parameter | Type | Default | Description |
|
|
151
156
|
|---|---|---|---|
|
|
152
157
|
| `enabled` | `boolean` | `true` | Master switch for background synchronization |
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
158
|
+
| `scheduleEnabled` | `boolean` | `false` | Enable periodic background synchronization schedule |
|
|
159
|
+
| `intervalMinutes` | `number` | `60` | Polling frequency for catalog refreshes in minutes |
|
|
160
|
+
| `autoApply` | `boolean` | `false` | Automatically apply discovered models to active catalog |
|
|
161
|
+
| `requestTimeoutMs`| `number` | `15000` | HTTP request timeout for catalog fetching (ms) |
|
|
162
|
+
| `retryAttempts` | `number` | `3` | Maximum retry attempts for transient network failures |
|
|
163
|
+
| `concurrency` | `number` | `4` | Maximum parallel provider synchronization workers |
|
|
164
|
+
| `providers` | `array` | `[]` | Per-provider configuration list (`provider`, `enabled`, `autoApply`) |
|
|
157
165
|
|
|
158
166
|
---
|
|
159
167
|
|
package/README.ru.md
CHANGED
|
@@ -137,23 +137,31 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
|
|
|
137
137
|
```yaml
|
|
138
138
|
dsh-model-sync:
|
|
139
139
|
enabled: true
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
scheduleEnabled: false
|
|
141
|
+
intervalMinutes: 60
|
|
142
|
+
autoApply: false
|
|
143
|
+
requestTimeoutMs: 15000
|
|
144
|
+
retryAttempts: 3
|
|
145
|
+
concurrency: 4
|
|
143
146
|
providers:
|
|
144
|
-
commandcode
|
|
147
|
+
- provider: commandcode
|
|
145
148
|
enabled: true
|
|
146
|
-
|
|
149
|
+
autoApply: false
|
|
150
|
+
- provider: clinebot
|
|
147
151
|
enabled: true
|
|
152
|
+
autoApply: false
|
|
148
153
|
```
|
|
149
154
|
|
|
150
155
|
| Параметр | Тип | По умолчанию | Описание |
|
|
151
156
|
|---|---|---|---|
|
|
152
157
|
| `enabled` | `boolean` | `true` | Главный переключатель фоновой синхронизации |
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
158
|
+
| `scheduleEnabled` | `boolean` | `false` | Включение периодического фонового расписания синхронизации |
|
|
159
|
+
| `intervalMinutes` | `number` | `60` | Периодичность опроса провайдеров (в минутах) |
|
|
160
|
+
| `autoApply` | `boolean` | `false` | Автоматически применять обнаруженные модели к активному каталогу |
|
|
161
|
+
| `requestTimeoutMs`| `number` | `15000` | Таймаут HTTP-запросов синхронизации каталогов (в миллисекундах) |
|
|
162
|
+
| `retryAttempts` | `number` | `3` | Максимальное число повторных попыток при сетевых сбоях |
|
|
163
|
+
| `concurrency` | `number` | `4` | Максимальное количество параллельных задач синхронизации |
|
|
164
|
+
| `providers` | `array` | `[]` | Список индивидуальных настроек провайдеров (`provider`, `enabled`, `autoApply`) |
|
|
157
165
|
|
|
158
166
|
---
|
|
159
167
|
|
package/README.zh.md
CHANGED
|
@@ -137,23 +137,31 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
|
|
|
137
137
|
```yaml
|
|
138
138
|
dsh-model-sync:
|
|
139
139
|
enabled: true
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
scheduleEnabled: false
|
|
141
|
+
intervalMinutes: 60
|
|
142
|
+
autoApply: false
|
|
143
|
+
requestTimeoutMs: 15000
|
|
144
|
+
retryAttempts: 3
|
|
145
|
+
concurrency: 4
|
|
143
146
|
providers:
|
|
144
|
-
commandcode
|
|
147
|
+
- provider: commandcode
|
|
145
148
|
enabled: true
|
|
146
|
-
|
|
149
|
+
autoApply: false
|
|
150
|
+
- provider: clinebot
|
|
147
151
|
enabled: true
|
|
152
|
+
autoApply: false
|
|
148
153
|
```
|
|
149
154
|
|
|
150
155
|
| 参数名称 | 类型 | 默认值 | 功能说明 |
|
|
151
156
|
|---|---|---|---|
|
|
152
157
|
| `enabled` | `boolean` | `true` | 后台自动同步主开关 |
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
158
|
+
| `scheduleEnabled` | `boolean` | `false` | 启用周期性后台定时同步任务 |
|
|
159
|
+
| `intervalMinutes` | `number` | `60` | 目录更新轮询间隔(分钟) |
|
|
160
|
+
| `autoApply` | `boolean` | `false` | 是否自动将新发现的模型生效至可用列表 |
|
|
161
|
+
| `requestTimeoutMs`| `number` | `15000` | 目录拉取 HTTP 请求超时时间(毫秒) |
|
|
162
|
+
| `retryAttempts` | `number` | `3` | 瞬态网络故障的最大重试次数 |
|
|
163
|
+
| `concurrency` | `number` | `4` | 最大并发服务商同步任务数 |
|
|
164
|
+
| `providers` | `array` | `[]` | 服务商独立配置列表(`provider`、`enabled`、`autoApply`) |
|
|
157
165
|
|
|
158
166
|
---
|
|
159
167
|
|
package/lib/client.js
CHANGED
|
@@ -108,14 +108,24 @@ window.__ModuleLoader__.load({
|
|
|
108
108
|
|
|
109
109
|
function ensureStyles() {
|
|
110
110
|
if (typeof document === 'undefined' || !document.head) return
|
|
111
|
+
const pluginId = '@goodandready/dsh-model-sync'
|
|
112
|
+
const styleId = 'dsh-model-sync-styles'
|
|
111
113
|
const cssId = 'dsh-model-sync/settings.module.css'
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
let tag = document.getElementById(styleId) || document.querySelector('style[data-plugin="' + pluginId + '"]') || document.querySelector('style[data-dsh-plugin="' + pluginId + '"]') || document.querySelector('style[data-plugin="dsh-model-sync"]') || document.querySelector('style[data-dsh-plugin="dsh-model-sync"]')
|
|
115
|
+
if (tag) {
|
|
116
|
+
if (tag.textContent !== CSS) tag.textContent = CSS
|
|
117
|
+
tag.setAttribute('data-plugin', pluginId)
|
|
118
|
+
tag.setAttribute('data-dsh-plugin', pluginId)
|
|
119
|
+
return tag
|
|
120
|
+
}
|
|
121
|
+
tag = document.createElement('style')
|
|
122
|
+
tag.id = styleId
|
|
114
123
|
tag.textContent = CSS
|
|
115
|
-
tag.setAttribute('data-plugin',
|
|
116
|
-
tag.setAttribute('data-dsh-plugin',
|
|
124
|
+
tag.setAttribute('data-plugin', pluginId)
|
|
125
|
+
tag.setAttribute('data-dsh-plugin', pluginId)
|
|
117
126
|
tag.setAttribute('data-plugin-css', cssId)
|
|
118
127
|
document.head.appendChild(tag)
|
|
128
|
+
return tag
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
const en = {
|
|
@@ -266,6 +276,25 @@ window.__ModuleLoader__.load({
|
|
|
266
276
|
updateFailed: 'Update failed: ',
|
|
267
277
|
checkUpdateFailed: 'Failed to check for updates',
|
|
268
278
|
targetProvider: 'Provider',
|
|
279
|
+
reliabilitySettings: 'Reliability & Engine configuration',
|
|
280
|
+
requestTimeoutMs: 'Request timeout (ms)',
|
|
281
|
+
retryAttempts: 'Retry attempts',
|
|
282
|
+
retryBaseDelayMs: 'Retry base delay (ms)',
|
|
283
|
+
retryMaxDelayMs: 'Retry max delay (ms)',
|
|
284
|
+
concurrency: 'Worker concurrency',
|
|
285
|
+
circuitBreakerFailures: 'Circuit breaker failure limit',
|
|
286
|
+
circuitBreakerCooldownMinutes: 'Circuit breaker cooldown (min)',
|
|
287
|
+
ttlMinutes: 'Catalog cache TTL (min, 0=off)',
|
|
288
|
+
lifecycleRetentionRuns: 'Lifecycle retention runs',
|
|
289
|
+
saveReliability: 'Save reliability settings',
|
|
290
|
+
providerSettings: 'Provider configuration',
|
|
291
|
+
enableProvider: 'Enable synchronization',
|
|
292
|
+
providerAutoApply: 'Auto-apply for this provider',
|
|
293
|
+
customEndpoint: 'Custom API endpoint',
|
|
294
|
+
saveProvider: 'Save provider settings',
|
|
295
|
+
saveFailed: 'Save rejected by settings service',
|
|
296
|
+
scopeReadOnly: 'Settings are read-only in this environment',
|
|
297
|
+
firstSeen: 'First seen',
|
|
269
298
|
}
|
|
270
299
|
|
|
271
300
|
const zh = {
|
|
@@ -416,6 +445,25 @@ window.__ModuleLoader__.load({
|
|
|
416
445
|
updateFailed: '更新失败: ',
|
|
417
446
|
checkUpdateFailed: '检查更新失败',
|
|
418
447
|
targetProvider: '服务商',
|
|
448
|
+
reliabilitySettings: '可靠性与引擎参数设置',
|
|
449
|
+
requestTimeoutMs: '请求超时时间(毫秒)',
|
|
450
|
+
retryAttempts: '重试次数',
|
|
451
|
+
retryBaseDelayMs: '重试基础延迟(毫秒)',
|
|
452
|
+
retryMaxDelayMs: '重试最大延迟(毫秒)',
|
|
453
|
+
concurrency: '并发任务数',
|
|
454
|
+
circuitBreakerFailures: '熔断器失败阈值',
|
|
455
|
+
circuitBreakerCooldownMinutes: '熔断冷却时间(分钟)',
|
|
456
|
+
ttlMinutes: '目录缓存 TTL(分钟,0为禁用)',
|
|
457
|
+
lifecycleRetentionRuns: '生命周期保留轮次',
|
|
458
|
+
saveReliability: '保存可靠性设置',
|
|
459
|
+
providerSettings: '服务商配置',
|
|
460
|
+
enableProvider: '启用同步',
|
|
461
|
+
providerAutoApply: '自动应用该服务商变更',
|
|
462
|
+
customEndpoint: '自定义 API 端点',
|
|
463
|
+
saveProvider: '保存服务商设置',
|
|
464
|
+
saveFailed: '设置服务拒绝保存',
|
|
465
|
+
scopeReadOnly: '当前环境为只读设置',
|
|
466
|
+
firstSeen: '首次发现',
|
|
419
467
|
}
|
|
420
468
|
|
|
421
469
|
const API = '/dsh-model-sync'
|
|
@@ -444,6 +492,41 @@ window.__ModuleLoader__.load({
|
|
|
444
492
|
)
|
|
445
493
|
}
|
|
446
494
|
|
|
495
|
+
function createTranslator(ctx, props, baseDict) {
|
|
496
|
+
const slotT = typeof props?.t === 'function' ? props.t : null
|
|
497
|
+
const bindT = typeof ctx?.locale?.bind === 'function' ? ctx.locale.bind(NS) : null
|
|
498
|
+
return new Proxy(baseDict, {
|
|
499
|
+
get(target, prop) {
|
|
500
|
+
if (typeof prop !== 'string') return target[prop]
|
|
501
|
+
if (slotT) {
|
|
502
|
+
try {
|
|
503
|
+
const res = slotT(prop)
|
|
504
|
+
if (res && res !== prop && res !== `${NS}.${prop}`) return res
|
|
505
|
+
} catch (_err) {
|
|
506
|
+
// fallback
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (bindT) {
|
|
510
|
+
try {
|
|
511
|
+
const res = bindT(prop)
|
|
512
|
+
if (res && res !== prop && res !== `${NS}.${prop}`) return res
|
|
513
|
+
} catch (_err) {
|
|
514
|
+
// fallback
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (typeof ctx?.locale?.t === 'function') {
|
|
518
|
+
try {
|
|
519
|
+
const res = ctx.locale.t(`${NS}.${prop}`) || ctx.locale.t(prop)
|
|
520
|
+
if (res && res !== prop && res !== `${NS}.${prop}`) return res
|
|
521
|
+
} catch (_err) {
|
|
522
|
+
// fallback
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return target[prop] ?? prop
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
447
530
|
|
|
448
531
|
function UpdaterSection({ t, api }) {
|
|
449
532
|
const [updaterStatus, setUpdaterStatus] = React.useState(null)
|
|
@@ -522,9 +605,10 @@ window.__ModuleLoader__.load({
|
|
|
522
605
|
)
|
|
523
606
|
}
|
|
524
607
|
|
|
525
|
-
function ModelSyncSection({ ctx, includeIntro = true }) {
|
|
608
|
+
function ModelSyncSection({ ctx, props, includeIntro = true }) {
|
|
526
609
|
const locale = useLocale(ctx)
|
|
527
|
-
const
|
|
610
|
+
const baseDict = locale === 'zh' ? zh : en
|
|
611
|
+
const t = React.useMemo(() => createTranslator(ctx, props, baseDict), [ctx, props, locale])
|
|
528
612
|
const [status, setStatus] = React.useState(null)
|
|
529
613
|
const [provider, setProvider] = React.useState('')
|
|
530
614
|
const [dryRun, setDryRun] = React.useState(true)
|
|
@@ -581,6 +665,7 @@ window.__ModuleLoader__.load({
|
|
|
581
665
|
)
|
|
582
666
|
const snapStatus = snapshot?.status || 'loading'
|
|
583
667
|
const stored = snapshot?.value || {}
|
|
668
|
+
const isWritable = snapshot?.writable !== false && scope?.writable !== false
|
|
584
669
|
const [schedDraft, setSchedDraft] = React.useState(null)
|
|
585
670
|
const [webhookDraft, setWebhookDraft] = React.useState(null)
|
|
586
671
|
const [webhookMsg, setWebhookMsg] = React.useState('')
|
|
@@ -589,22 +674,70 @@ window.__ModuleLoader__.load({
|
|
|
589
674
|
const [schedMsg, setSchedMsg] = React.useState('')
|
|
590
675
|
const [schedErr, setSchedErr] = React.useState('')
|
|
591
676
|
|
|
677
|
+
const [reliabilityDraft, setReliabilityDraft] = React.useState(null)
|
|
678
|
+
const [reliabilityMsg, setReliabilityMsg] = React.useState('')
|
|
679
|
+
const [reliabilityErr, setReliabilityErr] = React.useState('')
|
|
680
|
+
|
|
681
|
+
const [providerConfigDraft, setProviderConfigDraft] = React.useState({})
|
|
682
|
+
const [providerConfigOpen, setProviderConfigOpen] = React.useState('')
|
|
683
|
+
const [providerConfigMsg, setProviderConfigMsg] = React.useState('')
|
|
684
|
+
const [providerConfigErr, setProviderConfigErr] = React.useState('')
|
|
685
|
+
|
|
592
686
|
React.useEffect(() => {
|
|
593
|
-
if (snapStatus === 'ready'
|
|
594
|
-
|
|
687
|
+
if (snapStatus === 'ready') {
|
|
688
|
+
if (schedDraft === null) {
|
|
689
|
+
setSchedDraft({
|
|
690
|
+
scheduleEnabled: stored.scheduleEnabled ?? false,
|
|
691
|
+
intervalMinutes: stored.intervalMinutes ?? 60,
|
|
692
|
+
autoApply: stored.autoApply ?? false,
|
|
693
|
+
})
|
|
694
|
+
}
|
|
695
|
+
if (webhookDraft === null) {
|
|
696
|
+
setWebhookDraft({
|
|
697
|
+
diffWebhookUrl: stored.diffWebhookUrl ?? '',
|
|
698
|
+
diffWebhookSecretRef: stored.diffWebhookSecretRef ?? '',
|
|
699
|
+
diffWebhookSecret: stored.diffWebhookSecret ?? stored.diffWebhookSecretRef ?? '',
|
|
700
|
+
notifyOnModelAdded: stored.notifyOnModelAdded ?? true,
|
|
701
|
+
notifyOnModelDeprecated: stored.notifyOnModelDeprecated ?? true,
|
|
702
|
+
notifyOnPriceChanged: stored.notifyOnPriceChanged ?? true,
|
|
703
|
+
})
|
|
704
|
+
}
|
|
705
|
+
if (reliabilityDraft === null) {
|
|
706
|
+
setReliabilityDraft({
|
|
707
|
+
requestTimeoutMs: stored.requestTimeoutMs ?? 15000,
|
|
708
|
+
retryAttempts: stored.retryAttempts ?? 3,
|
|
709
|
+
retryBaseDelayMs: stored.retryBaseDelayMs ?? 250,
|
|
710
|
+
retryMaxDelayMs: stored.retryMaxDelayMs ?? 4000,
|
|
711
|
+
concurrency: stored.concurrency ?? 4,
|
|
712
|
+
circuitBreakerFailures: stored.circuitBreakerFailures ?? 3,
|
|
713
|
+
circuitBreakerCooldownMinutes: stored.circuitBreakerCooldownMinutes ?? 5,
|
|
714
|
+
ttlMinutes: stored.ttlMinutes ?? 0,
|
|
715
|
+
lifecycleRetentionRuns: stored.lifecycleRetentionRuns ?? 20,
|
|
716
|
+
})
|
|
717
|
+
}
|
|
595
718
|
}
|
|
596
|
-
}, [snapStatus, stored, schedDraft])
|
|
719
|
+
}, [snapStatus, stored, schedDraft, webhookDraft, reliabilityDraft])
|
|
597
720
|
|
|
598
721
|
const saveWebhook = async () => {
|
|
599
722
|
if (!scope || !webhookDraft) return
|
|
723
|
+
if (!isWritable) {
|
|
724
|
+
setWebhookErr(t.scopeReadOnly || 'Settings are read-only')
|
|
725
|
+
return
|
|
726
|
+
}
|
|
600
727
|
setBusy(true); setWebhookErr(''); setWebhookMsg('')
|
|
601
728
|
try {
|
|
602
|
-
await
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
729
|
+
const results = await Promise.all([
|
|
730
|
+
scope.set('diffWebhookUrl', webhookDraft.diffWebhookUrl || ''),
|
|
731
|
+
scope.set('diffWebhookSecretRef', webhookDraft.diffWebhookSecretRef || webhookDraft.diffWebhookSecret || ''),
|
|
732
|
+
scope.set('diffWebhookSecret', webhookDraft.diffWebhookSecret || ''),
|
|
733
|
+
scope.set('notifyOnModelAdded', !!webhookDraft.notifyOnModelAdded),
|
|
734
|
+
scope.set('notifyOnModelDeprecated', !!webhookDraft.notifyOnModelDeprecated),
|
|
735
|
+
scope.set('notifyOnPriceChanged', !!webhookDraft.notifyOnPriceChanged),
|
|
736
|
+
])
|
|
737
|
+
if (results.some((ok) => ok === false)) {
|
|
738
|
+
setWebhookErr(t.saveFailed || 'Save rejected by settings service')
|
|
739
|
+
return
|
|
740
|
+
}
|
|
608
741
|
setWebhookMsg(t.webhookSaved)
|
|
609
742
|
} catch (e) {
|
|
610
743
|
setWebhookErr(String(e?.message ?? e))
|
|
@@ -641,11 +774,21 @@ window.__ModuleLoader__.load({
|
|
|
641
774
|
|
|
642
775
|
const saveScheduler = async () => {
|
|
643
776
|
if (!scope || !schedDraft) return
|
|
777
|
+
if (!isWritable) {
|
|
778
|
+
setSchedErr(t.scopeReadOnly || 'Settings are read-only')
|
|
779
|
+
return
|
|
780
|
+
}
|
|
644
781
|
setBusy(true); setSchedErr(''); setSchedMsg('')
|
|
645
782
|
try {
|
|
646
|
-
await
|
|
647
|
-
|
|
648
|
-
|
|
783
|
+
const results = await Promise.all([
|
|
784
|
+
scope.set('scheduleEnabled', !!schedDraft.scheduleEnabled),
|
|
785
|
+
scope.set('intervalMinutes', Number(schedDraft.intervalMinutes) || 60),
|
|
786
|
+
scope.set('autoApply', !!schedDraft.autoApply),
|
|
787
|
+
])
|
|
788
|
+
if (results.some((ok) => ok === false)) {
|
|
789
|
+
setSchedErr(t.saveFailed || 'Save rejected by settings service')
|
|
790
|
+
return
|
|
791
|
+
}
|
|
649
792
|
setSchedMsg(t.schedulerSaved)
|
|
650
793
|
load()
|
|
651
794
|
} catch (e) {
|
|
@@ -655,6 +798,97 @@ window.__ModuleLoader__.load({
|
|
|
655
798
|
}
|
|
656
799
|
}
|
|
657
800
|
|
|
801
|
+
const saveReliability = async () => {
|
|
802
|
+
if (!scope || !reliabilityDraft) return
|
|
803
|
+
if (!isWritable) {
|
|
804
|
+
setReliabilityErr(t.scopeReadOnly || 'Settings are read-only')
|
|
805
|
+
return
|
|
806
|
+
}
|
|
807
|
+
setBusy(true); setReliabilityErr(''); setReliabilityMsg('')
|
|
808
|
+
try {
|
|
809
|
+
const results = await Promise.all([
|
|
810
|
+
scope.set('requestTimeoutMs', Number(reliabilityDraft.requestTimeoutMs) || 15000),
|
|
811
|
+
scope.set('retryAttempts', Number(reliabilityDraft.retryAttempts) || 3),
|
|
812
|
+
scope.set('retryBaseDelayMs', Number(reliabilityDraft.retryBaseDelayMs) || 250),
|
|
813
|
+
scope.set('retryMaxDelayMs', Number(reliabilityDraft.retryMaxDelayMs) || 4000),
|
|
814
|
+
scope.set('concurrency', Number(reliabilityDraft.concurrency) || 4),
|
|
815
|
+
scope.set('circuitBreakerFailures', Number(reliabilityDraft.circuitBreakerFailures) || 3),
|
|
816
|
+
scope.set('circuitBreakerCooldownMinutes', Number(reliabilityDraft.circuitBreakerCooldownMinutes) || 5),
|
|
817
|
+
scope.set('ttlMinutes', Number(reliabilityDraft.ttlMinutes) || 0),
|
|
818
|
+
scope.set('lifecycleRetentionRuns', Number(reliabilityDraft.lifecycleRetentionRuns) || 20),
|
|
819
|
+
])
|
|
820
|
+
if (results.some((ok) => ok === false)) {
|
|
821
|
+
setReliabilityErr(t.saveFailed || 'Save rejected by settings service')
|
|
822
|
+
return
|
|
823
|
+
}
|
|
824
|
+
setReliabilityMsg(t.schedulerSaved)
|
|
825
|
+
} catch (e) {
|
|
826
|
+
setReliabilityErr(String(e?.message ?? e))
|
|
827
|
+
} finally {
|
|
828
|
+
setBusy(false)
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const openProviderConfig = (row) => {
|
|
833
|
+
if (providerConfigOpen === row.provider) {
|
|
834
|
+
setProviderConfigOpen('')
|
|
835
|
+
return
|
|
836
|
+
}
|
|
837
|
+
setProviderConfigOpen(row.provider)
|
|
838
|
+
setProviderConfigMsg('')
|
|
839
|
+
setProviderConfigErr('')
|
|
840
|
+
const pList = Array.isArray(stored.providers) ? stored.providers : []
|
|
841
|
+
const pConf = pList.find((p) => p.provider === row.provider) || {}
|
|
842
|
+
const aRegistry = stored.adapterRegistry && typeof stored.adapterRegistry === 'object' ? stored.adapterRegistry : {}
|
|
843
|
+
const aConf = aRegistry[row.provider] || {}
|
|
844
|
+
setProviderConfigDraft({
|
|
845
|
+
enabled: pConf.enabled !== false,
|
|
846
|
+
autoApply: !!pConf.autoApply,
|
|
847
|
+
endpoint: aConf.endpoint || '',
|
|
848
|
+
})
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const saveProviderConfig = async (providerName) => {
|
|
852
|
+
if (!scope) return
|
|
853
|
+
if (!isWritable) {
|
|
854
|
+
setProviderConfigErr(t.scopeReadOnly || 'Settings are read-only')
|
|
855
|
+
return
|
|
856
|
+
}
|
|
857
|
+
setBusy(true); setProviderConfigErr(''); setProviderConfigMsg('')
|
|
858
|
+
try {
|
|
859
|
+
const pList = Array.isArray(stored.providers) ? [...stored.providers] : []
|
|
860
|
+
const existingIdx = pList.findIndex((p) => p.provider === providerName)
|
|
861
|
+
const updatedEntry = {
|
|
862
|
+
...(existingIdx >= 0 ? pList[existingIdx] : {}),
|
|
863
|
+
provider: providerName,
|
|
864
|
+
enabled: !!providerConfigDraft.enabled,
|
|
865
|
+
autoApply: !!providerConfigDraft.autoApply,
|
|
866
|
+
}
|
|
867
|
+
if (existingIdx >= 0) pList[existingIdx] = updatedEntry
|
|
868
|
+
else pList.push(updatedEntry)
|
|
869
|
+
|
|
870
|
+
const aRegistry = { ...(stored.adapterRegistry && typeof stored.adapterRegistry === 'object' ? stored.adapterRegistry : {}) }
|
|
871
|
+
aRegistry[providerName] = {
|
|
872
|
+
...(aRegistry[providerName] || {}),
|
|
873
|
+
endpoint: (providerConfigDraft.endpoint || '').trim(),
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const results = await Promise.all([
|
|
877
|
+
scope.set('providers', pList),
|
|
878
|
+
scope.set('adapterRegistry', aRegistry),
|
|
879
|
+
])
|
|
880
|
+
if (results.some((ok) => ok === false)) {
|
|
881
|
+
setProviderConfigErr(t.saveFailed || 'Save rejected by settings service')
|
|
882
|
+
return
|
|
883
|
+
}
|
|
884
|
+
setProviderConfigMsg(t.schedulerSaved)
|
|
885
|
+
} catch (e) {
|
|
886
|
+
setProviderConfigErr(String(e?.message ?? e))
|
|
887
|
+
} finally {
|
|
888
|
+
setBusy(false)
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
658
892
|
const doTry = (targetProvider, targetModel) => {
|
|
659
893
|
const key = `${targetProvider}:${targetModel}`
|
|
660
894
|
setTryState((current) => ({ ...current, [key]: { loading: true } }))
|
|
@@ -820,14 +1054,18 @@ window.__ModuleLoader__.load({
|
|
|
820
1054
|
|
|
821
1055
|
React.useEffect(() => { ensureStyles(); load() }, [])
|
|
822
1056
|
|
|
823
|
-
const execute = (targetProvider) => {
|
|
1057
|
+
const execute = (targetProvider, options = {}) => {
|
|
1058
|
+
const effectiveDryRun = options.dryRun !== undefined ? options.dryRun : dryRun
|
|
824
1059
|
setBusy(true); setError(''); setPreview(null)
|
|
825
|
-
fetch(`${API}/run`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider, dryRun, removeMissing }) })
|
|
1060
|
+
fetch(`${API}/run`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider, dryRun: effectiveDryRun, removeMissing }) })
|
|
826
1061
|
.then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body })
|
|
827
1062
|
.then((body) => {
|
|
828
1063
|
setStatus(body)
|
|
829
|
-
if (
|
|
830
|
-
else
|
|
1064
|
+
if (effectiveDryRun && body.results) setPreview(body.results)
|
|
1065
|
+
else {
|
|
1066
|
+
setPreview(null)
|
|
1067
|
+
load()
|
|
1068
|
+
}
|
|
831
1069
|
})
|
|
832
1070
|
.catch((e) => setError(String(e?.message ?? e)))
|
|
833
1071
|
.finally(() => setBusy(false))
|
|
@@ -915,6 +1153,7 @@ window.__ModuleLoader__.load({
|
|
|
915
1153
|
})
|
|
916
1154
|
}
|
|
917
1155
|
|
|
1156
|
+
const splitLines = (value) => [...new Set(String(value ?? '').split(/\r?\n/).map((item) => item.trim()).filter(Boolean))]
|
|
918
1157
|
const splitValues = (value) => [...new Set(String(value ?? '').split(/[,\n]+/).map((item) => item.trim()).filter(Boolean))]
|
|
919
1158
|
const capabilityValues = (value) => Object.fromEntries(splitValues(value).map((key) => [key, true]))
|
|
920
1159
|
const validatePatterns = (patterns) => {
|
|
@@ -925,8 +1164,8 @@ window.__ModuleLoader__.load({
|
|
|
925
1164
|
}
|
|
926
1165
|
|
|
927
1166
|
const savePolicy = () => {
|
|
928
|
-
const includeList =
|
|
929
|
-
const excludeList =
|
|
1167
|
+
const includeList = splitLines(policyDraft.include)
|
|
1168
|
+
const excludeList = splitLines(policyDraft.exclude)
|
|
930
1169
|
const badInclude = validatePatterns(includeList)
|
|
931
1170
|
if (badInclude) { setError((t.invalidPattern || 'Invalid regex pattern: %s').replace('%s', badInclude)); return }
|
|
932
1171
|
const badExclude = validatePatterns(excludeList)
|
|
@@ -1007,7 +1246,11 @@ window.__ModuleLoader__.load({
|
|
|
1007
1246
|
}) : filteredChoicesBase
|
|
1008
1247
|
const quickFilteredChoices = searchedChoices.filter((m) => {
|
|
1009
1248
|
if (quickFilter.context100k && (!m.contextWindow || m.contextWindow < 100000)) return false
|
|
1010
|
-
if (quickFilter.cheap
|
|
1249
|
+
if (quickFilter.cheap) {
|
|
1250
|
+
const price = m.pricing?.inputPerToken
|
|
1251
|
+
if (typeof price !== 'number' || Number.isNaN(price)) return false
|
|
1252
|
+
if ((price * 1000000) > 1.0) return false
|
|
1253
|
+
}
|
|
1011
1254
|
return true
|
|
1012
1255
|
})
|
|
1013
1256
|
return [...quickFilteredChoices].sort((a, b) => {
|
|
@@ -1017,7 +1260,11 @@ window.__ModuleLoader__.load({
|
|
|
1017
1260
|
return pa - pb
|
|
1018
1261
|
}
|
|
1019
1262
|
if (sortBy === 'context') return (b.contextWindow ?? 0) - (a.contextWindow ?? 0)
|
|
1020
|
-
if (sortBy === 'date')
|
|
1263
|
+
if (sortBy === 'date') {
|
|
1264
|
+
const da = a.firstSeenAt ? new Date(a.firstSeenAt).getTime() : (a.createdAt ? new Date(a.createdAt).getTime() : 0)
|
|
1265
|
+
const db = b.firstSeenAt ? new Date(b.firstSeenAt).getTime() : (b.createdAt ? new Date(b.createdAt).getTime() : 0)
|
|
1266
|
+
return db - da
|
|
1267
|
+
}
|
|
1021
1268
|
return (a.name || a.id || '').localeCompare(b.name || b.id || '')
|
|
1022
1269
|
})
|
|
1023
1270
|
}, [choices, capabilityFilter, debouncedSearchQuery, quickFilter, sortBy])
|
|
@@ -1101,6 +1348,90 @@ window.__ModuleLoader__.load({
|
|
|
1101
1348
|
)
|
|
1102
1349
|
}
|
|
1103
1350
|
|
|
1351
|
+
const renderReliability = () => {
|
|
1352
|
+
return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8, width: '100%' } },
|
|
1353
|
+
snapStatus === 'loading' ? React.createElement('small', null, t.loading) : null,
|
|
1354
|
+
snapStatus === 'unavailable' ? React.createElement('small', { style: { color: 'var(--dsw-alias-label-secondary)' } }, t.scopeUnavailable) : null,
|
|
1355
|
+
reliabilityDraft ? React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10, marginTop: 4 } },
|
|
1356
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1357
|
+
React.createElement('span', null, t.requestTimeoutMs + ':'),
|
|
1358
|
+
React.createElement('input', {
|
|
1359
|
+
type: 'number',
|
|
1360
|
+
value: reliabilityDraft.requestTimeoutMs,
|
|
1361
|
+
onChange: (e) => setReliabilityDraft((d) => ({ ...d, requestTimeoutMs: Number(e.target.value) || 0 })),
|
|
1362
|
+
disabled: busy || !isWritable,
|
|
1363
|
+
className: 'dms-input',
|
|
1364
|
+
})
|
|
1365
|
+
),
|
|
1366
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1367
|
+
React.createElement('span', null, t.retryAttempts + ':'),
|
|
1368
|
+
React.createElement('input', {
|
|
1369
|
+
type: 'number',
|
|
1370
|
+
value: reliabilityDraft.retryAttempts,
|
|
1371
|
+
onChange: (e) => setReliabilityDraft((d) => ({ ...d, retryAttempts: Number(e.target.value) || 0 })),
|
|
1372
|
+
disabled: busy || !isWritable,
|
|
1373
|
+
className: 'dms-input',
|
|
1374
|
+
})
|
|
1375
|
+
),
|
|
1376
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1377
|
+
React.createElement('span', null, t.concurrency + ':'),
|
|
1378
|
+
React.createElement('input', {
|
|
1379
|
+
type: 'number',
|
|
1380
|
+
value: reliabilityDraft.concurrency,
|
|
1381
|
+
onChange: (e) => setReliabilityDraft((d) => ({ ...d, concurrency: Number(e.target.value) || 0 })),
|
|
1382
|
+
disabled: busy || !isWritable,
|
|
1383
|
+
className: 'dms-input',
|
|
1384
|
+
})
|
|
1385
|
+
),
|
|
1386
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1387
|
+
React.createElement('span', null, t.circuitBreakerFailures + ':'),
|
|
1388
|
+
React.createElement('input', {
|
|
1389
|
+
type: 'number',
|
|
1390
|
+
value: reliabilityDraft.circuitBreakerFailures,
|
|
1391
|
+
onChange: (e) => setReliabilityDraft((d) => ({ ...d, circuitBreakerFailures: Number(e.target.value) || 0 })),
|
|
1392
|
+
disabled: busy || !isWritable,
|
|
1393
|
+
className: 'dms-input',
|
|
1394
|
+
})
|
|
1395
|
+
),
|
|
1396
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1397
|
+
React.createElement('span', null, t.circuitBreakerCooldownMinutes + ':'),
|
|
1398
|
+
React.createElement('input', {
|
|
1399
|
+
type: 'number',
|
|
1400
|
+
value: reliabilityDraft.circuitBreakerCooldownMinutes,
|
|
1401
|
+
onChange: (e) => setReliabilityDraft((d) => ({ ...d, circuitBreakerCooldownMinutes: Number(e.target.value) || 0 })),
|
|
1402
|
+
disabled: busy || !isWritable,
|
|
1403
|
+
className: 'dms-input',
|
|
1404
|
+
})
|
|
1405
|
+
),
|
|
1406
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1407
|
+
React.createElement('span', null, t.ttlMinutes + ':'),
|
|
1408
|
+
React.createElement('input', {
|
|
1409
|
+
type: 'number',
|
|
1410
|
+
value: reliabilityDraft.ttlMinutes,
|
|
1411
|
+
onChange: (e) => setReliabilityDraft((d) => ({ ...d, ttlMinutes: Number(e.target.value) || 0 })),
|
|
1412
|
+
disabled: busy || !isWritable,
|
|
1413
|
+
className: 'dms-input',
|
|
1414
|
+
})
|
|
1415
|
+
),
|
|
1416
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1417
|
+
React.createElement('span', null, t.lifecycleRetentionRuns + ':'),
|
|
1418
|
+
React.createElement('input', {
|
|
1419
|
+
type: 'number',
|
|
1420
|
+
value: reliabilityDraft.lifecycleRetentionRuns,
|
|
1421
|
+
onChange: (e) => setReliabilityDraft((d) => ({ ...d, lifecycleRetentionRuns: Number(e.target.value) || 0 })),
|
|
1422
|
+
disabled: busy || !isWritable,
|
|
1423
|
+
className: 'dms-input',
|
|
1424
|
+
})
|
|
1425
|
+
),
|
|
1426
|
+
React.createElement('div', { style: { gridColumn: '1 / -1', display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 } },
|
|
1427
|
+
button(t.saveReliability, saveReliability, 'primary'),
|
|
1428
|
+
reliabilityMsg ? React.createElement('small', { style: { color: 'var(--dsw-alias-state-success-primary)' } }, reliabilityMsg) : null,
|
|
1429
|
+
reliabilityErr ? React.createElement('small', { style: { color: 'var(--dsw-alias-state-error-primary)' } }, reliabilityErr) : null
|
|
1430
|
+
)
|
|
1431
|
+
) : null
|
|
1432
|
+
)
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1104
1435
|
const renderAliases = () => {
|
|
1105
1436
|
const aliasMap = status?.aliases ?? {}
|
|
1106
1437
|
const entries = Object.entries(aliasMap)
|
|
@@ -1241,7 +1572,7 @@ window.__ModuleLoader__.load({
|
|
|
1241
1572
|
)
|
|
1242
1573
|
}),
|
|
1243
1574
|
React.createElement('div', { style: { marginTop: 8 } },
|
|
1244
|
-
button(t.previewApply, () => execute(provider || undefined), 'primary'),
|
|
1575
|
+
button(t.previewApply, () => execute(provider || undefined, { dryRun: false }), 'primary'),
|
|
1245
1576
|
button(t.previewDismiss, () => setPreview(null))
|
|
1246
1577
|
)
|
|
1247
1578
|
) : null,
|
|
@@ -1270,9 +1601,51 @@ window.__ModuleLoader__.load({
|
|
|
1270
1601
|
' ', healthBadge, ' ', staleBadge
|
|
1271
1602
|
),
|
|
1272
1603
|
button(t.selectModels, () => openSelection(row)),
|
|
1273
|
-
button(t.policy, () => openPolicy(row))
|
|
1604
|
+
button(t.policy, () => openPolicy(row)),
|
|
1605
|
+
button(t.providerSettings || 'Configure', () => openProviderConfig(row))
|
|
1274
1606
|
)
|
|
1275
1607
|
|
|
1608
|
+
const providerConfigEditor = providerConfigOpen === row.provider ? React.createElement('div', { className: 'dms-editor' },
|
|
1609
|
+
React.createElement('strong', null, `${t.providerSettings}: ${row.provider}`),
|
|
1610
|
+
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8, margin: '8px 0' } },
|
|
1611
|
+
React.createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 7, fontSize: 12, cursor: 'pointer' } },
|
|
1612
|
+
React.createElement('input', {
|
|
1613
|
+
type: 'checkbox',
|
|
1614
|
+
checked: !!providerConfigDraft.enabled,
|
|
1615
|
+
onChange: (e) => setProviderConfigDraft((d) => ({ ...d, enabled: e.target.checked })),
|
|
1616
|
+
disabled: busy || !isWritable
|
|
1617
|
+
}),
|
|
1618
|
+
t.enableProvider
|
|
1619
|
+
),
|
|
1620
|
+
React.createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 7, fontSize: 12, cursor: 'pointer' } },
|
|
1621
|
+
React.createElement('input', {
|
|
1622
|
+
type: 'checkbox',
|
|
1623
|
+
checked: !!providerConfigDraft.autoApply,
|
|
1624
|
+
onChange: (e) => setProviderConfigDraft((d) => ({ ...d, autoApply: e.target.checked })),
|
|
1625
|
+
disabled: busy || !isWritable
|
|
1626
|
+
}),
|
|
1627
|
+
t.providerAutoApply
|
|
1628
|
+
),
|
|
1629
|
+
React.createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 12 } },
|
|
1630
|
+
React.createElement('span', null, t.customEndpoint + ':'),
|
|
1631
|
+
React.createElement('input', {
|
|
1632
|
+
type: 'text',
|
|
1633
|
+
placeholder: 'https://api.example.com/v1',
|
|
1634
|
+
value: providerConfigDraft.endpoint || '',
|
|
1635
|
+
onChange: (e) => setProviderConfigDraft((d) => ({ ...d, endpoint: e.target.value })),
|
|
1636
|
+
disabled: busy || !isWritable,
|
|
1637
|
+
className: 'dms-input'
|
|
1638
|
+
})
|
|
1639
|
+
),
|
|
1640
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 } },
|
|
1641
|
+
button(t.saveProvider, () => saveProviderConfig(row.provider), 'primary'),
|
|
1642
|
+
button(t.cancel, () => setProviderConfigOpen('')),
|
|
1643
|
+
providerConfigMsg ? React.createElement('small', { style: { color: 'var(--dsw-alias-state-success-primary)' } }, providerConfigMsg) : null,
|
|
1644
|
+
providerConfigErr ? React.createElement('small', { style: { color: 'var(--dsw-alias-state-error-primary)' } }, providerConfigErr) : null
|
|
1645
|
+
)
|
|
1646
|
+
)
|
|
1647
|
+
) : null
|
|
1648
|
+
|
|
1276
1649
|
const selectionEditor = selectionProvider === row.provider ? React.createElement('div', { className: 'dms-editor' },
|
|
1277
1650
|
React.createElement('strong', null, `${t.selectModels}: ${selectionProvider}`),
|
|
1278
1651
|
React.createElement('div', { style: { display: 'flex', gap: 8, margin: '8px 0', flexWrap: 'wrap' } },
|
|
@@ -1382,7 +1755,7 @@ window.__ModuleLoader__.load({
|
|
|
1382
1755
|
)
|
|
1383
1756
|
) : null
|
|
1384
1757
|
|
|
1385
|
-
return React.createElement('div', { key: row.provider, className: 'dms-provider-wrap' }, providerCard, selectionEditor, policyEditor)
|
|
1758
|
+
return React.createElement('div', { key: row.provider, className: 'dms-provider-wrap' }, providerCard, providerConfigEditor, selectionEditor, policyEditor)
|
|
1386
1759
|
}),
|
|
1387
1760
|
React.createElement('div', { className: 'dms-section-card dms-section' },
|
|
1388
1761
|
React.createElement('div', { className: 'dms-section-title' }, React.createElement('strong', null, t.modelAliases)),
|
|
@@ -1408,6 +1781,10 @@ window.__ModuleLoader__.load({
|
|
|
1408
1781
|
React.createElement('div', { className: 'dms-section-title' }, React.createElement('strong', null, t.schedulerSettings)),
|
|
1409
1782
|
renderScheduler()
|
|
1410
1783
|
),
|
|
1784
|
+
React.createElement('div', { className: 'dms-section-card dms-section' },
|
|
1785
|
+
React.createElement('div', { className: 'dms-section-title' }, React.createElement('strong', null, t.reliabilitySettings)),
|
|
1786
|
+
renderReliability()
|
|
1787
|
+
),
|
|
1411
1788
|
React.createElement('div', { className: 'dms-section-card dms-section' },
|
|
1412
1789
|
React.createElement('div', { className: 'dms-section-title' }, React.createElement('strong', null, t.history)),
|
|
1413
1790
|
renderHistory()
|
|
@@ -1419,9 +1796,10 @@ window.__ModuleLoader__.load({
|
|
|
1419
1796
|
)
|
|
1420
1797
|
}
|
|
1421
1798
|
|
|
1422
|
-
function ModelSyncCard({ ctx, view }) {
|
|
1799
|
+
function ModelSyncCard({ ctx, view, ...props }) {
|
|
1423
1800
|
const locale = useLocale(ctx)
|
|
1424
|
-
const
|
|
1801
|
+
const baseDict = locale === 'zh' ? zh : en
|
|
1802
|
+
const t = React.useMemo(() => createTranslator(ctx, props, baseDict), [ctx, props, locale])
|
|
1425
1803
|
const page = view === 'page'
|
|
1426
1804
|
const [open, setOpen] = React.useState(!!page)
|
|
1427
1805
|
const [cardStatus, setCardStatus] = React.useState(null)
|
|
@@ -1455,12 +1833,18 @@ window.__ModuleLoader__.load({
|
|
|
1455
1833
|
React.createElement('path', { d: 'M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5', fill: 'currentColor' })
|
|
1456
1834
|
)
|
|
1457
1835
|
),
|
|
1458
|
-
(page || open) ? React.createElement('div', { className: 'dms-card-body' }, React.createElement(ErrorBoundary, null, React.createElement(ModelSyncSection, { ctx, includeIntro: false }))) : null
|
|
1836
|
+
(page || open) ? React.createElement('div', { className: 'dms-card-body' }, React.createElement(ErrorBoundary, null, React.createElement(ModelSyncSection, { ctx, props, includeIntro: false }))) : null
|
|
1459
1837
|
)
|
|
1460
1838
|
}
|
|
1461
1839
|
|
|
1462
1840
|
function apply(ctx) {
|
|
1463
1841
|
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-model-sync: dictionaries')
|
|
1842
|
+
ctx.effect(() => {
|
|
1843
|
+
const tag = ensureStyles()
|
|
1844
|
+
return () => {
|
|
1845
|
+
if (tag && tag.parentNode) tag.parentNode.removeChild(tag)
|
|
1846
|
+
}
|
|
1847
|
+
}, 'dsh-model-sync: styles')
|
|
1464
1848
|
const trySlot = (name, register) => {
|
|
1465
1849
|
try {
|
|
1466
1850
|
if (typeof ctx.slots?.inject === 'function') ctx.slots.inject(name, register)
|
package/lib/models.js
CHANGED
|
@@ -277,6 +277,27 @@ function normalizePricing(row) {
|
|
|
277
277
|
return pricing
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
function normalizeDate(val) {
|
|
281
|
+
if (val === undefined || val === null || val === '') return undefined
|
|
282
|
+
if (typeof val === 'number' && Number.isFinite(val)) {
|
|
283
|
+
const ms = val < 1e11 ? val * 1000 : val
|
|
284
|
+
const d = new Date(ms)
|
|
285
|
+
return Number.isNaN(d.getTime()) ? undefined : d.toISOString()
|
|
286
|
+
}
|
|
287
|
+
if (typeof val === 'string' && val.trim()) {
|
|
288
|
+
const s = val.trim()
|
|
289
|
+
if (/^\d+$/.test(s)) {
|
|
290
|
+
const n = Number(s)
|
|
291
|
+
const ms = n < 1e11 ? n * 1000 : n
|
|
292
|
+
const d = new Date(ms)
|
|
293
|
+
return Number.isNaN(d.getTime()) ? undefined : d.toISOString()
|
|
294
|
+
}
|
|
295
|
+
const d = new Date(s)
|
|
296
|
+
return Number.isNaN(d.getTime()) ? undefined : d.toISOString()
|
|
297
|
+
}
|
|
298
|
+
return undefined
|
|
299
|
+
}
|
|
300
|
+
|
|
280
301
|
export function normalizeModel(provider, row) {
|
|
281
302
|
if (typeof row === 'string') {
|
|
282
303
|
row = { id: row }
|
|
@@ -297,10 +318,14 @@ export function normalizeModel(provider, row) {
|
|
|
297
318
|
const lifecycle = normalizeLifecycle(row)
|
|
298
319
|
const input = normalizeInput(row)
|
|
299
320
|
const compat = normalizeCompat(effectiveProvider, row, id)
|
|
321
|
+
const firstSeenAt = normalizeDate(row.firstSeenAt)
|
|
322
|
+
const createdAt = normalizeDate(row.createdAt ?? row.created)
|
|
300
323
|
return {
|
|
301
324
|
provider: effectiveProvider || provider,
|
|
302
325
|
id,
|
|
303
326
|
name,
|
|
327
|
+
...(firstSeenAt === undefined ? {} : { firstSeenAt }),
|
|
328
|
+
...(createdAt === undefined ? {} : { createdAt }),
|
|
304
329
|
...(contextWindow === undefined ? {} : { contextWindow }),
|
|
305
330
|
...(maxTokens === undefined ? {} : { maxTokens }),
|
|
306
331
|
...(row.description ? { description: String(row.description) } : {}),
|
package/lib/reconcile.js
CHANGED
|
@@ -22,6 +22,8 @@ export function reconcileModels(provider, current, discovered, {
|
|
|
22
22
|
const merged = {
|
|
23
23
|
...row,
|
|
24
24
|
...fresh,
|
|
25
|
+
...(row.firstSeenAt || fresh.firstSeenAt ? { firstSeenAt: row.firstSeenAt ?? fresh.firstSeenAt } : {}),
|
|
26
|
+
...(row.createdAt || fresh.createdAt ? { createdAt: row.createdAt ?? fresh.createdAt } : {}),
|
|
25
27
|
...(row.capabilities || fresh.capabilities ? {
|
|
26
28
|
capabilities: { ...(fresh.capabilities ?? {}), ...(row.capabilities ?? {}) },
|
|
27
29
|
} : {}),
|
package/lib/synchronizer.js
CHANGED
|
@@ -326,6 +326,19 @@ export function createModelSynchronizer(ctx, { getConfig = () => ({}), saveConfi
|
|
|
326
326
|
const current = profileOf(settings, row.provider).models ?? row.models
|
|
327
327
|
const fullModels = normalizeModels(row.provider, discovered.models)
|
|
328
328
|
const lifecycleBefore = normalizeModels(row.provider, config.modelCatalogs?.[row.provider] ?? current)
|
|
329
|
+
const existingMap = new Map(lifecycleBefore.map((m) => [m.id, m]))
|
|
330
|
+
const nowIso = new Date(startedAt).toISOString()
|
|
331
|
+
for (const model of fullModels) {
|
|
332
|
+
const prev = existingMap.get(model.id)
|
|
333
|
+
if (prev?.firstSeenAt) {
|
|
334
|
+
model.firstSeenAt = prev.firstSeenAt
|
|
335
|
+
} else if (!model.firstSeenAt) {
|
|
336
|
+
model.firstSeenAt = nowIso
|
|
337
|
+
}
|
|
338
|
+
if (prev?.createdAt && !model.createdAt) {
|
|
339
|
+
model.createdAt = prev.createdAt
|
|
340
|
+
}
|
|
341
|
+
}
|
|
329
342
|
const lifecycle = updateLifecycle(config.modelLifecycle?.[row.provider], lifecycleBefore, fullModels, {
|
|
330
343
|
runNumber: lifecycleRun,
|
|
331
344
|
...lifecycleConfig,
|