@goodandready/dsh-model-sync 0.2.10 → 0.2.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 +1 -0
- package/lib/client.js +51 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ OAuth, subscription, and tool-only routes are intentionally outside the scope.
|
|
|
15
15
|
Runtime, scheduler, HTTP API, reconciliation, model allowlists, and Settings UI are implemented. The release workflow includes staging and production verification before publication.
|
|
16
16
|
|
|
17
17
|
The Settings UI groups synchronization controls, run summaries, configured providers, diagnostics, notifications, history, and editors into responsive cards that follow the DSH theme.
|
|
18
|
+
Each configured provider row shows `enabled/total` model counts and a temporary `+N` discovery suffix for newly found models; the manual model-selection and policy editors expand directly below that row.
|
|
18
19
|
|
|
19
20
|
## Development
|
|
20
21
|
|
package/lib/client.js
CHANGED
|
@@ -5,9 +5,9 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports
|
|
6
6
|
const React = require('react')
|
|
7
7
|
|
|
8
|
-
const CSS = '.dms-wrap{display:flex;flex-direction:column;gap:14px;max-width:760px;box-sizing:border-box;color:var(--dsw-alias-label-primary,inherit)}' +
|
|
8
|
+
const CSS = '.dms-card{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-primary,rgba(255,255,255,.14)));border-radius:10px;background:var(--dsw-alias-bg-layer-1,transparent);overflow:hidden;box-sizing:border-box}' + '.dms-card-open{background:var(--dsw-alias-bg-layer-2,transparent);border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}' + '.dms-card-header{display:flex;align-items:center;gap:8px;width:100%;padding:11px 14px;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer}' + '.dms-card-header:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.05))}' + '.dms-card-header:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}' + '.dms-card-head-text{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}' + '.dms-card-name{font-weight:650}' + '.dms-card-description{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:12px;line-height:1.45}' + '.dms-card-chevron{color:var(--dsw-alias-label-tertiary);font-size:16px;transition:transform .12s}' + '.dms-card-body{display:flex;flex-direction:column;gap:10px;padding:0 14px 14px}' + '.dms-wrap{display:flex;flex-direction:column;gap:14px;max-width:760px;box-sizing:border-box;color:var(--dsw-alias-label-primary,inherit)}' +
|
|
9
9
|
'.dms-wrap>p{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}' +
|
|
10
|
-
'.dms-toolbar,.dms-section
|
|
10
|
+
'.dms-toolbar,.dms-section{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;gap:10px;padding:13px 14px;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-primary,rgba(255,255,255,.14)));border-radius:10px;background:var(--dsw-alias-bg-layer-1,transparent);box-sizing:border-box}' +
|
|
11
11
|
'.dms-toolbar>select{flex:1 1 190px;min-width:190px}' +
|
|
12
12
|
'.dms-toolbar>.dms-actions{flex-basis:100%}' +
|
|
13
13
|
'.dms-toolbar>label{display:inline-flex;align-items:flex-start;gap:7px;font-size:12px;line-height:1.35;color:var(--dsw-alias-label-secondary)}' +
|
|
@@ -19,10 +19,11 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
'.dms-btn:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.05))}' +
|
|
20
20
|
'.dms-btn:disabled{opacity:.45;cursor:default}' +
|
|
21
21
|
'.dms-section>strong{display:block;font-size:13px;font-weight:650}' +
|
|
22
|
-
'.dms-provider{
|
|
22
|
+
'.dms-provider-wrap{margin:0 0 7px}' +
|
|
23
|
+
'.dms-provider{display:grid!important;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:10px;padding:10px 11px;margin:0;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-primary));border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent)}' +
|
|
23
24
|
'.dms-provider>span{min-width:0;overflow-wrap:anywhere;line-height:1.4}' +
|
|
24
25
|
'.dms-section>div[style]{padding:8px 0;border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
25
|
-
'.dms-editor{border-color:var(--dsw-alias-brand-primary,var(--dsw-alias-border-l2))}' +
|
|
26
|
+
'.dms-editor{display:flex;flex-direction:column;align-items:stretch;gap:10px;padding:13px 14px;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-primary));border-radius:10px;background:var(--dsw-alias-bg-layer-1,transparent);box-sizing:border-box;border-color:var(--dsw-alias-brand-primary,var(--dsw-alias-border-l2))}' +
|
|
26
27
|
'.dms-editor strong{font-size:13px}' +
|
|
27
28
|
'.dms-editor label{display:block;gap:5px;margin-top:8px;color:var(--dsw-alias-label-secondary);font-size:12px}' +
|
|
28
29
|
'.dms-editor label input,.dms-editor label textarea{width:100%}' +
|
|
@@ -40,29 +41,30 @@ window.__ModuleLoader__.load({
|
|
|
40
41
|
document.head.appendChild(tag)
|
|
41
42
|
}
|
|
42
43
|
const en = {
|
|
43
|
-
title: 'Model synchronization', desc: 'Refresh model catalogs for API-key providers. A dry-run is the default; applying changes is explicit.', loading: 'Loading…', refresh: 'Refresh status', refreshAll: 'Refresh all', run: 'Discover', apply: 'Apply changes', dryRun: 'Preview only (dry-run)', all: 'All API-key providers', ok: 'ready', dormant: 'not configured', running: 'running…', noProviders: 'No API-key providers found.', lastRun: 'Last run', selectModels: 'Choose models', saveSelection: 'Save and apply selection', selectAtLeastOne: 'Select at least one model or use All models.', cancel: 'Cancel', selected: 'selected', allModels: 'All models', noModels: 'No models in the current catalog.', pickerNote: 'The selection is applied to the DSH model picker immediately.', health: 'Check availability', healthOk: 'available', healthError: 'unavailable', healthDormant: 'not configured', policy: '
|
|
44
|
+
title: 'Model synchronization', expand: 'Expand', collapse: 'Collapse', desc: 'Refresh model catalogs for API-key providers. A dry-run is the default; applying changes is explicit.', loading: 'Loading…', refresh: 'Refresh status', refreshAll: 'Refresh all', run: 'Discover', apply: 'Apply changes', dryRun: 'Preview only (dry-run)', all: 'All API-key providers', ok: 'ready', dormant: 'not configured', running: 'running…', noProviders: 'No API-key providers found.', lastRun: 'Last run', selectModels: 'Choose models', saveSelection: 'Save and apply selection', selectAtLeastOne: 'Select at least one model or use All models.', cancel: 'Cancel', selected: 'selected', allModels: 'All models', noModels: 'No models in the current catalog.', pickerNote: 'The selection is applied to the DSH model picker immediately.', health: 'Check availability', healthOk: 'available', healthError: 'unavailable', healthDormant: 'not configured', policy: 'Manual model selection', savePolicy: 'Save policy', policyInclude: 'Include patterns (one per line)', policyExclude: 'Exclude patterns (one per line)', policyRequire: 'Required capabilities (comma-separated)', policyDeny: 'Denied capabilities (comma-separated)', policyNote: 'Patterns match model id, name, and tags. Apply changes to update the DSH picker.', scheduler: 'Scheduler', nextRun: 'next run', schedulerOff: 'off', schedulerOn: 'on', removeMissing: 'Confirm stale removal', stale: 'stale', deprecated: 'deprecated', history: 'Synchronization history', historyRun: 'Run', diffAdded: 'added', diffRemoved: 'removed', diffRenamed: 'renamed', diffChanged: 'metadata changed', rollback: 'Rollback catalog', rollbackConfirm: 'Rollback this provider catalog to the selected snapshot?', credentials: 'Credential diagnostics', checkCredentials: 'Check credentials', missingCredential: 'not configured', credentialReady: 'configured', report: 'Run report', success: 'success', partial: 'partial success', failure: 'failed', empty: 'no configured providers', notifications: 'Notifications', markRead: 'Mark read', acknowledge: 'Acknowledge', noNotifications: 'No notifications.',
|
|
44
45
|
}
|
|
45
46
|
const ru = {
|
|
46
|
-
title: 'Синхронизация моделей', desc: 'Обновляет каталоги моделей у провайдеров с API-ключом. По умолчанию только dry-run; применение запускается явно.', loading: 'Загрузка…', refresh: 'Обновить статус', refreshAll: 'Обновить все', run: 'Проверить', apply: 'Применить изменения', dryRun: 'Только просмотр (dry-run)', all: 'Все API-key провайдеры', ok: 'готов', dormant: 'не настроен', running: 'выполняется…', noProviders: 'API-key провайдеры не найдены.', lastRun: 'Последний запуск', selectModels: 'Выбрать модели', saveSelection: 'Сохранить и применить выбор', selectAtLeastOne: 'Выберите хотя бы одну модель или нажмите «Все модели».', cancel: 'Отмена', selected: 'выбрано', allModels: 'Все модели', noModels: 'В текущем каталоге нет моделей.', pickerNote: 'Выбор сразу применяется к пикеру моделей DSH.', health: 'Проверить доступность', healthOk: 'доступен', healthError: 'недоступен', healthDormant: 'не настроен', policy: '
|
|
47
|
+
title: 'Синхронизация моделей', expand: 'Развернуть', collapse: 'Свернуть', desc: 'Обновляет каталоги моделей у провайдеров с API-ключом. По умолчанию только dry-run; применение запускается явно.', loading: 'Загрузка…', refresh: 'Обновить статус', refreshAll: 'Обновить все', run: 'Проверить', apply: 'Применить изменения', dryRun: 'Только просмотр (dry-run)', all: 'Все API-key провайдеры', ok: 'готов', dormant: 'не настроен', running: 'выполняется…', noProviders: 'API-key провайдеры не найдены.', lastRun: 'Последний запуск', selectModels: 'Выбрать модели', saveSelection: 'Сохранить и применить выбор', selectAtLeastOne: 'Выберите хотя бы одну модель или нажмите «Все модели».', cancel: 'Отмена', selected: 'выбрано', allModels: 'Все модели', noModels: 'В текущем каталоге нет моделей.', pickerNote: 'Выбор сразу применяется к пикеру моделей DSH.', health: 'Проверить доступность', healthOk: 'доступен', healthError: 'недоступен', healthDormant: 'не настроен', policy: 'Ручной выбор моделей', savePolicy: 'Сохранить политику', policyInclude: 'Включать (по одному шаблону в строке)', policyExclude: 'Исключать (по одному шаблону в строке)', policyRequire: 'Обязательные возможности (через запятую)', policyDeny: 'Запрещённые возможности (через запятую)', policyNote: 'Шаблоны ищутся в id, названии и тегах модели. Нажмите «Применить», чтобы обновить пикер DSH.', scheduler: 'Планировщик', nextRun: 'следующий запуск', schedulerOff: 'выключен', schedulerOn: 'включён', removeMissing: 'Подтвердить удаление устаревших', stale: 'устарела', deprecated: 'deprecated', history: 'История синхронизаций', historyRun: 'Запуск', diffAdded: 'добавлено', diffRemoved: 'удалено', diffRenamed: 'переименовано', diffChanged: 'изменена метаинформация', rollback: 'Откатить каталог', rollbackConfirm: 'Откатить каталог этого провайдера к выбранному снимку?', credentials: 'Диагностика ключей', checkCredentials: 'Проверить ключи', missingCredential: 'не настроен', credentialReady: 'настроен', report: 'Итог запуска', success: 'успех', partial: 'частичный успех', failure: 'ошибка', empty: 'нет настроенных провайдеров', notifications: 'Уведомления', markRead: 'Прочитано', acknowledge: 'Подтвердить', noNotifications: 'Новых уведомлений нет.',
|
|
47
48
|
}
|
|
48
49
|
const API = '/dsh-model-sync'
|
|
50
|
+
const NS = 'dsh-model-sync'
|
|
49
51
|
function useLocale(ctx) {
|
|
50
52
|
return React.useSyncExternalStore(React.useMemo(() => (cb) => ctx.locale?.subscribe?.(cb) ?? (() => {}), [ctx]), React.useCallback(() => ctx.locale?.getSnapshot?.().active ?? 'en', [ctx]))
|
|
51
53
|
}
|
|
52
|
-
function ModelSyncSection({ ctx }) {
|
|
54
|
+
function ModelSyncSection({ ctx, includeIntro = true }) {
|
|
53
55
|
const locale = useLocale(ctx); const t = locale === 'ru' ? ru : en
|
|
54
56
|
const [status, setStatus] = React.useState(null); const [provider, setProvider] = React.useState(''); const [dryRun, setDryRun] = React.useState(true); const [removeMissing, setRemoveMissing] = React.useState(false); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(''); const [credentialStatus, setCredentialStatus] = React.useState(null)
|
|
55
57
|
const [selectionProvider, setSelectionProvider] = React.useState(''); const [selectionDraft, setSelectionDraft] = React.useState([]); const [policyProvider, setPolicyProvider] = React.useState(''); const [policyDraft, setPolicyDraft] = React.useState({ include: '', exclude: '', requireCapabilities: '', denyCapabilities: '' })
|
|
56
58
|
const load = React.useCallback(() => { setError(''); return fetch(`${API}/status`).then((r) => r.json()).then(setStatus).catch((e) => setError(String(e?.message ?? e))) }, [])
|
|
57
59
|
const loadCredentials = React.useCallback(() => { setError(''); return fetch(`${API}/credentials`).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then(setCredentialStatus).catch((e) => setError(String(e?.message ?? e))) }, [])
|
|
58
|
-
React.useEffect(() => { ensureStyles(); load(); const timer = setInterval(load, 15000); return () => clearInterval(timer) }, [load])
|
|
60
|
+
React.useEffect(() => { ensureStyles(); load(); loadCredentials(); const timer = setInterval(load, 15000); return () => clearInterval(timer) }, [load, loadCredentials])
|
|
59
61
|
const execute = (targetProvider) => { if (!dryRun && removeMissing && !window.confirm(t.removeMissing)) return; setBusy(true); setError(''); fetch(`${API}/run`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider || undefined, dryRun, removeMissing }) }).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then(() => load()).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(false)) }
|
|
60
62
|
const checkHealth = (targetProvider) => { setBusy(true); setError(''); fetch(`${API}/health`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(targetProvider ? { provider: targetProvider } : {}) }).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then(setStatus).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(false)) }
|
|
61
63
|
const checkCredentials = (targetProvider) => { setBusy(true); setError(''); fetch(`${API}/credentials/check`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(targetProvider ? { provider: targetProvider } : {}) }).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then((body) => { setCredentialStatus(body.credentials); setStatus((current) => ({ ...(current ?? {}), lastHealth: body.health })) }).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(false)) }
|
|
62
64
|
const updateNotification = (id, action) => { setBusy(true); setError(''); fetch(API + '/notifications/' + action, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id }) }).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? ('HTTP ' + r.status)); return body }).then(() => load()).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(false)) }
|
|
63
|
-
const openSelection = (row) => { const available = row.availableModels ?? row.models ?? []; const defaults = row.selectableModels ?? available; setSelectionProvider(row.provider); setSelectionDraft(row.selectedModels?.length ? row.selectedModels : defaults.map((model) => model.id)) }
|
|
65
|
+
const openSelection = (row) => { const available = row.availableModels ?? row.models ?? []; const defaults = row.selectableModels ?? available; closePolicy(); setSelectionProvider(row.provider); setSelectionDraft(row.selectedModels?.length ? row.selectedModels : defaults.map((model) => model.id)) }
|
|
64
66
|
const closeSelection = () => { setSelectionProvider(''); setSelectionDraft([]) }
|
|
65
|
-
const openPolicy = (row) => { const policy = status?.policies?.[row.provider] ?? row.policy ?? {}; setPolicyProvider(row.provider); setPolicyDraft({ include: (policy.include ?? []).join('\n'), exclude: (policy.exclude ?? []).join('\n'), requireCapabilities: Object.keys(policy.requireCapabilities ?? {}).join(', '), denyCapabilities: Object.keys(policy.denyCapabilities ?? {}).join(', ') }) }
|
|
67
|
+
const openPolicy = (row) => { const policy = status?.policies?.[row.provider] ?? row.policy ?? {}; closeSelection(); setPolicyProvider(row.provider); setPolicyDraft({ include: (policy.include ?? []).join('\n'), exclude: (policy.exclude ?? []).join('\n'), requireCapabilities: Object.keys(policy.requireCapabilities ?? {}).join(', '), denyCapabilities: Object.keys(policy.denyCapabilities ?? {}).join(', ') }) }
|
|
66
68
|
const closePolicy = () => { setPolicyProvider(''); setPolicyDraft({ include: '', exclude: '', requireCapabilities: '', denyCapabilities: '' }) }
|
|
67
69
|
const splitValues = (value) => [...new Set(String(value ?? '').split(/[,\n]+/).map((item) => item.trim()).filter(Boolean))]
|
|
68
70
|
const capabilityValues = (value) => Object.fromEntries(splitValues(value).map((key) => [key, true]))
|
|
@@ -95,19 +97,52 @@ window.__ModuleLoader__.load({
|
|
|
95
97
|
})))
|
|
96
98
|
}
|
|
97
99
|
return React.createElement('div', { className: 'dms-wrap' },
|
|
98
|
-
React.createElement('p', null, t.desc),
|
|
100
|
+
includeIntro ? React.createElement('p', null, t.desc) : null,
|
|
99
101
|
React.createElement('div', { className: 'dms-toolbar' }, React.createElement('select', { value: provider, onChange: (e) => setProvider(e.target.value) }, React.createElement('option', { value: '' }, t.all), rows.map((row) => React.createElement('option', { key: row.provider, value: row.provider }, row.provider))), React.createElement('label', { style: { marginLeft: 12 } }, React.createElement('input', { type: 'checkbox', checked: dryRun, onChange: (e) => setDryRun(e.target.checked) }), ` ${t.dryRun}`), React.createElement('label', { style: { marginLeft: 12 } }, React.createElement('input', { type: 'checkbox', checked: removeMissing, onChange: (e) => setRemoveMissing(e.target.checked) }), ` ${t.removeMissing}`)),
|
|
100
102
|
React.createElement('div', { className: 'dms-actions' }, button(t.refresh, load), button(t.refreshAll, () => execute(undefined)), button(dryRun ? t.run : t.apply, () => execute(provider || undefined)), button(t.health, () => checkHealth(provider || undefined)), button(t.checkCredentials, () => checkCredentials(provider || undefined))),
|
|
101
103
|
status?.lastRun ? React.createElement('small', null, t.lastRun + ': ' + new Date(status.lastRun.finishedAt).toLocaleString()) : null, status?.lastReport ? React.createElement('div', { style: { marginTop: 8 } }, React.createElement('strong', null, t.report), React.createElement('small', { style: { display: 'block' } }, (t[status.lastReport.outcome] ?? status.lastReport.outcome) + ' · ' + (status.lastReport.counts?.succeeded ?? 0) + '/' + (status.lastReport.counts?.providers ?? 0) + ' providers · ' + (status.lastReport.counts?.models?.added ?? 0) + ' ' + t.diffAdded)) : null, status?.scheduler ? React.createElement('small', null, `${t.scheduler}: ${status.scheduler.active ? t.schedulerOn : t.schedulerOff}${status.scheduler.nextRunAt ? ` · ${t.nextRun}: ${new Date(status.scheduler.nextRunAt).toLocaleString()}` : ''}`) : null, status?.running || busy ? React.createElement('small', null, t.running) : null, error ? React.createElement('small', { style: { color: 'var(--dsw-alias-state-error-primary)' } }, error) : null, status === null ? React.createElement('small', null, t.loading) : null, rows.length === 0 && status ? React.createElement('small', null, t.noProviders) : null,
|
|
102
|
-
rows.map((row) => {
|
|
104
|
+
rows.map((row) => {
|
|
105
|
+
const health = status?.lastHealth?.results?.find((item) => item.provider === row.provider)
|
|
106
|
+
const healthText = health ? ` · ${health.status === 'ok' ? t.healthOk : health.status === 'dormant' ? t.healthDormant : t.healthError}` : ''
|
|
107
|
+
const lifecycleCounts = (row.lifecycleModels ?? []).reduce((counts, model) => { if (model.lifecycleStatus === 'stale') counts.stale += 1; if (model.lifecycleStatus === 'deprecated') counts.deprecated += 1; return counts }, { stale: 0, deprecated: 0 })
|
|
108
|
+
const lifecycleText = lifecycleCounts.stale || lifecycleCounts.deprecated ? ` · ${lifecycleCounts.stale} ${t.stale}, ${lifecycleCounts.deprecated} ${t.deprecated}` : ''
|
|
109
|
+
const modelCount = (row.availableModels ?? row.models ?? []).length
|
|
110
|
+
const latestRun = status?.lastRun?.results?.find((item) => item.provider === row.provider)
|
|
111
|
+
const addedCount = latestRun?.status === 'ok' ? (latestRun.diff?.added?.length ?? 0) : 0
|
|
112
|
+
const modelSummary = `${(row.models ?? []).length}/${modelCount} models${addedCount > 0 ? ` +${addedCount}` : ''}`
|
|
113
|
+
const providerCard = React.createElement('div', { className: 'dms-provider' }, React.createElement('span', null, `${row.provider}: ${row.configured ? t.ok : t.dormant} · ${modelSummary}${lifecycleText}${healthText}`), button(t.selectModels, () => openSelection(row)), button(t.policy, () => openPolicy(row)))
|
|
114
|
+
const selectionEditor = selectionProvider === row.provider ? React.createElement('div', { className: 'dms-editor' }, React.createElement('strong', null, `${t.selectModels}: ${selectionProvider}`), React.createElement('div', { style: { margin: '8px 0' } }, choices.length === 0 ? React.createElement('small', null, t.noModels) : choices.map((model) => React.createElement('label', { key: model.id, style: { display: 'block', margin: '4px 0' } }, 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)) }), ` ${model.name ?? model.id} (${model.id})`))), React.createElement('small', null, t.pickerNote), React.createElement('div', { style: { marginTop: 8 } }, button(t.allModels, () => setSelectionDraft(choices.map((model) => model.id))), button(t.saveSelection, saveSelection), button(t.cancel, closeSelection))) : null
|
|
115
|
+
const policyEditor = policyProvider === row.provider ? React.createElement('div', { className: 'dms-editor' }, React.createElement('strong', null, `${t.policy}: ${policyProvider}`), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyInclude, React.createElement('textarea', { rows: 3, value: policyDraft.include, onChange: (e) => setPolicyDraft((current) => ({ ...current, include: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyExclude, React.createElement('textarea', { rows: 3, value: policyDraft.exclude, onChange: (e) => setPolicyDraft((current) => ({ ...current, exclude: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyRequire, React.createElement('input', { value: policyDraft.requireCapabilities, onChange: (e) => setPolicyDraft((current) => ({ ...current, requireCapabilities: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyDeny, React.createElement('input', { value: policyDraft.denyCapabilities, onChange: (e) => setPolicyDraft((current) => ({ ...current, denyCapabilities: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('small', null, t.policyNote), React.createElement('div', { style: { marginTop: 8 } }, button(t.savePolicy, savePolicy), button(t.cancel, closePolicy))) : null
|
|
116
|
+
return React.createElement('div', { key: row.provider, className: 'dms-provider-wrap' }, providerCard, selectionEditor, policyEditor)
|
|
117
|
+
}),
|
|
103
118
|
React.createElement('div', { className: 'dms-section' }, React.createElement('strong', null, t.notifications), renderNotifications()),
|
|
104
|
-
React.createElement('div', { className: 'dms-section' }, React.createElement('strong', null, t.credentials), (credentialStatus
|
|
119
|
+
React.createElement('div', { className: 'dms-section' }, React.createElement('strong', null, t.credentials), credentialStatus === null ? React.createElement('small', { style: { display: 'block', marginTop: 6 } }, t.loading) : (credentialStatus.results ?? []).length === 0 ? React.createElement('small', { style: { display: 'block', marginTop: 6 } }, t.noProviders) : (credentialStatus.results ?? []).map((item) => React.createElement('div', { key: item.provider, style: { marginTop: 6 } }, React.createElement('span', null, `${item.provider}: ${item.rotation?.configured ? `${item.rotation.keyCount} refs` : 'single ref'}${item.lastRequest ? ` · ${item.lastRequest.status}` : ''}`), (item.refs ?? []).map((ref) => React.createElement('small', { key: ref.ref || ref.label, style: { display: 'block', marginLeft: 12 } }, `${ref.order}. ${ref.label} · ${ref.configured === true ? t.credentialReady : t.missingCredential}${ref.source && ref.source !== 'unknown' ? ` · ${ref.source}` : ''}${ref.lastResolution?.status ? ` · ${ref.lastResolution.status}` : ''}`))))),
|
|
105
120
|
React.createElement('div', { className: 'dms-section' }, React.createElement('strong', null, t.history), renderHistory()),
|
|
106
|
-
selectionProvider ? React.createElement('div', { className: 'dms-editor' }, React.createElement('strong', null, `${t.selectModels}: ${selectionProvider}`), React.createElement('div', { style: { margin: '8px 0' } }, choices.length === 0 ? React.createElement('small', null, t.noModels) : choices.map((model) => React.createElement('label', { key: model.id, style: { display: 'block', margin: '4px 0' } }, 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)) }), ` ${model.name ?? model.id} (${model.id})`))), React.createElement('small', null, t.pickerNote), React.createElement('div', { style: { marginTop: 8 } }, button(t.allModels, () => setSelectionDraft(choices.map((model) => model.id))), button(t.saveSelection, saveSelection), button(t.cancel, closeSelection))) : null,
|
|
107
|
-
policyProvider ? React.createElement('div', { style: { border: '1px solid var(--dsw-alias-border-primary)', padding: 12, borderRadius: 6 } }, React.createElement('strong', null, `${t.policy}: ${policyProvider}`), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyInclude, React.createElement('textarea', { rows: 3, value: policyDraft.include, onChange: (e) => setPolicyDraft((current) => ({ ...current, include: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyExclude, React.createElement('textarea', { rows: 3, value: policyDraft.exclude, onChange: (e) => setPolicyDraft((current) => ({ ...current, exclude: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyRequire, React.createElement('input', { value: policyDraft.requireCapabilities, onChange: (e) => setPolicyDraft((current) => ({ ...current, requireCapabilities: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('label', { style: { display: 'block', marginTop: 8 } }, t.policyDeny, React.createElement('input', { value: policyDraft.denyCapabilities, onChange: (e) => setPolicyDraft((current) => ({ ...current, denyCapabilities: e.target.value })), style: { display: 'block', width: '100%' } })), React.createElement('small', null, t.policyNote), React.createElement('div', { style: { marginTop: 8 } }, button(t.savePolicy, savePolicy), button(t.cancel, closePolicy))) : null,
|
|
108
121
|
)
|
|
109
122
|
}
|
|
110
|
-
function
|
|
123
|
+
function ModelSyncCard({ ctx }) {
|
|
124
|
+
const locale = useLocale(ctx); const t = locale === 'ru' ? ru : en; const [open, setOpen] = React.useState(false)
|
|
125
|
+
return React.createElement('div', { className: 'dms-card' + (open ? ' dms-card-open' : '') },
|
|
126
|
+
React.createElement('button', { type: 'button', className: 'dms-card-header', 'aria-expanded': open, 'aria-label': `${t[open ? 'collapse' : 'expand']}: ${t.title}`, onClick: () => setOpen((value) => !value) },
|
|
127
|
+
React.createElement('span', { className: 'dms-card-head-text' }, React.createElement('span', { className: 'dms-card-name' }, t.title), React.createElement('span', { className: 'dms-card-description' }, t.desc)),
|
|
128
|
+
React.createElement('span', { className: 'dms-card-chevron', 'aria-hidden': 'true' }, open ? '▴' : '▾')),
|
|
129
|
+
open ? React.createElement('div', { className: 'dms-card-body' }, React.createElement(ModelSyncSection, { ctx, includeIntro: false })) : null)
|
|
130
|
+
}
|
|
131
|
+
function apply(ctx) {
|
|
132
|
+
ctx.effect(() => {
|
|
133
|
+
try { ctx.locale?.register?.(NS, { en, ru }) } catch {}
|
|
134
|
+
return () => {}
|
|
135
|
+
}, 'dsh-model-sync: dictionaries')
|
|
136
|
+
const registerPluginCard = () => {
|
|
137
|
+
try {
|
|
138
|
+
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', key: NS, locale: NS, inject: () => ({ ctx }) }, (props) => React.createElement(ModelSyncCard, { ...props, ctx })))
|
|
139
|
+
return true
|
|
140
|
+
} catch { return false }
|
|
141
|
+
}
|
|
142
|
+
if (!registerPluginCard()) {
|
|
143
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'dsh-model-sync', order: 25, label: () => 'Model Sync' }, (props) => React.createElement(ModelSyncSection, { ...props, ctx })))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
111
146
|
module.exports = { apply, inject: ['slots', 'locale'] }; return module.exports
|
|
112
147
|
},
|
|
113
148
|
})
|