@goodandready/dsh-moa 0.2.8 → 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 +489 -463
- package/lib/moa-runner.js +228 -265
- 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>)',
|
|
17
76
|
}
|
|
18
77
|
|
|
19
|
-
|
|
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
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
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,57 +770,42 @@ 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
|
}),
|
|
654
806
|
React.createElement(
|
|
655
807
|
'div',
|
|
656
|
-
{ style: { marginTop:
|
|
808
|
+
{ style: { marginTop: 6, display: 'flex', flexDirection: 'column', gap: 3 } },
|
|
657
809
|
React.createElement(
|
|
658
810
|
'label',
|
|
659
811
|
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500 } },
|
|
@@ -661,37 +813,38 @@ window.__ModuleLoader__.load({
|
|
|
661
813
|
type: 'checkbox',
|
|
662
814
|
checked: currentPreset.ask_clarifying_questions !== false,
|
|
663
815
|
onChange: (e) => updateCurrentPreset((p) => ({ ...p, ask_clarifying_questions: e.target.checked })),
|
|
664
|
-
style: { accentColor: 'var(--dsw-alias-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
816
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
665
817
|
}),
|
|
666
|
-
t('
|
|
818
|
+
t('aggregator.questions_label')
|
|
667
819
|
),
|
|
668
820
|
React.createElement(
|
|
669
821
|
'div',
|
|
670
|
-
{
|
|
671
|
-
t('
|
|
822
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
823
|
+
t('aggregator.questions_hint')
|
|
672
824
|
)
|
|
673
825
|
)
|
|
674
826
|
)
|
|
675
827
|
),
|
|
676
|
-
|
|
828
|
+
|
|
829
|
+
/* ── SECTION 3: CANDIDATE PROPOSERS ── */
|
|
677
830
|
React.createElement(
|
|
678
831
|
'div',
|
|
679
|
-
{ className: 'moa-
|
|
832
|
+
{ className: 'moa-section-card' },
|
|
680
833
|
React.createElement(
|
|
681
834
|
'div',
|
|
682
|
-
{ className: 'moa-
|
|
683
|
-
React.createElement('span', null, t('
|
|
835
|
+
{ className: 'moa-section-title' },
|
|
836
|
+
React.createElement('span', null, t('proposers.title')),
|
|
684
837
|
React.createElement(
|
|
685
838
|
'div',
|
|
686
|
-
{ className: 'moa-
|
|
687
|
-
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') + ':'),
|
|
688
841
|
[2, 3, 4, 5].map((cnt) =>
|
|
689
842
|
React.createElement(
|
|
690
843
|
'button',
|
|
691
844
|
{
|
|
692
845
|
key: cnt,
|
|
693
846
|
type: 'button',
|
|
694
|
-
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',
|
|
695
848
|
onClick: () => setReferenceCount(cnt),
|
|
696
849
|
},
|
|
697
850
|
cnt
|
|
@@ -699,18 +852,18 @@ window.__ModuleLoader__.load({
|
|
|
699
852
|
)
|
|
700
853
|
)
|
|
701
854
|
),
|
|
702
|
-
React.createElement('div', { className: 'moa-
|
|
855
|
+
React.createElement('div', { className: 'moa-section-desc' }, t('proposers.desc')),
|
|
703
856
|
React.createElement(
|
|
704
857
|
'div',
|
|
705
|
-
{ className: 'moa-
|
|
858
|
+
{ className: 'moa-cand-list' },
|
|
706
859
|
currentRefs.map((ref, idx) =>
|
|
707
860
|
React.createElement(
|
|
708
861
|
'div',
|
|
709
|
-
{ key: idx, className: 'moa-
|
|
710
|
-
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}`),
|
|
711
864
|
React.createElement(
|
|
712
865
|
'div',
|
|
713
|
-
{ className: 'moa-
|
|
866
|
+
{ className: 'moa-cand-picker' },
|
|
714
867
|
React.createElement(SearchableModelPicker, {
|
|
715
868
|
provider: ref.provider || '',
|
|
716
869
|
model: ref.model || '',
|
|
@@ -724,8 +877,8 @@ window.__ModuleLoader__.load({
|
|
|
724
877
|
'button',
|
|
725
878
|
{
|
|
726
879
|
type: 'button',
|
|
727
|
-
className: 'moa-btn-
|
|
728
|
-
title:
|
|
880
|
+
className: 'moa-btn moa-btn-danger moa-btn-sub',
|
|
881
|
+
title: 'Remove',
|
|
729
882
|
onClick: () => removeReference(idx),
|
|
730
883
|
},
|
|
731
884
|
'✕'
|
|
@@ -737,180 +890,107 @@ window.__ModuleLoader__.load({
|
|
|
737
890
|
'button',
|
|
738
891
|
{
|
|
739
892
|
type: 'button',
|
|
740
|
-
className: 'moa-btn
|
|
893
|
+
className: 'moa-btn',
|
|
894
|
+
style: { alignSelf: 'flex-start' },
|
|
741
895
|
onClick: addReference,
|
|
742
896
|
},
|
|
743
|
-
|
|
897
|
+
t('proposers.add_btn')
|
|
744
898
|
),
|
|
745
899
|
React.createElement(
|
|
746
900
|
'div',
|
|
747
|
-
{
|
|
901
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4, borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 10 } },
|
|
748
902
|
React.createElement(
|
|
749
903
|
'div',
|
|
750
|
-
{ style: { display: 'flex',
|
|
751
|
-
React.createElement('span',
|
|
752
|
-
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'))
|
|
753
907
|
),
|
|
754
908
|
React.createElement(
|
|
755
909
|
'div',
|
|
756
|
-
{ className: 'moa-
|
|
910
|
+
{ className: 'moa-slider-row' },
|
|
757
911
|
React.createElement('input', {
|
|
758
912
|
type: 'range',
|
|
759
|
-
className: 'moa-
|
|
913
|
+
className: 'moa-slider',
|
|
760
914
|
min: '0',
|
|
761
915
|
max: '2',
|
|
762
916
|
step: '0.05',
|
|
763
917
|
value: refTemp,
|
|
764
918
|
onChange: (e) => setReferenceTemp(e.target.value),
|
|
765
919
|
}),
|
|
766
|
-
React.createElement('
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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'))
|
|
775
939
|
),
|
|
776
|
-
React.createElement(
|
|
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'))
|
|
945
|
+
),
|
|
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
|
+
)
|
|
777
952
|
)
|
|
778
953
|
),
|
|
779
|
-
|
|
954
|
+
|
|
955
|
+
/* ── FOOTER ACTIONS & SAVE ── */
|
|
780
956
|
React.createElement(
|
|
781
957
|
'div',
|
|
782
|
-
{
|
|
783
|
-
saveStatus && React.createElement('span', { className: 'moa-status-text' }, saveStatus),
|
|
958
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 12, marginTop: 4 } },
|
|
784
959
|
React.createElement(
|
|
785
960
|
'button',
|
|
786
961
|
{
|
|
787
962
|
type: 'button',
|
|
788
|
-
className: 'moa-
|
|
963
|
+
className: 'moa-btn moa-btn-primary',
|
|
964
|
+
style: { padding: '10px 24px', fontSize: 14, fontWeight: 600 },
|
|
789
965
|
onClick: handleSave,
|
|
790
966
|
},
|
|
791
|
-
t('save')
|
|
792
|
-
)
|
|
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
|
+
)
|
|
793
986
|
)
|
|
794
987
|
)
|
|
795
988
|
}
|
|
796
989
|
|
|
797
|
-
let rootCtx = null
|
|
798
|
-
|
|
799
|
-
const en = {
|
|
800
|
-
title: 'Mixture of Agents (MoA)',
|
|
801
|
-
description: 'Run tasks with multiple parallel proposer models and a synthesizing judge model',
|
|
802
|
-
slashMoa: 'Run task through Mixture of Agents (/moa <prompt>)',
|
|
803
|
-
defaultPreset: 'Default Preset (used on /moa without arguments)',
|
|
804
|
-
isDefault: 'default',
|
|
805
|
-
presetSelectLabel: 'Active Preset to Configure:',
|
|
806
|
-
createNewPreset: 'Create new preset...',
|
|
807
|
-
makeDefault: 'Make Default',
|
|
808
|
-
makeDefaultHint: 'Use this preset for /moa when no preset argument is given',
|
|
809
|
-
deletePreset: 'Delete preset',
|
|
810
|
-
delete: 'Delete',
|
|
811
|
-
confirmDeletePreset: 'Are you sure you want to delete preset',
|
|
812
|
-
newPresetPlaceholder: 'New preset name (e.g. fast-audit)',
|
|
813
|
-
create: 'Create',
|
|
814
|
-
cancel: 'Cancel',
|
|
815
|
-
aggregatorTitle: 'Judge / Aggregator Model (who reviews and synthesizes)',
|
|
816
|
-
aggregatorDesc: 'Evaluates candidate proposals, corrects hallucinations, and outputs the final unified solution.',
|
|
817
|
-
temperature: 'Temperature',
|
|
818
|
-
aggTempHint: 'Recommended 0.2 - 0.4 for rigorous verification and factual synthesis.',
|
|
819
|
-
proposersTitle: 'Candidate Models (Advisors)',
|
|
820
|
-
proposersDesc: 'Generates independent candidate answers in parallel to provide diverse perspectives.',
|
|
821
|
-
refTempLabel: 'Candidates Temperature (solution diversity):',
|
|
822
|
-
refTempHint: 'Recommended 0.6 - 0.8 for diverse creative hypotheses.',
|
|
823
|
-
parallelCount: 'Parallel workers',
|
|
824
|
-
candidate: 'Candidate',
|
|
825
|
-
addCandidate: 'Add parallel model slot',
|
|
826
|
-
remove: 'Remove',
|
|
827
|
-
save: 'Save Changes',
|
|
828
|
-
saved: 'Saved successfully',
|
|
829
|
-
loading: 'Saving…',
|
|
830
|
-
retry: 'Retry connection',
|
|
831
|
-
unavailable: 'MoA service temporarily unavailable (reconnecting...)',
|
|
832
|
-
searchPlaceholder: 'Search by model name or provider...',
|
|
833
|
-
modelsFound: 'Found',
|
|
834
|
-
of: 'of',
|
|
835
|
-
clear: 'Clear',
|
|
836
|
-
noModelsMatch: 'No models found matching query.',
|
|
837
|
-
useCustom: 'Use custom',
|
|
838
|
-
selectModel: 'Select model...',
|
|
839
|
-
judgeCriteriaLabel: 'Custom Judge Evaluation Criteria (optional):',
|
|
840
|
-
judgeCriteriaPlaceholder: 'e.g. Priority: performance, zero external dependencies, robust edge-case handling...',
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
const ru = {
|
|
844
|
-
title: 'Mixture of Agents (MoA)',
|
|
845
|
-
description: 'Запуск задач через ансамбль параллельных моделей-советников и модель-судью',
|
|
846
|
-
slashMoa: 'Запустить задачу через архитектуру Mixture of Agents (/moa <prompt>)',
|
|
847
|
-
defaultPreset: 'Основной пресет (при вызове /moa без параметров)',
|
|
848
|
-
isDefault: 'основной',
|
|
849
|
-
presetSelectLabel: 'Текущий пресет для настройки:',
|
|
850
|
-
createNewPreset: 'Создать новый пресет...',
|
|
851
|
-
makeDefault: 'Сделать основным',
|
|
852
|
-
makeDefaultHint: 'Использовать этот пресет для /moa при вызове без параметров',
|
|
853
|
-
deletePreset: 'Удалить пресет',
|
|
854
|
-
delete: 'Удалить',
|
|
855
|
-
confirmDeletePreset: 'Вы уверены, что хотите удалить пресет',
|
|
856
|
-
newPresetPlaceholder: 'Название нового пресета (например: fast-audit)',
|
|
857
|
-
create: 'Создать',
|
|
858
|
-
cancel: 'Отмена',
|
|
859
|
-
aggregatorTitle: 'Ведущая модель / Судья (кто проверяет и объединяет)',
|
|
860
|
-
aggregatorDesc: 'Сопоставляет варианты кандидатов, устраняет ошибки и синтезирует итоговое решение.',
|
|
861
|
-
temperature: 'Температура',
|
|
862
|
-
aggTempHint: 'Рекомендуется 0.2 – 0.4 для строгой проверки и синтеза без выдумок.',
|
|
863
|
-
proposersTitle: 'Модели-кандидаты (советники)',
|
|
864
|
-
proposersDesc: 'Параллельно формируют независимые варианты решения одной задачи.',
|
|
865
|
-
refTempLabel: 'Температура кандидатов (разнообразие решений):',
|
|
866
|
-
refTempHint: 'Рекомендуется 0.6 – 0.8 для получения независимых и креативных вариантов.',
|
|
867
|
-
parallelCount: 'Параллельных моделей',
|
|
868
|
-
candidate: 'Кандидат',
|
|
869
|
-
addCandidate: 'Добавить модель в параллель',
|
|
870
|
-
remove: 'Удалить',
|
|
871
|
-
save: 'Сохранить',
|
|
872
|
-
saved: 'Успешно сохранено',
|
|
873
|
-
loading: 'Сохранение…',
|
|
874
|
-
retry: 'Повторить подключение',
|
|
875
|
-
unavailable: 'Служба MoA временно недоступна (переподключение...)',
|
|
876
|
-
searchPlaceholder: 'Поиск модели по названию или провайдеру...',
|
|
877
|
-
modelsFound: 'Найдено',
|
|
878
|
-
of: 'из',
|
|
879
|
-
clear: 'Сбросить',
|
|
880
|
-
noModelsMatch: 'Модели не найдены.',
|
|
881
|
-
useCustom: 'Использовать кастомное',
|
|
882
|
-
selectModel: 'Выберите модель...',
|
|
883
|
-
judgeCriteriaLabel: 'Критерии оценки судьи (опционально):',
|
|
884
|
-
judgeCriteriaPlaceholder: 'например: Приоритет — производительность, минимум зависимостей, обработка граничных случаев...',
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
function makeT(ctx, props) {
|
|
888
|
-
return (key) => {
|
|
889
|
-
if (props && typeof props.t === 'function') {
|
|
890
|
-
try {
|
|
891
|
-
const res = props.t(key)
|
|
892
|
-
if (res && res !== key) return res
|
|
893
|
-
} catch (_) {}
|
|
894
|
-
}
|
|
895
|
-
const effectiveCtx = (props && props.ctx) || ctx || rootCtx
|
|
896
|
-
if (effectiveCtx && effectiveCtx.locale && typeof effectiveCtx.locale.bind === 'function') {
|
|
897
|
-
try {
|
|
898
|
-
const bound = effectiveCtx.locale.bind(NS)
|
|
899
|
-
const res = bound(key)
|
|
900
|
-
if (res && res !== key) return res
|
|
901
|
-
} catch (_) {}
|
|
902
|
-
}
|
|
903
|
-
const curLocale = (effectiveCtx && effectiveCtx.locale && effectiveCtx.locale.getSnapshot && effectiveCtx.locale.getSnapshot().active) || 'ru'
|
|
904
|
-
const dict = curLocale === 'en' ? en : ru
|
|
905
|
-
return dict[key] || ru[key] || en[key] || key
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
990
|
function useMoASettings(props) {
|
|
910
|
-
const t = makeT(rootCtx, props)
|
|
911
991
|
const effectiveCtx = (props && props.ctx) || rootCtx
|
|
992
|
+
const t = makeT(ru, en)
|
|
912
993
|
|
|
913
|
-
// Reactive binding to settingsScope if available
|
|
914
994
|
const scope = React.useMemo(() => {
|
|
915
995
|
if (!effectiveCtx) return undefined
|
|
916
996
|
const s = effectiveCtx.get ? effectiveCtx.get('settingsScope') : effectiveCtx.settingsScope
|
|
@@ -931,7 +1011,6 @@ window.__ModuleLoader__.load({
|
|
|
931
1011
|
const [availableModels, setAvailableModels] = React.useState([])
|
|
932
1012
|
const [saveStatus, setSaveStatus] = React.useState('')
|
|
933
1013
|
|
|
934
|
-
// Sync from settingsScope snapshot when available
|
|
935
1014
|
React.useEffect(() => {
|
|
936
1015
|
if (!scope || !snapshot) return
|
|
937
1016
|
const snapStatus = snapshot.status || 'ready'
|
|
@@ -984,7 +1063,7 @@ window.__ModuleLoader__.load({
|
|
|
984
1063
|
}, [reload])
|
|
985
1064
|
|
|
986
1065
|
const handleSave = () => {
|
|
987
|
-
setSaveStatus(t('
|
|
1066
|
+
setSaveStatus(t('actions.saving'))
|
|
988
1067
|
|
|
989
1068
|
if (scope && typeof scope.set === 'function') {
|
|
990
1069
|
Promise.all([
|
|
@@ -992,8 +1071,8 @@ window.__ModuleLoader__.load({
|
|
|
992
1071
|
scope.set('default_preset', defaultPreset),
|
|
993
1072
|
])
|
|
994
1073
|
.then(() => {
|
|
995
|
-
setSaveStatus(t('saved'))
|
|
996
|
-
setTimeout(() => setSaveStatus(''),
|
|
1074
|
+
setSaveStatus(t('actions.saved'))
|
|
1075
|
+
setTimeout(() => setSaveStatus(''), 3000)
|
|
997
1076
|
})
|
|
998
1077
|
.catch((err) => {
|
|
999
1078
|
console.warn('[dsh-moa] scope.set failed, trying REST fallback:', err)
|
|
@@ -1014,10 +1093,10 @@ window.__ModuleLoader__.load({
|
|
|
1014
1093
|
.then((r) => r.json())
|
|
1015
1094
|
.then((res) => {
|
|
1016
1095
|
if (res && res.ok) {
|
|
1017
|
-
setSaveStatus(t('saved'))
|
|
1096
|
+
setSaveStatus(t('actions.saved'))
|
|
1018
1097
|
if (Array.isArray(res.presets)) setPresets(res.presets)
|
|
1019
1098
|
if (res.defaultPreset) setDefaultPreset(res.defaultPreset)
|
|
1020
|
-
setTimeout(() => setSaveStatus(''),
|
|
1099
|
+
setTimeout(() => setSaveStatus(''), 3000)
|
|
1021
1100
|
} else {
|
|
1022
1101
|
setSaveStatus('Error saving: ' + (res?.error || 'failed'))
|
|
1023
1102
|
}
|
|
@@ -1041,86 +1120,34 @@ window.__ModuleLoader__.load({
|
|
|
1041
1120
|
}
|
|
1042
1121
|
}
|
|
1043
1122
|
|
|
1044
|
-
function MoASection(props) {
|
|
1045
|
-
ensureStyles()
|
|
1046
|
-
const state = useMoASettings(props)
|
|
1047
|
-
|
|
1048
|
-
return React.createElement(
|
|
1049
|
-
'div',
|
|
1050
|
-
{ className: 'moa-section' },
|
|
1051
|
-
React.createElement('div', { className: 'moa-section-title' }, state.t('title')),
|
|
1052
|
-
React.createElement('div', { className: 'moa-section-sub' }, state.t('description')),
|
|
1053
|
-
state.status === 'loading'
|
|
1054
|
-
? React.createElement('div', { className: 'moa-hint' }, state.t('loading'))
|
|
1055
|
-
: state.status === 'unavailable'
|
|
1056
|
-
? React.createElement(
|
|
1057
|
-
'div',
|
|
1058
|
-
{ className: 'moa-field' },
|
|
1059
|
-
React.createElement('div', { className: 'moa-hint' }, state.t('unavailable')),
|
|
1060
|
-
React.createElement(
|
|
1061
|
-
'button',
|
|
1062
|
-
{
|
|
1063
|
-
type: 'button',
|
|
1064
|
-
className: 'moa-count-btn',
|
|
1065
|
-
style: { width: 'fit-content', marginTop: 8 },
|
|
1066
|
-
onClick: state.reload,
|
|
1067
|
-
},
|
|
1068
|
-
state.t('retry')
|
|
1069
|
-
)
|
|
1070
|
-
)
|
|
1071
|
-
: React.createElement(MoAEditor, state)
|
|
1072
|
-
)
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
// ── ERROR BOUNDARY ──────────────────────────────────────────────
|
|
1077
1123
|
class MoAErrorBoundary extends React.Component {
|
|
1078
1124
|
constructor(props) {
|
|
1079
1125
|
super(props)
|
|
1080
|
-
this.state = { hasError: false, error: null
|
|
1126
|
+
this.state = { hasError: false, error: null }
|
|
1081
1127
|
}
|
|
1082
1128
|
static getDerivedStateFromError(error) {
|
|
1083
1129
|
return { hasError: true, error }
|
|
1084
1130
|
}
|
|
1085
1131
|
componentDidCatch(error, errorInfo) {
|
|
1086
1132
|
console.error('[dsh-moa] MoAErrorBoundary caught error:', error, errorInfo)
|
|
1087
|
-
this.setState({ errorInfo })
|
|
1088
1133
|
}
|
|
1089
1134
|
render() {
|
|
1090
1135
|
if (this.state.hasError) {
|
|
1091
1136
|
return React.createElement(
|
|
1092
1137
|
'div',
|
|
1093
1138
|
{
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
borderRadius: '8px',
|
|
1097
|
-
border: '1px solid #ef4444',
|
|
1098
|
-
background: 'rgba(239,68,68,0.08)',
|
|
1099
|
-
color: '#ef4444',
|
|
1100
|
-
fontSize: '13px',
|
|
1101
|
-
fontFamily: 'monospace',
|
|
1102
|
-
whiteSpace: 'pre-wrap',
|
|
1103
|
-
wordBreak: 'break-word',
|
|
1104
|
-
},
|
|
1139
|
+
className: 'moa-alert-err',
|
|
1140
|
+
style: { margin: '12px 0', padding: '14px', borderRadius: '8px' },
|
|
1105
1141
|
},
|
|
1106
|
-
React.createElement('div', { style: { fontWeight:
|
|
1107
|
-
React.createElement('div',
|
|
1108
|
-
this.state.errorInfo && React.createElement(
|
|
1109
|
-
'details',
|
|
1110
|
-
{ style: { marginTop: 8 } },
|
|
1111
|
-
React.createElement('summary', null, 'Component stack'),
|
|
1112
|
-
React.createElement('pre', { style: { fontSize: 11 } }, this.state.errorInfo.componentStack || '')
|
|
1113
|
-
),
|
|
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)),
|
|
1114
1144
|
React.createElement(
|
|
1115
1145
|
'button',
|
|
1116
1146
|
{
|
|
1117
1147
|
type: 'button',
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
cursor: 'pointer', fontSize: 12,
|
|
1122
|
-
},
|
|
1123
|
-
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 }),
|
|
1124
1151
|
},
|
|
1125
1152
|
'Retry'
|
|
1126
1153
|
)
|
|
@@ -1149,8 +1176,8 @@ window.__ModuleLoader__.load({
|
|
|
1149
1176
|
React.createElement(
|
|
1150
1177
|
'div',
|
|
1151
1178
|
null,
|
|
1152
|
-
React.createElement('div', { className: 'moa-title' }, state.t('title')),
|
|
1153
|
-
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)
|
|
1154
1181
|
),
|
|
1155
1182
|
React.createElement(Chevron, {
|
|
1156
1183
|
className: open ? 'moa-chev moa-chev-open' : 'moa-chev',
|
|
@@ -1161,21 +1188,21 @@ window.__ModuleLoader__.load({
|
|
|
1161
1188
|
'div',
|
|
1162
1189
|
{ className: 'moa-body' },
|
|
1163
1190
|
state.status === 'loading'
|
|
1164
|
-
? React.createElement('div', {
|
|
1191
|
+
? React.createElement('div', { style: { padding: '16px 0', color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, 'Loading MoA settings…')
|
|
1165
1192
|
: state.status === 'unavailable'
|
|
1166
1193
|
? React.createElement(
|
|
1167
1194
|
'div',
|
|
1168
|
-
{
|
|
1169
|
-
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'),
|
|
1170
1197
|
React.createElement(
|
|
1171
1198
|
'button',
|
|
1172
1199
|
{
|
|
1173
1200
|
type: 'button',
|
|
1174
|
-
className: 'moa-
|
|
1175
|
-
style: { width: 'fit-content'
|
|
1201
|
+
className: 'moa-btn',
|
|
1202
|
+
style: { width: 'fit-content' },
|
|
1176
1203
|
onClick: state.reload,
|
|
1177
1204
|
},
|
|
1178
|
-
state.t('retry')
|
|
1205
|
+
state.t('actions.retry')
|
|
1179
1206
|
)
|
|
1180
1207
|
)
|
|
1181
1208
|
: React.createElement(MoAEditor, state)
|
|
@@ -1186,14 +1213,13 @@ window.__ModuleLoader__.load({
|
|
|
1186
1213
|
exports.inject = ['slots', 'locale', 'inputTriggers', 'settingsScope']
|
|
1187
1214
|
exports.apply = function apply(ctx) {
|
|
1188
1215
|
rootCtx = ctx
|
|
1189
|
-
|
|
1216
|
+
|
|
1190
1217
|
const addLocale = (locale, dictionary) => {
|
|
1191
1218
|
try {
|
|
1192
1219
|
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
1193
1220
|
return ctx.locale.register(NS, locale, dictionary)
|
|
1194
1221
|
}
|
|
1195
1222
|
} catch {
|
|
1196
|
-
// Если язык уже занят (например, dsh-russian-lang) или зарегистрирован ранее — уступаем без ошибки
|
|
1197
1223
|
return () => {}
|
|
1198
1224
|
}
|
|
1199
1225
|
return () => {}
|
|
@@ -1215,7 +1241,6 @@ window.__ModuleLoader__.load({
|
|
|
1215
1241
|
addLocale('ru', ru)
|
|
1216
1242
|
}
|
|
1217
1243
|
|
|
1218
|
-
|
|
1219
1244
|
if (ctx.slots) {
|
|
1220
1245
|
const registerCard = () => {
|
|
1221
1246
|
try {
|
|
@@ -1250,7 +1275,7 @@ window.__ModuleLoader__.load({
|
|
|
1250
1275
|
trigger: '/',
|
|
1251
1276
|
name: 'moa',
|
|
1252
1277
|
order: 35,
|
|
1253
|
-
description:
|
|
1278
|
+
description: ru['slash.desc'] || en['slash.desc'],
|
|
1254
1279
|
candidates: (_session, req) => {
|
|
1255
1280
|
if (req.position !== 'leading') return Promise.resolve([])
|
|
1256
1281
|
const q = req.query.trim().toLowerCase()
|
|
@@ -1258,7 +1283,7 @@ window.__ModuleLoader__.load({
|
|
|
1258
1283
|
return Promise.resolve([
|
|
1259
1284
|
{
|
|
1260
1285
|
name: 'moa',
|
|
1261
|
-
description:
|
|
1286
|
+
description: ru['slash.desc'] || en['slash.desc'],
|
|
1262
1287
|
},
|
|
1263
1288
|
])
|
|
1264
1289
|
},
|
|
@@ -1283,3 +1308,4 @@ window.__ModuleLoader__.load({
|
|
|
1283
1308
|
return module.exports
|
|
1284
1309
|
},
|
|
1285
1310
|
})
|
|
1311
|
+
|