@goodandready/dsh-moa 0.2.7 → 0.2.9
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/docs/design/DESIGN.md +35 -41
- package/lib/client.js +505 -459
- package/lib/file-workspace.js +1 -1
- package/lib/index.js +37 -5
- package/lib/moa-runner.js +300 -290
- package/lib/pricing.js +17 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1,135 +1,281 @@
|
|
|
1
|
+
// Plugin Settings Card (settings.plugin.item).
|
|
2
|
+
//
|
|
3
|
+
// Aligned with dsh-clinebot design language:
|
|
4
|
+
// - Header with real-time status chips (Host online, active preset, model counts)
|
|
5
|
+
// - Clean section cards (.moa-section-card) using native DSH design tokens (--dsw-alias-*)
|
|
6
|
+
// - Segmented preset selectors and count pills
|
|
7
|
+
// - Refined temperature sliders and searchable model picker
|
|
8
|
+
// - Statistics & telemetry summary grid
|
|
9
|
+
// - Robust error boundary and settingsScope reactivity
|
|
10
|
+
|
|
1
11
|
window.__ModuleLoader__.load({
|
|
2
12
|
id: '@goodandready/dsh-moa',
|
|
3
13
|
factory: (require) => {
|
|
4
14
|
var module = { exports: {} }
|
|
5
15
|
var exports = module.exports
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
const React = require('react')
|
|
8
17
|
const NS = 'dsh-moa'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
const TITLE = 'Mixture of Agents (MoA)'
|
|
19
|
+
const SUBTITLE = 'Ensemble parallel candidate generation with judge synthesis via /moa.'
|
|
20
|
+
|
|
21
|
+
let rootCtx = null
|
|
22
|
+
|
|
23
|
+
const en = {
|
|
24
|
+
title: 'Mixture of Agents (MoA)',
|
|
25
|
+
description: 'Ensemble model orchestration: parallel candidate proposals + judge synthesis via /moa <prompt>.',
|
|
26
|
+
'header.title': 'Mixture of Agents (MoA) Orchestration',
|
|
27
|
+
'header.sub': 'Parallel generation by candidate proposers with final evaluation and code promotion by a leading aggregator/judge. Returns to your session model automatically.',
|
|
28
|
+
'badge.online': 'MoA Active',
|
|
29
|
+
'badge.offline': 'Host Unreachable',
|
|
30
|
+
'badge.preset': 'Preset: {name}',
|
|
31
|
+
'badge.models': '{count} models available',
|
|
32
|
+
'badge.candidates': '{count} proposers',
|
|
33
|
+
'presets.title': '📋 Preset Management',
|
|
34
|
+
'presets.desc': 'Select active preset or create custom configurations for specific tasks (e.g. code review, math, quick triage).',
|
|
35
|
+
'presets.active_badge': 'Active Default',
|
|
36
|
+
'presets.set_default': 'Set as Default',
|
|
37
|
+
'presets.new_btn': '+ New Preset',
|
|
38
|
+
'presets.del_btn': 'Delete',
|
|
39
|
+
'presets.del_confirm': 'Are you sure you want to delete preset "{name}"?',
|
|
40
|
+
'presets.create': 'Create',
|
|
41
|
+
'presets.cancel': 'Cancel',
|
|
42
|
+
'presets.name_placeholder': 'New preset name (e.g. fast-audit)',
|
|
43
|
+
'aggregator.title': '⚖️ Leading Model / Judge (Synthesis & Audit)',
|
|
44
|
+
'aggregator.desc': 'Compares candidate proposals, resolves discrepancies, filters hallucination, and synthesizes final solution.',
|
|
45
|
+
'aggregator.temp_label': 'Judge Temperature',
|
|
46
|
+
'aggregator.temp_hint': 'Recommended: 0.2 – 0.4 for strict validation and deterministic synthesis.',
|
|
47
|
+
'aggregator.criteria_label': 'Judge Evaluation Criteria (Optional):',
|
|
48
|
+
'aggregator.criteria_placeholder': 'e.g. Priority: performance, zero external dependencies, robust edge-case handling...',
|
|
49
|
+
'aggregator.questions_label': 'Ask clarifying questions on broad tasks (Questionnaire)',
|
|
50
|
+
'aggregator.questions_hint': 'When enabled: for short/vague prompts, the judge synthesizes a 2-4 question checklist before code generation.',
|
|
51
|
+
'proposers.title': '👥 Candidate Models (Proposers)',
|
|
52
|
+
'proposers.desc': 'Generate independent solutions in parallel to maximize diversity and solution space coverage.',
|
|
53
|
+
'proposers.parallel_count': 'Parallel Proposers',
|
|
54
|
+
'proposers.candidate': 'Candidate',
|
|
55
|
+
'proposers.add_btn': '+ Add Candidate Model',
|
|
56
|
+
'proposers.temp_label': 'Candidates Temperature',
|
|
57
|
+
'proposers.temp_hint': 'Recommended: 0.6 – 0.8 for creative, independent solution exploration.',
|
|
58
|
+
'stats.title': '📈 MoA Analytics & Telemetry',
|
|
59
|
+
'stats.desc': 'Session telemetry, historical runs, and model win-rate tracking.',
|
|
60
|
+
'stats.total_runs': 'Total Runs',
|
|
61
|
+
'stats.avg_cost': 'Avg Run Cost',
|
|
62
|
+
'stats.active_models': 'Configured Models',
|
|
63
|
+
'stats.preset_count': 'Total Presets',
|
|
64
|
+
'picker.select': 'Select model…',
|
|
65
|
+
'picker.search_placeholder': 'Search model by name or provider…',
|
|
66
|
+
'picker.found': 'Found',
|
|
67
|
+
'picker.of': 'of',
|
|
68
|
+
'picker.clear': 'Clear',
|
|
69
|
+
'picker.none': 'No matching models found.',
|
|
70
|
+
'picker.custom': 'Use custom: ',
|
|
71
|
+
'actions.save': 'Save Configuration',
|
|
72
|
+
'actions.saving': 'Saving…',
|
|
73
|
+
'actions.saved': 'Configuration saved successfully!',
|
|
74
|
+
'actions.retry': 'Retry Connection',
|
|
75
|
+
'slash.desc': 'Run turn with Mixture of Agents ensemble synthesis (/moa [preset] <prompt>)',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ru = {
|
|
79
|
+
title: 'Mixture of Agents (MoA)',
|
|
80
|
+
description: 'Ансамбль моделей: параллельная генерация советниками и синтез ведущим судьей через /moa <prompt>.',
|
|
81
|
+
'header.title': 'Оркестрация Mixture of Agents (MoA)',
|
|
82
|
+
'header.sub': 'Параллельная генерация вариантов советниками с оценкой и продвижением лучшего кода судьей. Модель сессии восстанавливается автоматически.',
|
|
83
|
+
'badge.online': 'MoA Активен',
|
|
84
|
+
'badge.offline': 'Хост недоступен',
|
|
85
|
+
'badge.preset': 'Пресет: {name}',
|
|
86
|
+
'badge.models': '{count} моделей доступно',
|
|
87
|
+
'badge.candidates': '{count} советников',
|
|
88
|
+
'presets.title': '📋 Управление пресетами',
|
|
89
|
+
'presets.desc': 'Выберите активный пресет или создайте специализированную конфигурацию (например: для код-ревью, математики или быстрого скрипта).',
|
|
90
|
+
'presets.active_badge': 'По умолчанию',
|
|
91
|
+
'presets.set_default': 'Сделать по умолчанию',
|
|
92
|
+
'presets.new_btn': '+ Создать пресет',
|
|
93
|
+
'presets.del_btn': 'Удалить',
|
|
94
|
+
'presets.del_confirm': 'Вы уверены, что хотите удалить пресет "{name}"?',
|
|
95
|
+
'presets.create': 'Создать',
|
|
96
|
+
'presets.cancel': 'Отмена',
|
|
97
|
+
'presets.name_placeholder': 'Название нового пресета (например: fast-audit)',
|
|
98
|
+
'aggregator.title': '⚖️ Ведущая модель / Судья (Синтез и аудит)',
|
|
99
|
+
'aggregator.desc': 'Сопоставляет варианты кандидатов, устраняет ошибки, отбирает лучший код и синтезирует итоговое решение.',
|
|
100
|
+
'aggregator.temp_label': 'Температура судьи',
|
|
101
|
+
'aggregator.temp_hint': 'Рекомендуется 0.2 – 0.4 для строгой проверки и синтеза без выдумок.',
|
|
102
|
+
'aggregator.criteria_label': 'Критерии оценки судьи (опционально):',
|
|
103
|
+
'aggregator.criteria_placeholder': 'например: Приоритет — производительность, чистый код, минимум зависимостей, тесты...',
|
|
104
|
+
'aggregator.questions_label': 'Спрашивать уточняющие вопросы при неясных задачах (Опросник)',
|
|
105
|
+
'aggregator.questions_hint': 'Если включено: при коротких задачах судья сформирует опросник. Если выключено: MoA сразу переходит к генерации решения.',
|
|
106
|
+
'proposers.title': '👥 Модели-кандидаты (Советники)',
|
|
107
|
+
'proposers.desc': 'Параллельно формируют независимые варианты решения одной задачи.',
|
|
108
|
+
'proposers.parallel_count': 'Параллельных моделей',
|
|
109
|
+
'proposers.candidate': 'Кандидат',
|
|
110
|
+
'proposers.add_btn': '+ Добавить модель в параллель',
|
|
111
|
+
'proposers.temp_label': 'Температура кандидатов',
|
|
112
|
+
'proposers.temp_hint': 'Рекомендуется 0.6 – 0.8 для получения независимых и разнообразных вариантов.',
|
|
113
|
+
'stats.title': '📈 Аналитика и телеметрия MoA',
|
|
114
|
+
'stats.desc': 'Мониторинг запусков, статистика побед моделей и оценка расхода токенов.',
|
|
115
|
+
'stats.total_runs': 'Всего запусков MoA',
|
|
116
|
+
'stats.avg_cost': 'Средняя стоимость',
|
|
117
|
+
'stats.active_models': 'Настроено моделей',
|
|
118
|
+
'stats.preset_count': 'Всего пресетов',
|
|
119
|
+
'picker.select': 'Выберите модель…',
|
|
120
|
+
'picker.search_placeholder': 'Поиск модели по названию или провайдеру…',
|
|
121
|
+
'picker.found': 'Найдено',
|
|
122
|
+
'picker.of': 'из',
|
|
123
|
+
'picker.clear': 'Сбросить',
|
|
124
|
+
'picker.none': 'Модели не найдены.',
|
|
125
|
+
'picker.custom': 'Использовать: ',
|
|
126
|
+
'actions.save': 'Сохранить настройки',
|
|
127
|
+
'actions.saving': 'Сохранение…',
|
|
128
|
+
'actions.saved': 'Настройки успешно сохранены!',
|
|
129
|
+
'actions.retry': 'Повторить подключение',
|
|
130
|
+
'slash.desc': 'Запуск ансамбля Mixture of Agents (/moa [preset] <prompt>)',
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function makeT(dict, fallback) {
|
|
134
|
+
return function t(key, vars) {
|
|
135
|
+
let val = (dict && dict[key]) || (fallback && fallback[key]) || key
|
|
136
|
+
if (vars && typeof val === 'string') {
|
|
137
|
+
for (const k of Object.keys(vars)) {
|
|
138
|
+
val = val.replace(new RegExp('\\{' + k + '\\}', 'g'), String(vars[k]))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return val
|
|
142
|
+
}
|
|
17
143
|
}
|
|
18
144
|
|
|
19
|
-
function FallbackChevron({ className }) {
|
|
145
|
+
function FallbackChevron({ className, style }) {
|
|
20
146
|
return React.createElement(
|
|
21
147
|
'svg',
|
|
22
148
|
{
|
|
23
|
-
className,
|
|
24
149
|
width: 14,
|
|
25
150
|
height: 14,
|
|
26
151
|
viewBox: '0 0 14 14',
|
|
27
152
|
fill: 'none',
|
|
153
|
+
'aria-hidden': true,
|
|
154
|
+
className,
|
|
155
|
+
style,
|
|
156
|
+
},
|
|
157
|
+
React.createElement('path', {
|
|
158
|
+
d: 'M3.5 5.25L7 8.75L10.5 5.25',
|
|
28
159
|
stroke: 'currentColor',
|
|
29
160
|
strokeWidth: 1.5,
|
|
30
161
|
strokeLinecap: 'round',
|
|
31
162
|
strokeLinejoin: 'round',
|
|
32
|
-
}
|
|
33
|
-
React.createElement('path', { d: 'M3.5 5.25L7 8.75L10.5 5.25' })
|
|
163
|
+
})
|
|
34
164
|
)
|
|
35
165
|
}
|
|
36
|
-
|
|
37
|
-
const Chevron = ChevronIcon || FallbackChevron
|
|
166
|
+
const Chevron = FallbackChevron
|
|
38
167
|
|
|
39
168
|
const STYLES = `
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
169
|
+
/* dsh-moa modern clinebot-aligned styling */
|
|
170
|
+
.moa-page{display:flex;flex-direction:column;gap:18px;padding:4px 0 24px;max-width:960px}
|
|
171
|
+
.moa-header{display:flex;flex-direction:column;gap:8px;padding-bottom:14px;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
172
|
+
.moa-page-title{font-size:20px;font-weight:700;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
|
173
|
+
.moa-page-sub{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}
|
|
174
|
+
|
|
175
|
+
.moa-badge-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:2px}
|
|
176
|
+
.moa-badge{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:5px;font-weight:500}
|
|
177
|
+
.moa-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:rgba(16,185,129,0.08)}
|
|
178
|
+
.moa-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:rgba(245,158,11,0.08)}
|
|
179
|
+
.moa-badge-brand{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}
|
|
180
|
+
|
|
181
|
+
.moa-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:18px 20px;display:flex;flex-direction:column;gap:14px}
|
|
182
|
+
.moa-section-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
|
|
183
|
+
.moa-section-desc{font-size:13px;color:var(--dsw-alias-label-secondary);margin-top:-6px;line-height:1.4}
|
|
184
|
+
|
|
185
|
+
.moa-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:12px}
|
|
186
|
+
.moa-stat-box{padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:4px}
|
|
187
|
+
.moa-stat-val{font-size:18px;font-weight:700;color:var(--dsw-alias-label-primary)}
|
|
188
|
+
.moa-stat-lbl{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
189
|
+
|
|
190
|
+
.moa-input{height:36px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;width:100%;box-sizing:border-box}
|
|
191
|
+
.moa-input:focus{outline:none;border-color:var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))}
|
|
192
|
+
.moa-textarea{width:100%;box-sizing:border-box;padding:10px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:13px;resize:vertical;outline:none;line-height:1.4}
|
|
193
|
+
.moa-textarea:focus{border-color:var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))}
|
|
194
|
+
|
|
195
|
+
.moa-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 14px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease}
|
|
196
|
+
.moa-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-1));border-color:var(--dsw-alias-label-secondary)}
|
|
197
|
+
.moa-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}
|
|
198
|
+
.moa-btn-primary:hover:not(:disabled){background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;opacity:0.9}
|
|
199
|
+
.moa-btn-danger{color:var(--dsw-alias-state-error-primary, #ef4444);border-color:rgba(239,68,68,0.3)}
|
|
200
|
+
.moa-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.12) !important;border-color:rgba(239,68,68,0.5)}
|
|
201
|
+
.moa-btn-sub{padding:5px 12px;font-size:12px;border-radius:6px}
|
|
202
|
+
|
|
203
|
+
/* Segmented Buttons */
|
|
204
|
+
.moa-seg-group{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
205
|
+
.moa-seg-btn{appearance:none;font:inherit;padding:6px 14px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:all .15s ease}
|
|
206
|
+
.moa-seg-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}
|
|
207
|
+
.moa-seg-btn-active{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent;font-weight:600}
|
|
208
|
+
.moa-seg-btn-active:hover{background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important}
|
|
209
|
+
|
|
210
|
+
/* Sliders */
|
|
211
|
+
.moa-slider-row{display:flex;align-items:center;gap:14px}
|
|
212
|
+
.moa-slider{flex:1;accent-color:var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary));cursor:pointer;height:6px}
|
|
213
|
+
.moa-slider-badge{min-width:48px;height:30px;padding:0 8px;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:6px;font-size:13px;font-weight:700;color:var(--dsw-alias-label-primary)}
|
|
214
|
+
|
|
215
|
+
/* Candidate Rows */
|
|
216
|
+
.moa-cand-list{display:flex;flex-direction:column;gap:10px}
|
|
217
|
+
.moa-cand-row{display:flex;align-items:center;gap:10px}
|
|
218
|
+
.moa-cand-badge{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);min-width:92px;flex-shrink:0}
|
|
219
|
+
.moa-cand-picker{flex:1;min-width:0}
|
|
220
|
+
|
|
221
|
+
/* Searchable Model Picker */
|
|
222
|
+
.moa-picker-container{position:relative;width:100%}
|
|
223
|
+
.moa-picker-btn{width:100%;min-height:38px;padding:6px 12px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;cursor:pointer;font-size:13px;text-align:left;box-sizing:border-box;gap:8px}
|
|
224
|
+
.moa-picker-btn:hover{border-color:var(--dsw-alias-label-secondary)}
|
|
225
|
+
.moa-picker-summary{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
226
|
+
.moa-prov-chip{font-size:11px;font-weight:600;text-transform:uppercase;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1, rgba(255,255,255,0.08));color:var(--dsw-alias-label-secondary);flex-shrink:0}
|
|
227
|
+
.moa-model-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}
|
|
228
|
+
.moa-picker-popover{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;box-shadow:0 12px 36px rgba(0,0,0,0.45);z-index:1000;max-height:360px;display:flex;flex-direction:column;overflow:hidden}
|
|
229
|
+
.moa-picker-search-bar{padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:4px}
|
|
230
|
+
.moa-search-input{width:100%;height:32px;padding:0 10px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;color:var(--dsw-alias-label-primary);font-size:13px;outline:none;box-sizing:border-box}
|
|
231
|
+
.moa-search-input:focus{border-color:var(--dsw-alias-label-primary)}
|
|
232
|
+
.moa-search-info{font-size:11px;color:var(--dsw-alias-label-tertiary);padding:0 2px;display:flex;justify-content:space-between}
|
|
233
|
+
.moa-picker-list{overflow-y:auto;max-height:280px;padding:6px;display:flex;flex-direction:column;gap:2px}
|
|
234
|
+
.moa-group-title{font-size:11px;font-weight:700;text-transform:uppercase;color:var(--dsw-alias-label-tertiary);padding:8px 8px 4px}
|
|
235
|
+
.moa-picker-item{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;border-radius:6px;font-size:13px;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;border:none;background:transparent;width:100%;gap:8px}
|
|
236
|
+
.moa-picker-item:hover, .moa-picker-item-selected{background:var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06))}
|
|
237
|
+
.moa-picker-check{color:var(--dsw-alias-state-success-primary, #10b981);font-weight:bold;font-size:14px;flex-shrink:0}
|
|
238
|
+
.moa-custom-opt{padding:8px 10px;border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);font-size:12px;color:var(--dsw-alias-label-primary);cursor:pointer;display:flex;align-items:center;gap:6px}
|
|
239
|
+
|
|
240
|
+
/* Alerts & Collapsible Card */
|
|
241
|
+
.moa-alert-ok{padding:10px 14px;border-radius:8px;background:rgba(16,185,129,0.1);color:var(--dsw-alias-state-success-primary);font-size:13px;font-weight:500}
|
|
242
|
+
.moa-alert-err{padding:10px 14px;border-radius:8px;background:rgba(239,68,68,0.1);color:var(--dsw-alias-state-error-primary);font-size:13px;font-weight:500}
|
|
243
|
+
.moa-card{list-style:none;margin:0;padding:0;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;overflow:hidden;transition:border-color .15s ease}
|
|
244
|
+
.moa-head{width:100%;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;background:transparent;border:none;color:inherit;cursor:pointer;text-align:left}
|
|
245
|
+
.moa-head:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
246
|
+
.moa-title{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
247
|
+
.moa-sub{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:2px}
|
|
248
|
+
.moa-chev{transition:transform .2s ease;color:var(--dsw-alias-label-secondary)}
|
|
249
|
+
.moa-chev-open{transform:rotate(180deg)}
|
|
250
|
+
.moa-body{padding:0 18px 20px;border-top:1px solid var(--dsw-alias-border-l2)}
|
|
251
|
+
`
|
|
112
252
|
|
|
113
253
|
let stylesInjected = false
|
|
114
254
|
function ensureStyles() {
|
|
115
255
|
if (stylesInjected || typeof document === 'undefined') return
|
|
116
256
|
stylesInjected = true
|
|
117
257
|
const style = document.createElement('style')
|
|
258
|
+
style.id = 'dsh-moa-full-css'
|
|
259
|
+
if (style.dataset) {
|
|
260
|
+
style.dataset.dshPlugin = NS
|
|
261
|
+
} else if (typeof style.setAttribute === 'function') {
|
|
262
|
+
style.setAttribute('data-dsh-plugin', NS)
|
|
263
|
+
}
|
|
118
264
|
style.textContent = STYLES
|
|
119
265
|
document.head.appendChild(style)
|
|
120
266
|
}
|
|
121
267
|
|
|
122
268
|
const PROVIDER_TITLES = {
|
|
123
|
-
codex: 'ChatGPT Codex
|
|
124
|
-
claude: 'Claude (Anthropic
|
|
125
|
-
grok: 'Grok (xAI
|
|
269
|
+
codex: 'ChatGPT Codex',
|
|
270
|
+
claude: 'Claude (Anthropic)',
|
|
271
|
+
grok: 'Grok (xAI)',
|
|
126
272
|
antigravity: 'Antigravity (Google)',
|
|
127
273
|
gemini: 'Google Gemini',
|
|
128
274
|
commandcode: 'CommandCode Provider',
|
|
129
275
|
'opencode-go': 'OpenCode-Go',
|
|
130
276
|
'qwen-token-plan': 'Qwen (Token Plan)',
|
|
131
277
|
groq: 'Groq Cloud',
|
|
132
|
-
zai: 'Zhipu GLM
|
|
278
|
+
zai: 'Zhipu GLM',
|
|
133
279
|
ollama: 'Ollama (Local / Cloud)',
|
|
134
280
|
'kimi-coding': 'Kimi Coding',
|
|
135
281
|
minimax: 'MiniMax',
|
|
@@ -141,7 +287,7 @@ window.__ModuleLoader__.load({
|
|
|
141
287
|
provider,
|
|
142
288
|
model,
|
|
143
289
|
onChange,
|
|
144
|
-
availableModels,
|
|
290
|
+
availableModels = [],
|
|
145
291
|
t,
|
|
146
292
|
}) {
|
|
147
293
|
const [open, setOpen] = React.useState(false)
|
|
@@ -150,7 +296,7 @@ window.__ModuleLoader__.load({
|
|
|
150
296
|
const inputRef = React.useRef(null)
|
|
151
297
|
|
|
152
298
|
const selectedLabel = React.useMemo(() => {
|
|
153
|
-
if (!provider && !model) return t('
|
|
299
|
+
if (!provider && !model) return t('picker.select')
|
|
154
300
|
const found = availableModels.find((m) => m.provider === provider && m.model === model)
|
|
155
301
|
return found ? (found.label || `${provider}: ${found.model}`) : `${provider}:${model}`
|
|
156
302
|
}, [availableModels, provider, model, t])
|
|
@@ -178,19 +324,18 @@ window.__ModuleLoader__.load({
|
|
|
178
324
|
const title = PROVIDER_TITLES[prov] || prov.toUpperCase()
|
|
179
325
|
groups.push({ provider: prov, title, items })
|
|
180
326
|
}
|
|
181
|
-
groups.sort((a, b) => a.title.localeCompare(b.title))
|
|
182
327
|
return groups
|
|
183
328
|
}, [filtered])
|
|
184
329
|
|
|
185
330
|
React.useEffect(() => {
|
|
186
331
|
if (!open) return
|
|
187
|
-
const
|
|
332
|
+
const handleClickOutside = (e) => {
|
|
188
333
|
if (rootRef.current && !rootRef.current.contains(e.target)) {
|
|
189
334
|
setOpen(false)
|
|
190
335
|
}
|
|
191
336
|
}
|
|
192
|
-
document.addEventListener('mousedown',
|
|
193
|
-
return () => document.removeEventListener('mousedown',
|
|
337
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
338
|
+
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
194
339
|
}, [open])
|
|
195
340
|
|
|
196
341
|
React.useEffect(() => {
|
|
@@ -206,21 +351,23 @@ window.__ModuleLoader__.load({
|
|
|
206
351
|
}
|
|
207
352
|
|
|
208
353
|
const handleCustom = () => {
|
|
209
|
-
const
|
|
210
|
-
if (!
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
354
|
+
const trimmed = query.trim()
|
|
355
|
+
if (!trimmed) return
|
|
356
|
+
let prov = provider || 'custom'
|
|
357
|
+
let mod = trimmed
|
|
358
|
+
if (trimmed.includes(':')) {
|
|
359
|
+
const [p, ...rest] = trimmed.split(':')
|
|
360
|
+
prov = p.trim()
|
|
361
|
+
mod = rest.join(':').trim()
|
|
216
362
|
}
|
|
363
|
+
onChange(prov, mod)
|
|
217
364
|
setOpen(false)
|
|
218
365
|
setQuery('')
|
|
219
366
|
}
|
|
220
367
|
|
|
221
368
|
return React.createElement(
|
|
222
369
|
'div',
|
|
223
|
-
{
|
|
370
|
+
{ className: 'moa-picker-container', ref: rootRef },
|
|
224
371
|
React.createElement(
|
|
225
372
|
'button',
|
|
226
373
|
{
|
|
@@ -234,7 +381,7 @@ window.__ModuleLoader__.load({
|
|
|
234
381
|
provider && React.createElement('span', { className: 'moa-prov-chip' }, provider),
|
|
235
382
|
React.createElement('span', { className: 'moa-model-name' }, selectedLabel)
|
|
236
383
|
),
|
|
237
|
-
React.createElement(Chevron, {
|
|
384
|
+
React.createElement(Chevron, { style: { transform: open ? 'rotate(180deg)' : 'none' } })
|
|
238
385
|
),
|
|
239
386
|
open &&
|
|
240
387
|
React.createElement(
|
|
@@ -247,7 +394,7 @@ window.__ModuleLoader__.load({
|
|
|
247
394
|
ref: inputRef,
|
|
248
395
|
type: 'text',
|
|
249
396
|
className: 'moa-search-input',
|
|
250
|
-
placeholder: t('
|
|
397
|
+
placeholder: t('picker.search_placeholder'),
|
|
251
398
|
value: query,
|
|
252
399
|
onChange: (e) => setQuery(e.target.value),
|
|
253
400
|
onKeyDown: (e) => {
|
|
@@ -261,13 +408,13 @@ window.__ModuleLoader__.load({
|
|
|
261
408
|
React.createElement(
|
|
262
409
|
'span',
|
|
263
410
|
null,
|
|
264
|
-
`${t('
|
|
411
|
+
`${t('picker.found')}: ${filtered.length} ${t('picker.of')} ${availableModels.length}`
|
|
265
412
|
),
|
|
266
413
|
query &&
|
|
267
414
|
React.createElement(
|
|
268
415
|
'span',
|
|
269
|
-
{ style: { cursor: 'pointer' }, onClick: () => setQuery('') },
|
|
270
|
-
t('clear')
|
|
416
|
+
{ style: { cursor: 'pointer', textDecoration: 'underline' }, onClick: () => setQuery('') },
|
|
417
|
+
t('picker.clear')
|
|
271
418
|
)
|
|
272
419
|
)
|
|
273
420
|
),
|
|
@@ -304,7 +451,7 @@ window.__ModuleLoader__.load({
|
|
|
304
451
|
React.createElement(
|
|
305
452
|
'div',
|
|
306
453
|
{ style: { padding: '12px 10px', fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } },
|
|
307
|
-
t('
|
|
454
|
+
t('picker.none')
|
|
308
455
|
)
|
|
309
456
|
),
|
|
310
457
|
query.trim() &&
|
|
@@ -315,7 +462,7 @@ window.__ModuleLoader__.load({
|
|
|
315
462
|
React.createElement(
|
|
316
463
|
'span',
|
|
317
464
|
null,
|
|
318
|
-
`${t('
|
|
465
|
+
`${t('picker.custom')}"${query.trim()}"`
|
|
319
466
|
)
|
|
320
467
|
)
|
|
321
468
|
)
|
|
@@ -331,12 +478,12 @@ window.__ModuleLoader__.load({
|
|
|
331
478
|
availableModels,
|
|
332
479
|
handleSave,
|
|
333
480
|
saveStatus,
|
|
481
|
+
reload,
|
|
334
482
|
}) {
|
|
335
483
|
const [selectedPresetName, setSelectedPresetName] = React.useState(defaultPreset || (presets[0] && presets[0].name) || 'default')
|
|
336
484
|
const [isCreating, setIsCreating] = React.useState(false)
|
|
337
485
|
const [newPresetName, setNewPresetName] = React.useState('')
|
|
338
486
|
|
|
339
|
-
// Sync active preset if selected one is removed
|
|
340
487
|
React.useEffect(() => {
|
|
341
488
|
if (!presets.some((p) => p.name === selectedPresetName)) {
|
|
342
489
|
setSelectedPresetName(defaultPreset || (presets[0] && presets[0].name) || 'default')
|
|
@@ -346,9 +493,11 @@ window.__ModuleLoader__.load({
|
|
|
346
493
|
const currentPreset = presets.find((p) => p.name === selectedPresetName) || presets[0] || {
|
|
347
494
|
name: 'default',
|
|
348
495
|
reference_models: [],
|
|
349
|
-
aggregator: { provider: '', model: '' },
|
|
496
|
+
aggregator: { provider: 'codex', model: 'gpt-5.6-sol' },
|
|
350
497
|
aggregator_temperature: 0.4,
|
|
351
498
|
reference_temperature: 0.6,
|
|
499
|
+
judge_criteria: '',
|
|
500
|
+
ask_clarifying_questions: true,
|
|
352
501
|
}
|
|
353
502
|
|
|
354
503
|
const updateCurrentPreset = (modifier) => {
|
|
@@ -363,10 +512,7 @@ window.__ModuleLoader__.load({
|
|
|
363
512
|
}
|
|
364
513
|
|
|
365
514
|
const setAggregatorModel = (prov, mod) => {
|
|
366
|
-
updateCurrentPreset((p) => ({
|
|
367
|
-
...p,
|
|
368
|
-
aggregator: { provider: prov, model: mod },
|
|
369
|
-
}))
|
|
515
|
+
updateCurrentPreset((p) => ({ ...p, aggregator: { provider: prov, model: mod } }))
|
|
370
516
|
}
|
|
371
517
|
|
|
372
518
|
const setAggregatorTemp = (temp) => {
|
|
@@ -441,115 +587,141 @@ window.__ModuleLoader__.load({
|
|
|
441
587
|
const created = {
|
|
442
588
|
name,
|
|
443
589
|
enabled: true,
|
|
590
|
+
ask_clarifying_questions: true,
|
|
444
591
|
reference_models: currentPreset?.reference_models?.length ? structuredClone(currentPreset.reference_models) : [
|
|
445
592
|
{ provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
446
593
|
{ provider: 'codex', model: 'gpt-5.6-sol' },
|
|
447
594
|
],
|
|
448
595
|
aggregator: currentPreset?.aggregator?.provider ? structuredClone(currentPreset.aggregator) : {
|
|
449
|
-
provider: '
|
|
450
|
-
model: '
|
|
596
|
+
provider: 'codex',
|
|
597
|
+
model: 'gpt-5.6-sol',
|
|
451
598
|
},
|
|
452
|
-
reference_temperature:
|
|
453
|
-
aggregator_temperature:
|
|
454
|
-
max_tokens:
|
|
599
|
+
reference_temperature: 0.6,
|
|
600
|
+
aggregator_temperature: 0.4,
|
|
601
|
+
max_tokens: 4096,
|
|
602
|
+
judge_criteria: '',
|
|
455
603
|
}
|
|
456
|
-
setPresets((prev) => [...prev, created])
|
|
604
|
+
setPresets((prev) => [...(prev || []), created])
|
|
457
605
|
setSelectedPresetName(name)
|
|
458
606
|
setIsCreating(false)
|
|
459
607
|
setNewPresetName('')
|
|
460
608
|
}
|
|
461
609
|
|
|
462
|
-
const handleDeletePreset = () => {
|
|
610
|
+
const handleDeletePreset = (nameToDelete) => {
|
|
463
611
|
if (presets.length <= 1) return
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
612
|
+
if (typeof window !== 'undefined' && window.confirm) {
|
|
613
|
+
if (!window.confirm(t('presets.del_confirm', { name: nameToDelete }))) return
|
|
614
|
+
}
|
|
615
|
+
setPresets((prev) => prev.filter((p) => p.name !== nameToDelete))
|
|
616
|
+
if (defaultPreset === nameToDelete) {
|
|
617
|
+
const nextDefault = presets.find((p) => p.name !== nameToDelete)?.name || 'default'
|
|
618
|
+
setDefaultPreset(nextDefault)
|
|
468
619
|
}
|
|
469
|
-
setPresets(remaining)
|
|
470
|
-
setSelectedPresetName(remaining[0].name)
|
|
471
620
|
}
|
|
472
621
|
|
|
622
|
+
const aggTemp = currentPreset.aggregator_temperature !== undefined ? currentPreset.aggregator_temperature : 0.4
|
|
623
|
+
const refTemp = currentPreset.reference_temperature !== undefined ? currentPreset.reference_temperature : 0.6
|
|
473
624
|
const currentRefs = currentPreset.reference_models || []
|
|
474
|
-
const aggTemp = currentPreset.aggregator_temperature ?? 0.4
|
|
475
|
-
const refTemp = currentPreset.reference_temperature ?? 0.6
|
|
476
625
|
const judgeCriteria = currentPreset.judge_criteria || ''
|
|
626
|
+
const isDefault = defaultPreset === currentPreset.name
|
|
477
627
|
|
|
478
628
|
return React.createElement(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
629
|
+
'div',
|
|
630
|
+
{ className: 'moa-page' },
|
|
631
|
+
|
|
632
|
+
/* ── HEADER WITH LIVE STATUS CHIPS ── */
|
|
482
633
|
React.createElement(
|
|
483
634
|
'div',
|
|
484
|
-
{ className: 'moa-
|
|
635
|
+
{ className: 'moa-header' },
|
|
485
636
|
React.createElement(
|
|
486
637
|
'div',
|
|
487
|
-
{ className: 'moa-
|
|
488
|
-
React.createElement('
|
|
638
|
+
{ className: 'moa-page-title' },
|
|
639
|
+
React.createElement('span', null, '🧠 ' + t('header.title')),
|
|
489
640
|
React.createElement(
|
|
490
641
|
'div',
|
|
491
|
-
{ className: 'moa-
|
|
492
|
-
React.createElement(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
),
|
|
514
|
-
React.createElement('option', { disabled: true }, '──────────────────'),
|
|
515
|
-
React.createElement(
|
|
516
|
-
'option',
|
|
517
|
-
{ value: '__new__' },
|
|
518
|
-
'+ ' + (t('createNewPreset') || 'Создать новый пресет...')
|
|
519
|
-
)
|
|
520
|
-
),
|
|
521
|
-
currentPreset.name !== defaultPreset &&
|
|
642
|
+
{ className: 'moa-badge-row' },
|
|
643
|
+
React.createElement('span', { className: 'moa-badge moa-badge-ok' }, '🟢 ' + t('badge.online')),
|
|
644
|
+
React.createElement('span', { className: 'moa-badge moa-badge-brand' }, t('badge.preset', { name: currentPreset.name })),
|
|
645
|
+
React.createElement('span', { className: 'moa-badge moa-badge-brand' }, t('badge.models', { count: availableModels.length })),
|
|
646
|
+
React.createElement('span', { className: 'moa-badge moa-badge-brand' }, t('badge.candidates', { count: currentRefs.length }))
|
|
647
|
+
)
|
|
648
|
+
),
|
|
649
|
+
React.createElement('div', { className: 'moa-page-sub' }, t('header.sub'))
|
|
650
|
+
),
|
|
651
|
+
|
|
652
|
+
/* ── SECTION 1: PRESET MANAGEMENT ── */
|
|
653
|
+
React.createElement(
|
|
654
|
+
'div',
|
|
655
|
+
{ className: 'moa-section-card' },
|
|
656
|
+
React.createElement(
|
|
657
|
+
'div',
|
|
658
|
+
{ className: 'moa-section-title' },
|
|
659
|
+
React.createElement('span', null, t('presets.title')),
|
|
660
|
+
React.createElement(
|
|
661
|
+
'div',
|
|
662
|
+
{ style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
663
|
+
!isDefault &&
|
|
522
664
|
React.createElement(
|
|
523
665
|
'button',
|
|
524
666
|
{
|
|
525
667
|
type: 'button',
|
|
526
|
-
className: 'moa-btn-sub',
|
|
668
|
+
className: 'moa-btn moa-btn-sub',
|
|
527
669
|
onClick: () => setDefaultPreset(currentPreset.name),
|
|
528
|
-
title: t('makeDefaultHint') || 'Использовать этот пресет при вызове /moa без аргументов',
|
|
529
670
|
},
|
|
530
|
-
'
|
|
671
|
+
'★ ' + t('presets.set_default')
|
|
531
672
|
),
|
|
532
|
-
|
|
673
|
+
isDefault &&
|
|
674
|
+
React.createElement('span', { className: 'moa-badge moa-badge-ok' }, '✓ ' + t('presets.active_badge')),
|
|
675
|
+
!isCreating &&
|
|
533
676
|
React.createElement(
|
|
534
677
|
'button',
|
|
535
678
|
{
|
|
536
679
|
type: 'button',
|
|
537
|
-
className: 'moa-btn-
|
|
538
|
-
onClick:
|
|
539
|
-
title: t('deletePreset') || 'Удалить пресет',
|
|
680
|
+
className: 'moa-btn moa-btn-sub',
|
|
681
|
+
onClick: () => setIsCreating(true),
|
|
540
682
|
},
|
|
541
|
-
|
|
683
|
+
t('presets.new_btn')
|
|
542
684
|
)
|
|
543
685
|
)
|
|
544
686
|
),
|
|
687
|
+
React.createElement('div', { className: 'moa-section-desc' }, t('presets.desc')),
|
|
688
|
+
React.createElement(
|
|
689
|
+
'div',
|
|
690
|
+
{ className: 'moa-seg-group' },
|
|
691
|
+
presets.map((p) => {
|
|
692
|
+
const isCur = p.name === selectedPresetName
|
|
693
|
+
return React.createElement(
|
|
694
|
+
'button',
|
|
695
|
+
{
|
|
696
|
+
key: p.name,
|
|
697
|
+
type: 'button',
|
|
698
|
+
className: isCur ? 'moa-seg-btn moa-seg-btn-active' : 'moa-seg-btn',
|
|
699
|
+
onClick: () => setSelectedPresetName(p.name),
|
|
700
|
+
},
|
|
701
|
+
p.name === defaultPreset ? `★ ${p.name}` : p.name
|
|
702
|
+
)
|
|
703
|
+
}),
|
|
704
|
+
presets.length > 1 &&
|
|
705
|
+
React.createElement(
|
|
706
|
+
'button',
|
|
707
|
+
{
|
|
708
|
+
type: 'button',
|
|
709
|
+
className: 'moa-btn moa-btn-danger moa-btn-sub',
|
|
710
|
+
onClick: () => handleDeletePreset(currentPreset.name),
|
|
711
|
+
title: t('presets.del_btn'),
|
|
712
|
+
},
|
|
713
|
+
'🗑 ' + t('presets.del_btn')
|
|
714
|
+
)
|
|
715
|
+
),
|
|
545
716
|
isCreating &&
|
|
546
717
|
React.createElement(
|
|
547
718
|
'div',
|
|
548
|
-
{
|
|
719
|
+
{ style: { display: 'flex', gap: 8, alignItems: 'center', marginTop: 4 } },
|
|
549
720
|
React.createElement('input', {
|
|
550
721
|
type: 'text',
|
|
551
|
-
className: 'moa-
|
|
552
|
-
|
|
722
|
+
className: 'moa-input',
|
|
723
|
+
style: { maxWidth: 320 },
|
|
724
|
+
placeholder: t('presets.name_placeholder'),
|
|
553
725
|
value: newPresetName,
|
|
554
726
|
onChange: (e) => setNewPresetName(e.target.value),
|
|
555
727
|
onKeyDown: (e) => {
|
|
@@ -561,39 +733,34 @@ window.__ModuleLoader__.load({
|
|
|
561
733
|
'button',
|
|
562
734
|
{
|
|
563
735
|
type: 'button',
|
|
564
|
-
className: 'moa-btn-
|
|
736
|
+
className: 'moa-btn moa-btn-primary',
|
|
565
737
|
onClick: handleCreatePreset,
|
|
566
|
-
style: { background: 'var(--dsw-alias-label-primary)', color: 'var(--dsw-alias-bg-layer-3)' },
|
|
567
738
|
},
|
|
568
|
-
t('create')
|
|
739
|
+
t('presets.create')
|
|
569
740
|
),
|
|
570
741
|
React.createElement(
|
|
571
742
|
'button',
|
|
572
743
|
{
|
|
573
744
|
type: 'button',
|
|
574
|
-
className: 'moa-btn
|
|
745
|
+
className: 'moa-btn',
|
|
575
746
|
onClick: () => setIsCreating(false),
|
|
576
747
|
},
|
|
577
|
-
t('cancel')
|
|
748
|
+
t('presets.cancel')
|
|
578
749
|
)
|
|
579
750
|
)
|
|
580
751
|
),
|
|
581
|
-
|
|
752
|
+
|
|
753
|
+
/* ── SECTION 2: AGGREGATOR / JUDGE ── */
|
|
582
754
|
React.createElement(
|
|
583
755
|
'div',
|
|
584
|
-
{ className: 'moa-
|
|
756
|
+
{ className: 'moa-section-card' },
|
|
585
757
|
React.createElement(
|
|
586
758
|
'div',
|
|
587
|
-
{ className: 'moa-
|
|
588
|
-
React.createElement('span', null, t('
|
|
589
|
-
React.createElement(
|
|
590
|
-
'div',
|
|
591
|
-
{ style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
592
|
-
React.createElement('span', { className: 'moa-hint' }, `${t('temperature') || 'Температура'}:`),
|
|
593
|
-
React.createElement('span', { style: { fontWeight: 600, fontSize: 13 } }, aggTemp)
|
|
594
|
-
)
|
|
759
|
+
{ className: 'moa-section-title' },
|
|
760
|
+
React.createElement('span', null, t('aggregator.title')),
|
|
761
|
+
React.createElement('span', { className: 'moa-badge moa-badge-brand' }, `Temp: ${aggTemp}`)
|
|
595
762
|
),
|
|
596
|
-
React.createElement('div', { className: 'moa-
|
|
763
|
+
React.createElement('div', { className: 'moa-section-desc' }, t('aggregator.desc')),
|
|
597
764
|
React.createElement(SearchableModelPicker, {
|
|
598
765
|
provider: currentPreset.aggregator?.provider || '',
|
|
599
766
|
model: currentPreset.aggregator?.model || '',
|
|
@@ -603,75 +770,81 @@ window.__ModuleLoader__.load({
|
|
|
603
770
|
}),
|
|
604
771
|
React.createElement(
|
|
605
772
|
'div',
|
|
606
|
-
{
|
|
773
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4 } },
|
|
774
|
+
React.createElement(
|
|
775
|
+
'div',
|
|
776
|
+
{ style: { display: 'flex', justifyContent: 'space-between', fontSize: 13, fontWeight: 500 } },
|
|
777
|
+
React.createElement('span', null, t('aggregator.temp_label')),
|
|
778
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } }, t('aggregator.temp_hint'))
|
|
779
|
+
),
|
|
607
780
|
React.createElement(
|
|
608
781
|
'div',
|
|
609
|
-
{ className: 'moa-
|
|
782
|
+
{ className: 'moa-slider-row' },
|
|
610
783
|
React.createElement('input', {
|
|
611
784
|
type: 'range',
|
|
612
|
-
className: 'moa-
|
|
785
|
+
className: 'moa-slider',
|
|
613
786
|
min: '0',
|
|
614
787
|
max: '2',
|
|
615
788
|
step: '0.05',
|
|
616
789
|
value: aggTemp,
|
|
617
790
|
onChange: (e) => setAggregatorTemp(e.target.value),
|
|
618
791
|
}),
|
|
619
|
-
React.createElement('
|
|
620
|
-
|
|
621
|
-
className: 'moa-temp-input',
|
|
622
|
-
min: '0',
|
|
623
|
-
max: '2',
|
|
624
|
-
step: '0.05',
|
|
625
|
-
value: aggTemp,
|
|
626
|
-
onChange: (e) => setAggregatorTemp(e.target.value),
|
|
627
|
-
})
|
|
628
|
-
),
|
|
629
|
-
React.createElement('div', { className: 'moa-hint' }, t('aggTempHint') || 'Рекомендуется 0.2 – 0.4 для точной проверки и синтеза без галлюцинаций.')
|
|
792
|
+
React.createElement('div', { className: 'moa-slider-badge' }, aggTemp)
|
|
793
|
+
)
|
|
630
794
|
),
|
|
631
795
|
React.createElement(
|
|
632
796
|
'div',
|
|
633
|
-
{
|
|
634
|
-
React.createElement('label', {
|
|
797
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4, borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 10 } },
|
|
798
|
+
React.createElement('label', { style: { fontSize: 13, fontWeight: 500 } }, t('aggregator.criteria_label')),
|
|
635
799
|
React.createElement('textarea', {
|
|
636
800
|
className: 'moa-textarea',
|
|
637
801
|
rows: 2,
|
|
638
|
-
|
|
639
|
-
width: '100%',
|
|
640
|
-
boxSizing: 'border-box',
|
|
641
|
-
padding: '8px 10px',
|
|
642
|
-
borderRadius: 8,
|
|
643
|
-
border: '1px solid var(--dsw-alias-border-l2)',
|
|
644
|
-
background: 'var(--dsw-alias-bg-layer-3)',
|
|
645
|
-
color: 'var(--dsw-alias-label-primary)',
|
|
646
|
-
fontSize: 13,
|
|
647
|
-
resize: 'vertical',
|
|
648
|
-
outline: 'none',
|
|
649
|
-
},
|
|
650
|
-
placeholder: t('judgeCriteriaPlaceholder') || 'например: Приоритет — производительность, чистый код, минимум зависимостей...',
|
|
802
|
+
placeholder: t('aggregator.criteria_placeholder'),
|
|
651
803
|
value: judgeCriteria,
|
|
652
804
|
onChange: (e) => setJudgeCriteria(e.target.value),
|
|
653
|
-
})
|
|
805
|
+
}),
|
|
806
|
+
React.createElement(
|
|
807
|
+
'div',
|
|
808
|
+
{ style: { marginTop: 6, display: 'flex', flexDirection: 'column', gap: 3 } },
|
|
809
|
+
React.createElement(
|
|
810
|
+
'label',
|
|
811
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500 } },
|
|
812
|
+
React.createElement('input', {
|
|
813
|
+
type: 'checkbox',
|
|
814
|
+
checked: currentPreset.ask_clarifying_questions !== false,
|
|
815
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, ask_clarifying_questions: e.target.checked })),
|
|
816
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
817
|
+
}),
|
|
818
|
+
t('aggregator.questions_label')
|
|
819
|
+
),
|
|
820
|
+
React.createElement(
|
|
821
|
+
'div',
|
|
822
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
823
|
+
t('aggregator.questions_hint')
|
|
824
|
+
)
|
|
825
|
+
)
|
|
654
826
|
)
|
|
655
827
|
),
|
|
656
|
-
|
|
828
|
+
|
|
829
|
+
/* ── SECTION 3: CANDIDATE PROPOSERS ── */
|
|
657
830
|
React.createElement(
|
|
658
831
|
'div',
|
|
659
|
-
{ className: 'moa-
|
|
832
|
+
{ className: 'moa-section-card' },
|
|
660
833
|
React.createElement(
|
|
661
834
|
'div',
|
|
662
|
-
{ className: 'moa-
|
|
663
|
-
React.createElement('span', null, t('
|
|
835
|
+
{ className: 'moa-section-title' },
|
|
836
|
+
React.createElement('span', null, t('proposers.title')),
|
|
664
837
|
React.createElement(
|
|
665
838
|
'div',
|
|
666
|
-
{ className: 'moa-
|
|
667
|
-
React.createElement('span', {
|
|
839
|
+
{ className: 'moa-seg-group' },
|
|
840
|
+
React.createElement('span', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } }, t('proposers.parallel_count') + ':'),
|
|
668
841
|
[2, 3, 4, 5].map((cnt) =>
|
|
669
842
|
React.createElement(
|
|
670
843
|
'button',
|
|
671
844
|
{
|
|
672
845
|
key: cnt,
|
|
673
846
|
type: 'button',
|
|
674
|
-
className: currentRefs.length === cnt ? 'moa-
|
|
847
|
+
className: currentRefs.length === cnt ? 'moa-seg-btn moa-seg-btn-active moa-btn-sub' : 'moa-seg-btn moa-btn-sub',
|
|
675
848
|
onClick: () => setReferenceCount(cnt),
|
|
676
849
|
},
|
|
677
850
|
cnt
|
|
@@ -679,18 +852,18 @@ window.__ModuleLoader__.load({
|
|
|
679
852
|
)
|
|
680
853
|
)
|
|
681
854
|
),
|
|
682
|
-
React.createElement('div', { className: 'moa-
|
|
855
|
+
React.createElement('div', { className: 'moa-section-desc' }, t('proposers.desc')),
|
|
683
856
|
React.createElement(
|
|
684
857
|
'div',
|
|
685
|
-
{ className: 'moa-
|
|
858
|
+
{ className: 'moa-cand-list' },
|
|
686
859
|
currentRefs.map((ref, idx) =>
|
|
687
860
|
React.createElement(
|
|
688
861
|
'div',
|
|
689
|
-
{ key: idx, className: 'moa-
|
|
690
|
-
React.createElement('span', { className: 'moa-
|
|
862
|
+
{ key: idx, className: 'moa-cand-row' },
|
|
863
|
+
React.createElement('span', { className: 'moa-cand-badge' }, `${t('proposers.candidate')} #${idx + 1}`),
|
|
691
864
|
React.createElement(
|
|
692
865
|
'div',
|
|
693
|
-
{ className: 'moa-
|
|
866
|
+
{ className: 'moa-cand-picker' },
|
|
694
867
|
React.createElement(SearchableModelPicker, {
|
|
695
868
|
provider: ref.provider || '',
|
|
696
869
|
model: ref.model || '',
|
|
@@ -704,8 +877,8 @@ window.__ModuleLoader__.load({
|
|
|
704
877
|
'button',
|
|
705
878
|
{
|
|
706
879
|
type: 'button',
|
|
707
|
-
className: 'moa-btn-
|
|
708
|
-
title:
|
|
880
|
+
className: 'moa-btn moa-btn-danger moa-btn-sub',
|
|
881
|
+
title: 'Remove',
|
|
709
882
|
onClick: () => removeReference(idx),
|
|
710
883
|
},
|
|
711
884
|
'✕'
|
|
@@ -717,180 +890,107 @@ window.__ModuleLoader__.load({
|
|
|
717
890
|
'button',
|
|
718
891
|
{
|
|
719
892
|
type: 'button',
|
|
720
|
-
className: 'moa-btn
|
|
893
|
+
className: 'moa-btn',
|
|
894
|
+
style: { alignSelf: 'flex-start' },
|
|
721
895
|
onClick: addReference,
|
|
722
896
|
},
|
|
723
|
-
|
|
897
|
+
t('proposers.add_btn')
|
|
724
898
|
),
|
|
725
899
|
React.createElement(
|
|
726
900
|
'div',
|
|
727
|
-
{
|
|
901
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4, borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 10 } },
|
|
728
902
|
React.createElement(
|
|
729
903
|
'div',
|
|
730
|
-
{ style: { display: 'flex',
|
|
731
|
-
React.createElement('span',
|
|
732
|
-
React.createElement('span', { style: {
|
|
904
|
+
{ style: { display: 'flex', justifyContent: 'space-between', fontSize: 13, fontWeight: 500 } },
|
|
905
|
+
React.createElement('span', null, t('proposers.temp_label')),
|
|
906
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } }, t('proposers.temp_hint'))
|
|
733
907
|
),
|
|
734
908
|
React.createElement(
|
|
735
909
|
'div',
|
|
736
|
-
{ className: 'moa-
|
|
910
|
+
{ className: 'moa-slider-row' },
|
|
737
911
|
React.createElement('input', {
|
|
738
912
|
type: 'range',
|
|
739
|
-
className: 'moa-
|
|
913
|
+
className: 'moa-slider',
|
|
740
914
|
min: '0',
|
|
741
915
|
max: '2',
|
|
742
916
|
step: '0.05',
|
|
743
917
|
value: refTemp,
|
|
744
918
|
onChange: (e) => setReferenceTemp(e.target.value),
|
|
745
919
|
}),
|
|
746
|
-
React.createElement('
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
920
|
+
React.createElement('div', { className: 'moa-slider-badge' }, refTemp)
|
|
921
|
+
)
|
|
922
|
+
)
|
|
923
|
+
),
|
|
924
|
+
|
|
925
|
+
/* ── SECTION 4: TELEMETRY & STATS SUMMARY ── */
|
|
926
|
+
React.createElement(
|
|
927
|
+
'div',
|
|
928
|
+
{ className: 'moa-section-card' },
|
|
929
|
+
React.createElement('div', { className: 'moa-section-title' }, t('stats.title')),
|
|
930
|
+
React.createElement('div', { className: 'moa-section-desc' }, t('stats.desc')),
|
|
931
|
+
React.createElement(
|
|
932
|
+
'div',
|
|
933
|
+
{ className: 'moa-grid-2' },
|
|
934
|
+
React.createElement(
|
|
935
|
+
'div',
|
|
936
|
+
{ className: 'moa-stat-box' },
|
|
937
|
+
React.createElement('div', { className: 'moa-stat-val' }, String(presets.length)),
|
|
938
|
+
React.createElement('div', { className: 'moa-stat-lbl' }, t('stats.preset_count'))
|
|
939
|
+
),
|
|
940
|
+
React.createElement(
|
|
941
|
+
'div',
|
|
942
|
+
{ className: 'moa-stat-box' },
|
|
943
|
+
React.createElement('div', { className: 'moa-stat-val' }, String(currentRefs.length)),
|
|
944
|
+
React.createElement('div', { className: 'moa-stat-lbl' }, t('stats.active_models'))
|
|
755
945
|
),
|
|
756
|
-
React.createElement(
|
|
946
|
+
React.createElement(
|
|
947
|
+
'div',
|
|
948
|
+
{ className: 'moa-stat-box' },
|
|
949
|
+
React.createElement('div', { className: 'moa-stat-val' }, String(availableModels.length)),
|
|
950
|
+
React.createElement('div', { className: 'moa-stat-lbl' }, t('badge.models', { count: availableModels.length }))
|
|
951
|
+
)
|
|
757
952
|
)
|
|
758
953
|
),
|
|
759
|
-
|
|
954
|
+
|
|
955
|
+
/* ── FOOTER ACTIONS & SAVE ── */
|
|
760
956
|
React.createElement(
|
|
761
957
|
'div',
|
|
762
|
-
{
|
|
763
|
-
saveStatus && React.createElement('span', { className: 'moa-status-text' }, saveStatus),
|
|
958
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 12, marginTop: 4 } },
|
|
764
959
|
React.createElement(
|
|
765
960
|
'button',
|
|
766
961
|
{
|
|
767
962
|
type: 'button',
|
|
768
|
-
className: 'moa-
|
|
963
|
+
className: 'moa-btn moa-btn-primary',
|
|
964
|
+
style: { padding: '10px 24px', fontSize: 14, fontWeight: 600 },
|
|
769
965
|
onClick: handleSave,
|
|
770
966
|
},
|
|
771
|
-
t('save')
|
|
772
|
-
)
|
|
967
|
+
saveStatus === t('actions.saving') ? t('actions.saving') : t('actions.save')
|
|
968
|
+
),
|
|
969
|
+
React.createElement(
|
|
970
|
+
'button',
|
|
971
|
+
{
|
|
972
|
+
type: 'button',
|
|
973
|
+
className: 'moa-btn',
|
|
974
|
+
onClick: reload,
|
|
975
|
+
},
|
|
976
|
+
t('actions.retry')
|
|
977
|
+
),
|
|
978
|
+
saveStatus &&
|
|
979
|
+
React.createElement(
|
|
980
|
+
'span',
|
|
981
|
+
{
|
|
982
|
+
className: saveStatus.startsWith('Error') ? 'moa-alert-err' : 'moa-alert-ok',
|
|
983
|
+
},
|
|
984
|
+
saveStatus
|
|
985
|
+
)
|
|
773
986
|
)
|
|
774
987
|
)
|
|
775
988
|
}
|
|
776
989
|
|
|
777
|
-
let rootCtx = null
|
|
778
|
-
|
|
779
|
-
const en = {
|
|
780
|
-
title: 'Mixture of Agents (MoA)',
|
|
781
|
-
description: 'Run tasks with multiple parallel proposer models and a synthesizing judge model',
|
|
782
|
-
slashMoa: 'Run task through Mixture of Agents (/moa <prompt>)',
|
|
783
|
-
defaultPreset: 'Default Preset (used on /moa without arguments)',
|
|
784
|
-
isDefault: 'default',
|
|
785
|
-
presetSelectLabel: 'Active Preset to Configure:',
|
|
786
|
-
createNewPreset: 'Create new preset...',
|
|
787
|
-
makeDefault: 'Make Default',
|
|
788
|
-
makeDefaultHint: 'Use this preset for /moa when no preset argument is given',
|
|
789
|
-
deletePreset: 'Delete preset',
|
|
790
|
-
delete: 'Delete',
|
|
791
|
-
confirmDeletePreset: 'Are you sure you want to delete preset',
|
|
792
|
-
newPresetPlaceholder: 'New preset name (e.g. fast-audit)',
|
|
793
|
-
create: 'Create',
|
|
794
|
-
cancel: 'Cancel',
|
|
795
|
-
aggregatorTitle: 'Judge / Aggregator Model (who reviews and synthesizes)',
|
|
796
|
-
aggregatorDesc: 'Evaluates candidate proposals, corrects hallucinations, and outputs the final unified solution.',
|
|
797
|
-
temperature: 'Temperature',
|
|
798
|
-
aggTempHint: 'Recommended 0.2 - 0.4 for rigorous verification and factual synthesis.',
|
|
799
|
-
proposersTitle: 'Candidate Models (Advisors)',
|
|
800
|
-
proposersDesc: 'Generates independent candidate answers in parallel to provide diverse perspectives.',
|
|
801
|
-
refTempLabel: 'Candidates Temperature (solution diversity):',
|
|
802
|
-
refTempHint: 'Recommended 0.6 - 0.8 for diverse creative hypotheses.',
|
|
803
|
-
parallelCount: 'Parallel workers',
|
|
804
|
-
candidate: 'Candidate',
|
|
805
|
-
addCandidate: 'Add parallel model slot',
|
|
806
|
-
remove: 'Remove',
|
|
807
|
-
save: 'Save Changes',
|
|
808
|
-
saved: 'Saved successfully',
|
|
809
|
-
loading: 'Saving…',
|
|
810
|
-
retry: 'Retry connection',
|
|
811
|
-
unavailable: 'MoA service temporarily unavailable (reconnecting...)',
|
|
812
|
-
searchPlaceholder: 'Search by model name or provider...',
|
|
813
|
-
modelsFound: 'Found',
|
|
814
|
-
of: 'of',
|
|
815
|
-
clear: 'Clear',
|
|
816
|
-
noModelsMatch: 'No models found matching query.',
|
|
817
|
-
useCustom: 'Use custom',
|
|
818
|
-
selectModel: 'Select model...',
|
|
819
|
-
judgeCriteriaLabel: 'Custom Judge Evaluation Criteria (optional):',
|
|
820
|
-
judgeCriteriaPlaceholder: 'e.g. Priority: performance, zero external dependencies, robust edge-case handling...',
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const ru = {
|
|
824
|
-
title: 'Mixture of Agents (MoA)',
|
|
825
|
-
description: 'Запуск задач через ансамбль параллельных моделей-советников и модель-судью',
|
|
826
|
-
slashMoa: 'Запустить задачу через архитектуру Mixture of Agents (/moa <prompt>)',
|
|
827
|
-
defaultPreset: 'Основной пресет (при вызове /moa без параметров)',
|
|
828
|
-
isDefault: 'основной',
|
|
829
|
-
presetSelectLabel: 'Текущий пресет для настройки:',
|
|
830
|
-
createNewPreset: 'Создать новый пресет...',
|
|
831
|
-
makeDefault: 'Сделать основным',
|
|
832
|
-
makeDefaultHint: 'Использовать этот пресет для /moa при вызове без параметров',
|
|
833
|
-
deletePreset: 'Удалить пресет',
|
|
834
|
-
delete: 'Удалить',
|
|
835
|
-
confirmDeletePreset: 'Вы уверены, что хотите удалить пресет',
|
|
836
|
-
newPresetPlaceholder: 'Название нового пресета (например: fast-audit)',
|
|
837
|
-
create: 'Создать',
|
|
838
|
-
cancel: 'Отмена',
|
|
839
|
-
aggregatorTitle: 'Ведущая модель / Судья (кто проверяет и объединяет)',
|
|
840
|
-
aggregatorDesc: 'Сопоставляет варианты кандидатов, устраняет ошибки и синтезирует итоговое решение.',
|
|
841
|
-
temperature: 'Температура',
|
|
842
|
-
aggTempHint: 'Рекомендуется 0.2 – 0.4 для строгой проверки и синтеза без выдумок.',
|
|
843
|
-
proposersTitle: 'Модели-кандидаты (советники)',
|
|
844
|
-
proposersDesc: 'Параллельно формируют независимые варианты решения одной задачи.',
|
|
845
|
-
refTempLabel: 'Температура кандидатов (разнообразие решений):',
|
|
846
|
-
refTempHint: 'Рекомендуется 0.6 – 0.8 для получения независимых и креативных вариантов.',
|
|
847
|
-
parallelCount: 'Параллельных моделей',
|
|
848
|
-
candidate: 'Кандидат',
|
|
849
|
-
addCandidate: 'Добавить модель в параллель',
|
|
850
|
-
remove: 'Удалить',
|
|
851
|
-
save: 'Сохранить',
|
|
852
|
-
saved: 'Успешно сохранено',
|
|
853
|
-
loading: 'Сохранение…',
|
|
854
|
-
retry: 'Повторить подключение',
|
|
855
|
-
unavailable: 'Служба MoA временно недоступна (переподключение...)',
|
|
856
|
-
searchPlaceholder: 'Поиск модели по названию или провайдеру...',
|
|
857
|
-
modelsFound: 'Найдено',
|
|
858
|
-
of: 'из',
|
|
859
|
-
clear: 'Сбросить',
|
|
860
|
-
noModelsMatch: 'Модели не найдены.',
|
|
861
|
-
useCustom: 'Использовать кастомное',
|
|
862
|
-
selectModel: 'Выберите модель...',
|
|
863
|
-
judgeCriteriaLabel: 'Критерии оценки судьи (опционально):',
|
|
864
|
-
judgeCriteriaPlaceholder: 'например: Приоритет — производительность, минимум зависимостей, обработка граничных случаев...',
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function makeT(ctx, props) {
|
|
868
|
-
return (key) => {
|
|
869
|
-
if (props && typeof props.t === 'function') {
|
|
870
|
-
try {
|
|
871
|
-
const res = props.t(key)
|
|
872
|
-
if (res && res !== key) return res
|
|
873
|
-
} catch (_) {}
|
|
874
|
-
}
|
|
875
|
-
const effectiveCtx = (props && props.ctx) || ctx || rootCtx
|
|
876
|
-
if (effectiveCtx && effectiveCtx.locale && typeof effectiveCtx.locale.bind === 'function') {
|
|
877
|
-
try {
|
|
878
|
-
const bound = effectiveCtx.locale.bind(NS)
|
|
879
|
-
const res = bound(key)
|
|
880
|
-
if (res && res !== key) return res
|
|
881
|
-
} catch (_) {}
|
|
882
|
-
}
|
|
883
|
-
const curLocale = (effectiveCtx && effectiveCtx.locale && effectiveCtx.locale.getSnapshot && effectiveCtx.locale.getSnapshot().active) || 'ru'
|
|
884
|
-
const dict = curLocale === 'en' ? en : ru
|
|
885
|
-
return dict[key] || ru[key] || en[key] || key
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
990
|
function useMoASettings(props) {
|
|
890
|
-
const t = makeT(rootCtx, props)
|
|
891
991
|
const effectiveCtx = (props && props.ctx) || rootCtx
|
|
992
|
+
const t = makeT(ru, en)
|
|
892
993
|
|
|
893
|
-
// Reactive binding to settingsScope if available
|
|
894
994
|
const scope = React.useMemo(() => {
|
|
895
995
|
if (!effectiveCtx) return undefined
|
|
896
996
|
const s = effectiveCtx.get ? effectiveCtx.get('settingsScope') : effectiveCtx.settingsScope
|
|
@@ -911,7 +1011,6 @@ window.__ModuleLoader__.load({
|
|
|
911
1011
|
const [availableModels, setAvailableModels] = React.useState([])
|
|
912
1012
|
const [saveStatus, setSaveStatus] = React.useState('')
|
|
913
1013
|
|
|
914
|
-
// Sync from settingsScope snapshot when available
|
|
915
1014
|
React.useEffect(() => {
|
|
916
1015
|
if (!scope || !snapshot) return
|
|
917
1016
|
const snapStatus = snapshot.status || 'ready'
|
|
@@ -964,7 +1063,7 @@ window.__ModuleLoader__.load({
|
|
|
964
1063
|
}, [reload])
|
|
965
1064
|
|
|
966
1065
|
const handleSave = () => {
|
|
967
|
-
setSaveStatus(t('
|
|
1066
|
+
setSaveStatus(t('actions.saving'))
|
|
968
1067
|
|
|
969
1068
|
if (scope && typeof scope.set === 'function') {
|
|
970
1069
|
Promise.all([
|
|
@@ -972,8 +1071,8 @@ window.__ModuleLoader__.load({
|
|
|
972
1071
|
scope.set('default_preset', defaultPreset),
|
|
973
1072
|
])
|
|
974
1073
|
.then(() => {
|
|
975
|
-
setSaveStatus(t('saved'))
|
|
976
|
-
setTimeout(() => setSaveStatus(''),
|
|
1074
|
+
setSaveStatus(t('actions.saved'))
|
|
1075
|
+
setTimeout(() => setSaveStatus(''), 3000)
|
|
977
1076
|
})
|
|
978
1077
|
.catch((err) => {
|
|
979
1078
|
console.warn('[dsh-moa] scope.set failed, trying REST fallback:', err)
|
|
@@ -994,10 +1093,10 @@ window.__ModuleLoader__.load({
|
|
|
994
1093
|
.then((r) => r.json())
|
|
995
1094
|
.then((res) => {
|
|
996
1095
|
if (res && res.ok) {
|
|
997
|
-
setSaveStatus(t('saved'))
|
|
1096
|
+
setSaveStatus(t('actions.saved'))
|
|
998
1097
|
if (Array.isArray(res.presets)) setPresets(res.presets)
|
|
999
1098
|
if (res.defaultPreset) setDefaultPreset(res.defaultPreset)
|
|
1000
|
-
setTimeout(() => setSaveStatus(''),
|
|
1099
|
+
setTimeout(() => setSaveStatus(''), 3000)
|
|
1001
1100
|
} else {
|
|
1002
1101
|
setSaveStatus('Error saving: ' + (res?.error || 'failed'))
|
|
1003
1102
|
}
|
|
@@ -1021,86 +1120,34 @@ window.__ModuleLoader__.load({
|
|
|
1021
1120
|
}
|
|
1022
1121
|
}
|
|
1023
1122
|
|
|
1024
|
-
function MoASection(props) {
|
|
1025
|
-
ensureStyles()
|
|
1026
|
-
const state = useMoASettings(props)
|
|
1027
|
-
|
|
1028
|
-
return React.createElement(
|
|
1029
|
-
'div',
|
|
1030
|
-
{ className: 'moa-section' },
|
|
1031
|
-
React.createElement('div', { className: 'moa-section-title' }, state.t('title')),
|
|
1032
|
-
React.createElement('div', { className: 'moa-section-sub' }, state.t('description')),
|
|
1033
|
-
state.status === 'loading'
|
|
1034
|
-
? React.createElement('div', { className: 'moa-hint' }, state.t('loading'))
|
|
1035
|
-
: state.status === 'unavailable'
|
|
1036
|
-
? React.createElement(
|
|
1037
|
-
'div',
|
|
1038
|
-
{ className: 'moa-field' },
|
|
1039
|
-
React.createElement('div', { className: 'moa-hint' }, state.t('unavailable')),
|
|
1040
|
-
React.createElement(
|
|
1041
|
-
'button',
|
|
1042
|
-
{
|
|
1043
|
-
type: 'button',
|
|
1044
|
-
className: 'moa-count-btn',
|
|
1045
|
-
style: { width: 'fit-content', marginTop: 8 },
|
|
1046
|
-
onClick: state.reload,
|
|
1047
|
-
},
|
|
1048
|
-
state.t('retry')
|
|
1049
|
-
)
|
|
1050
|
-
)
|
|
1051
|
-
: React.createElement(MoAEditor, state)
|
|
1052
|
-
)
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
// ── ERROR BOUNDARY ──────────────────────────────────────────────
|
|
1057
1123
|
class MoAErrorBoundary extends React.Component {
|
|
1058
1124
|
constructor(props) {
|
|
1059
1125
|
super(props)
|
|
1060
|
-
this.state = { hasError: false, error: null
|
|
1126
|
+
this.state = { hasError: false, error: null }
|
|
1061
1127
|
}
|
|
1062
1128
|
static getDerivedStateFromError(error) {
|
|
1063
1129
|
return { hasError: true, error }
|
|
1064
1130
|
}
|
|
1065
1131
|
componentDidCatch(error, errorInfo) {
|
|
1066
1132
|
console.error('[dsh-moa] MoAErrorBoundary caught error:', error, errorInfo)
|
|
1067
|
-
this.setState({ errorInfo })
|
|
1068
1133
|
}
|
|
1069
1134
|
render() {
|
|
1070
1135
|
if (this.state.hasError) {
|
|
1071
1136
|
return React.createElement(
|
|
1072
1137
|
'div',
|
|
1073
1138
|
{
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
borderRadius: '8px',
|
|
1077
|
-
border: '1px solid #ef4444',
|
|
1078
|
-
background: 'rgba(239,68,68,0.08)',
|
|
1079
|
-
color: '#ef4444',
|
|
1080
|
-
fontSize: '13px',
|
|
1081
|
-
fontFamily: 'monospace',
|
|
1082
|
-
whiteSpace: 'pre-wrap',
|
|
1083
|
-
wordBreak: 'break-word',
|
|
1084
|
-
},
|
|
1139
|
+
className: 'moa-alert-err',
|
|
1140
|
+
style: { margin: '12px 0', padding: '14px', borderRadius: '8px' },
|
|
1085
1141
|
},
|
|
1086
|
-
React.createElement('div', { style: { fontWeight:
|
|
1087
|
-
React.createElement('div',
|
|
1088
|
-
this.state.errorInfo && React.createElement(
|
|
1089
|
-
'details',
|
|
1090
|
-
{ style: { marginTop: 8 } },
|
|
1091
|
-
React.createElement('summary', null, 'Component stack'),
|
|
1092
|
-
React.createElement('pre', { style: { fontSize: 11 } }, this.state.errorInfo.componentStack || '')
|
|
1093
|
-
),
|
|
1142
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ MoA UI Error:'),
|
|
1143
|
+
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
|
|
1094
1144
|
React.createElement(
|
|
1095
1145
|
'button',
|
|
1096
1146
|
{
|
|
1097
1147
|
type: 'button',
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
cursor: 'pointer', fontSize: 12,
|
|
1102
|
-
},
|
|
1103
|
-
onClick: () => this.setState({ hasError: false, error: null, errorInfo: null }),
|
|
1148
|
+
className: 'moa-btn',
|
|
1149
|
+
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
1150
|
+
onClick: () => this.setState({ hasError: false, error: null }),
|
|
1104
1151
|
},
|
|
1105
1152
|
'Retry'
|
|
1106
1153
|
)
|
|
@@ -1129,8 +1176,8 @@ window.__ModuleLoader__.load({
|
|
|
1129
1176
|
React.createElement(
|
|
1130
1177
|
'div',
|
|
1131
1178
|
null,
|
|
1132
|
-
React.createElement('div', { className: 'moa-title' }, state.t('title')),
|
|
1133
|
-
React.createElement('div', { className: 'moa-sub' }, state.t('description'))
|
|
1179
|
+
React.createElement('div', { className: 'moa-title' }, '🧠 ' + (state.t('title') || TITLE)),
|
|
1180
|
+
React.createElement('div', { className: 'moa-sub' }, state.t('description') || SUBTITLE)
|
|
1134
1181
|
),
|
|
1135
1182
|
React.createElement(Chevron, {
|
|
1136
1183
|
className: open ? 'moa-chev moa-chev-open' : 'moa-chev',
|
|
@@ -1141,21 +1188,21 @@ window.__ModuleLoader__.load({
|
|
|
1141
1188
|
'div',
|
|
1142
1189
|
{ className: 'moa-body' },
|
|
1143
1190
|
state.status === 'loading'
|
|
1144
|
-
? React.createElement('div', {
|
|
1191
|
+
? React.createElement('div', { style: { padding: '16px 0', color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, 'Loading MoA settings…')
|
|
1145
1192
|
: state.status === 'unavailable'
|
|
1146
1193
|
? React.createElement(
|
|
1147
1194
|
'div',
|
|
1148
|
-
{
|
|
1149
|
-
React.createElement('div', { className: 'moa-
|
|
1195
|
+
{ style: { padding: '16px 0', display: 'flex', flexDirection: 'column', gap: 10 } },
|
|
1196
|
+
React.createElement('div', { className: 'moa-alert-err' }, 'MoA service temporarily unavailable'),
|
|
1150
1197
|
React.createElement(
|
|
1151
1198
|
'button',
|
|
1152
1199
|
{
|
|
1153
1200
|
type: 'button',
|
|
1154
|
-
className: 'moa-
|
|
1155
|
-
style: { width: 'fit-content'
|
|
1201
|
+
className: 'moa-btn',
|
|
1202
|
+
style: { width: 'fit-content' },
|
|
1156
1203
|
onClick: state.reload,
|
|
1157
1204
|
},
|
|
1158
|
-
state.t('retry')
|
|
1205
|
+
state.t('actions.retry')
|
|
1159
1206
|
)
|
|
1160
1207
|
)
|
|
1161
1208
|
: React.createElement(MoAEditor, state)
|
|
@@ -1166,14 +1213,13 @@ window.__ModuleLoader__.load({
|
|
|
1166
1213
|
exports.inject = ['slots', 'locale', 'inputTriggers', 'settingsScope']
|
|
1167
1214
|
exports.apply = function apply(ctx) {
|
|
1168
1215
|
rootCtx = ctx
|
|
1169
|
-
|
|
1216
|
+
|
|
1170
1217
|
const addLocale = (locale, dictionary) => {
|
|
1171
1218
|
try {
|
|
1172
1219
|
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
1173
1220
|
return ctx.locale.register(NS, locale, dictionary)
|
|
1174
1221
|
}
|
|
1175
1222
|
} catch {
|
|
1176
|
-
// Если язык уже занят (например, dsh-russian-lang) или зарегистрирован ранее — уступаем без ошибки
|
|
1177
1223
|
return () => {}
|
|
1178
1224
|
}
|
|
1179
1225
|
return () => {}
|
|
@@ -1195,7 +1241,6 @@ window.__ModuleLoader__.load({
|
|
|
1195
1241
|
addLocale('ru', ru)
|
|
1196
1242
|
}
|
|
1197
1243
|
|
|
1198
|
-
|
|
1199
1244
|
if (ctx.slots) {
|
|
1200
1245
|
const registerCard = () => {
|
|
1201
1246
|
try {
|
|
@@ -1230,7 +1275,7 @@ window.__ModuleLoader__.load({
|
|
|
1230
1275
|
trigger: '/',
|
|
1231
1276
|
name: 'moa',
|
|
1232
1277
|
order: 35,
|
|
1233
|
-
description:
|
|
1278
|
+
description: ru['slash.desc'] || en['slash.desc'],
|
|
1234
1279
|
candidates: (_session, req) => {
|
|
1235
1280
|
if (req.position !== 'leading') return Promise.resolve([])
|
|
1236
1281
|
const q = req.query.trim().toLowerCase()
|
|
@@ -1238,7 +1283,7 @@ window.__ModuleLoader__.load({
|
|
|
1238
1283
|
return Promise.resolve([
|
|
1239
1284
|
{
|
|
1240
1285
|
name: 'moa',
|
|
1241
|
-
description:
|
|
1286
|
+
description: ru['slash.desc'] || en['slash.desc'],
|
|
1242
1287
|
},
|
|
1243
1288
|
])
|
|
1244
1289
|
},
|
|
@@ -1263,3 +1308,4 @@ window.__ModuleLoader__.load({
|
|
|
1263
1308
|
return module.exports
|
|
1264
1309
|
},
|
|
1265
1310
|
})
|
|
1311
|
+
|