@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/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
- let React = require('react')
7
-
16
+ const React = require('react')
8
17
  const NS = 'dsh-moa'
9
- let localeRegistered = false
10
-
11
- let ChevronIcon = null
12
- try {
13
- const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
14
- ChevronIcon = primitives && primitives.IconChevronDownOutline14
15
- } catch {
16
- ChevronIcon = null
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
- .moa-section { display:flex; flex-direction:column; gap:16px; padding:4px 0; max-width:780px; }
41
- .moa-section-title { font-size:18px; font-weight:700; color:var(--dsw-alias-label-primary); }
42
- .moa-section-sub { font-size:13px; color:var(--dsw-alias-label-secondary); margin-top:2px; margin-bottom:6px; }
43
- .moa-card { border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-3); border-radius:12px; list-style:none; margin-bottom:12px; }
44
- .moa-head { appearance:none; width:100%; font:inherit; color:inherit; text-align:left; cursor:pointer; background:0 0; border:0; border-radius:12px; display:flex; align-items:center; gap:12px; padding:14px 16px; }
45
- .moa-head:focus-visible { outline:2px solid var(--dsw-alias-label-primary); outline-offset:2px; }
46
- .moa-title { color:var(--dsw-alias-label-primary); font-size:15px; font-weight:600; line-height:1.4; }
47
- .moa-sub { color:var(--dsw-alias-label-secondary); font-size:13px; margin-top:2px; }
48
- .moa-chev { margin-left:auto; flex:none; color:var(--dsw-alias-label-tertiary); transition:transform .16s ease; }
49
- .moa-chev-open { transform:rotate(180deg); }
50
- .moa-body { border-top:1px solid var(--dsw-alias-border-l2); margin:0 16px; padding:12px 0 16px; display:flex; flex-direction:column; gap:14px; }
51
- .moa-box { border:1px solid var(--dsw-alias-border-l2); border-radius:10px; padding:14px 16px; background:var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.02)); display:flex; flex-direction:column; gap:12px; }
52
- .moa-box-title { font-size:14px; font-weight:600; color:var(--dsw-alias-label-primary); display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px; }
53
- .moa-field { display:flex; flex-direction:column; gap:6px; }
54
- .moa-label { font-size:13px; font-weight:500; color:var(--dsw-alias-label-primary); }
55
- .moa-hint { font-size:12px; color:var(--dsw-alias-label-secondary); line-height:1.4; }
56
- .moa-select { height:36px; border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-3); color:var(--dsw-alias-label-primary); border-radius:8px; padding:0 10px; font-size:13px; }
57
- .moa-select:focus { outline:1px solid var(--dsw-alias-label-primary); }
58
- .moa-count-bar { display:flex; align-items:center; gap:8px; }
59
- .moa-count-btn { appearance:none; font:inherit; padding:4px 12px; border-radius:6px; border:1px solid var(--dsw-alias-border-l2); background:transparent; color:var(--dsw-alias-label-primary); font-size:12px; cursor:pointer; }
60
- .moa-count-btn-active { background:var(--dsw-alias-label-primary); color:var(--dsw-alias-bg-layer-3); border-color:transparent; font-weight:600; }
61
- .moa-refs-list { display:flex; flex-direction:column; gap:10px; }
62
- .moa-ref-row { display:flex; align-items:center; gap:8px; }
63
- .moa-ref-badge { font-size:12px; font-weight:600; color:var(--dsw-alias-label-secondary); width:85px; flex-shrink:0; }
64
- .moa-ref-picker { flex:1; min-width:0; }
65
- .moa-btn-del { appearance:none; font:inherit; border:none; background:transparent; color:var(--dsw-alias-label-tertiary); cursor:pointer; padding:6px 10px; font-size:14px; border-radius:6px; }
66
- .moa-btn-del:hover { color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-3); }
67
- .moa-btn-add { appearance:none; font:inherit; border:1px dashed var(--dsw-alias-border-l2); background:transparent; color:var(--dsw-alias-label-primary); border-radius:8px; padding:8px 12px; font-size:13px; font-weight:500; cursor:pointer; text-align:center; transition:border-color .15s; }
68
- .moa-btn-add:hover { border-color:var(--dsw-alias-label-primary); }
69
- .moa-foot { border-top:1px solid var(--dsw-alias-border-l2); display:flex; justify-content:flex-end; align-items:center; gap:10px; padding:14px 0 4px; }
70
- .moa-save { appearance:none; font:inherit; cursor:pointer; border:1px solid transparent; border-radius:8px; padding:8px 20px; font-size:13px; font-weight:600; background:var(--dsw-alias-label-primary); color:var(--dsw-alias-bg-layer-3); transition:opacity .15s; }
71
- .moa-save:hover { opacity:0.9; }
72
- .moa-status-text { font-size:12px; color:var(--dsw-alias-label-secondary); }
73
-
74
- /* Preset Toolbar & Actions */
75
- .moa-preset-bar { display:flex; flex-direction:column; gap:8px; margin-bottom:4px; }
76
- .moa-preset-row { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
77
- .moa-btn-sub { appearance:none; font:inherit; padding:7px 12px; border-radius:8px; border:1px solid var(--dsw-alias-border-l2); background:transparent; color:var(--dsw-alias-label-primary); font-size:12px; font-weight:500; cursor:pointer; white-space:nowrap; }
78
- .moa-btn-sub:hover { border-color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-3); }
79
- .moa-btn-del-preset { appearance:none; font:inherit; padding:7px 12px; border-radius:8px; border:1px solid var(--dsw-alias-border-l2); background:transparent; color:#ef4444; font-size:12px; font-weight:500; cursor:pointer; white-space:nowrap; }
80
- .moa-btn-del-preset:hover { border-color:#ef4444; background:rgba(239,68,68,0.08); }
81
- .moa-new-preset-box { display:flex; align-items:center; gap:8px; padding:10px 12px; border:1px dashed var(--dsw-alias-brand-primary, var(--dsw-alias-label-primary)); border-radius:8px; background:var(--dsw-alias-bg-layer-2); margin-top:4px; }
82
- .moa-new-input { flex:1; height:32px; padding:0 10px; border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-3); color:var(--dsw-alias-label-primary); border-radius:6px; font-size:13px; outline:none; }
83
- .moa-new-input:focus { border-color:var(--dsw-alias-label-primary); }
84
-
85
- /* Temperature Sliders */
86
- .moa-temp-ctrl { display:flex; align-items:center; gap:12px; margin-top:2px; }
87
- .moa-temp-slider { flex:1; accent-color:var(--dsw-alias-brand-primary, var(--dsw-alias-label-primary)); cursor:pointer; height:4px; }
88
- .moa-temp-input { width:62px; height:30px; text-align:center; padding:0 6px; border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-3); color:var(--dsw-alias-label-primary); border-radius:6px; font-size:13px; font-weight:600; outline:none; }
89
- .moa-temp-input:focus { border-color:var(--dsw-alias-label-primary); }
90
-
91
- /* Searchable Model Picker */
92
- .moa-picker-container { position:relative; width:100%; }
93
- .moa-picker-btn { width:100%; min-height:36px; padding:5px 12px; background:var(--dsw-alias-bg-layer-3); 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; }
94
- .moa-picker-btn:hover { border-color:var(--dsw-alias-label-secondary); }
95
- .moa-picker-btn:focus-visible { outline:2px solid var(--dsw-alias-label-primary); outline-offset:1px; }
96
- .moa-picker-summary { display:flex; align-items:center; gap:8px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
97
- .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; }
98
- .moa-model-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:500; }
99
- .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 10px 30px rgba(0,0,0,0.4); z-index:1000; max-height:360px; display:flex; flex-direction:column; overflow:hidden; }
100
- .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; }
101
- .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; }
102
- .moa-search-input:focus { border-color:var(--dsw-alias-label-primary); }
103
- .moa-search-info { font-size:11px; color:var(--dsw-alias-label-tertiary); padding:0 2px; display:flex; justify-content:space-between; }
104
- .moa-picker-list { overflow-y:auto; max-height:280px; padding:6px; display:flex; flex-direction:column; gap:2px; }
105
- .moa-group-title { font-size:11px; font-weight:700; text-transform:uppercase; color:var(--dsw-alias-label-tertiary); padding:8px 8px 4px; }
106
- .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; }
107
- .moa-picker-item:hover, .moa-picker-item-selected { background:var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06)); }
108
- .moa-picker-check { color:var(--dsw-alias-brand-primary, #10b981); font-weight:bold; font-size:14px; flex-shrink:0; }
109
- .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; }
110
- .moa-custom-opt:hover { background:var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06)); }
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 (Subscription)',
124
- claude: 'Claude (Anthropic Subscription)',
125
- grok: 'Grok (xAI Subscription)',
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 (Zai)',
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('selectModel') || 'Выберите модель...'
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 onDown = (e) => {
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', onDown)
193
- return () => document.removeEventListener('mousedown', onDown)
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 q = query.trim()
210
- if (!q) return
211
- const parts = q.split(':')
212
- if (parts.length >= 2) {
213
- onChange(parts[0].trim(), parts.slice(1).join(':').trim())
214
- } else {
215
- onChange(provider || 'custom', q)
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
- { ref: rootRef, className: 'moa-picker-container' },
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, { className: open ? 'moa-chev moa-chev-open' : 'moa-chev' })
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('searchPlaceholder') || 'Поиск по названию или провайдеру...',
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('modelsFound') || 'Найдено'}: ${filtered.length} ${t('of') || 'из'} ${availableModels.length}`
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('noModelsMatch') || 'Модели не найдены.'
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('useCustom') || 'Использовать'}: "${query.trim()}"`
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: 'antigravity',
450
- model: 'gemini-3-flash',
596
+ provider: 'codex',
597
+ model: 'gpt-5.6-sol',
451
598
  },
452
- reference_temperature: currentPreset?.reference_temperature ?? 0.6,
453
- aggregator_temperature: currentPreset?.aggregator_temperature ?? 0.4,
454
- max_tokens: currentPreset?.max_tokens ?? 4096,
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 (!confirm(`${t('confirmDeletePreset') || 'Удалить пресет'} "${currentPreset.name}"?`)) return
465
- const remaining = presets.filter((p) => p.name !== currentPreset.name)
466
- if (defaultPreset === currentPreset.name) {
467
- setDefaultPreset(remaining[0].name)
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
- React.Fragment,
480
- null,
481
- /* Preset Selector Toolbar (No top tab buttons!) */
629
+ 'div',
630
+ { className: 'moa-page' },
631
+
632
+ /* ── HEADER WITH LIVE STATUS CHIPS ── */
482
633
  React.createElement(
483
634
  'div',
484
- { className: 'moa-preset-bar' },
635
+ { className: 'moa-header' },
485
636
  React.createElement(
486
637
  'div',
487
- { className: 'moa-field' },
488
- React.createElement('label', { className: 'moa-label' }, t('presetSelectLabel') || 'Текущий пресет для настройки:'),
638
+ { className: 'moa-page-title' },
639
+ React.createElement('span', null, '🧠 ' + t('header.title')),
489
640
  React.createElement(
490
641
  'div',
491
- { className: 'moa-preset-row' },
492
- React.createElement(
493
- 'select',
494
- {
495
- className: 'moa-select',
496
- style: { flex: 1, minWidth: 200 },
497
- value: isCreating ? '__new__' : currentPreset.name,
498
- onChange: (e) => {
499
- if (e.target.value === '__new__') {
500
- setIsCreating(true)
501
- } else {
502
- setIsCreating(false)
503
- setSelectedPresetName(e.target.value)
504
- }
505
- },
506
- },
507
- presets.map((pr) =>
508
- React.createElement(
509
- 'option',
510
- { key: pr.name, value: pr.name },
511
- pr.name + (pr.name === defaultPreset ? ` ⭐ (${t('isDefault') || 'основной'})` : '')
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
- '⭐ ' + (t('makeDefault') || 'Сделать основным')
671
+ '★ ' + t('presets.set_default')
531
672
  ),
532
- presets.length > 1 &&
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-del-preset',
538
- onClick: handleDeletePreset,
539
- title: t('deletePreset') || 'Удалить пресет',
680
+ className: 'moa-btn moa-btn-sub',
681
+ onClick: () => setIsCreating(true),
540
682
  },
541
- '🗑️ ' + (t('delete') || 'Удалить')
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
- { className: 'moa-new-preset-box' },
719
+ { style: { display: 'flex', gap: 8, alignItems: 'center', marginTop: 4 } },
549
720
  React.createElement('input', {
550
721
  type: 'text',
551
- className: 'moa-new-input',
552
- placeholder: t('newPresetPlaceholder') || 'Название нового пресета (например: fast-audit)',
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-sub',
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-sub',
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
- /* Aggregator / Judge Box with Temperature Slider */
752
+
753
+ /* ── SECTION 2: AGGREGATOR / JUDGE ── */
582
754
  React.createElement(
583
755
  'div',
584
- { className: 'moa-box' },
756
+ { className: 'moa-section-card' },
585
757
  React.createElement(
586
758
  'div',
587
- { className: 'moa-box-title' },
588
- React.createElement('span', null, t('aggregatorTitle')),
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-hint' }, t('aggregatorDesc')),
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
- { className: 'moa-field', style: { marginTop: 4 } },
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-temp-ctrl' },
782
+ { className: 'moa-slider-row' },
610
783
  React.createElement('input', {
611
784
  type: 'range',
612
- className: 'moa-temp-slider',
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('input', {
620
- type: 'number',
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
- { className: 'moa-field', style: { marginTop: 4, borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8 } },
634
- React.createElement('label', { className: 'moa-label', style: { fontSize: 12 } }, t('judgeCriteriaLabel') || 'Критерии оценки судьи (опционально):'),
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
- style: {
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
- /* Proposers / Advisors Box with Temperature Slider */
828
+
829
+ /* ── SECTION 3: CANDIDATE PROPOSERS ── */
657
830
  React.createElement(
658
831
  'div',
659
- { className: 'moa-box' },
832
+ { className: 'moa-section-card' },
660
833
  React.createElement(
661
834
  'div',
662
- { className: 'moa-box-title' },
663
- React.createElement('span', null, t('proposersTitle')),
835
+ { className: 'moa-section-title' },
836
+ React.createElement('span', null, t('proposers.title')),
664
837
  React.createElement(
665
838
  'div',
666
- { className: 'moa-count-bar' },
667
- React.createElement('span', { className: 'moa-hint' }, t('parallelCount') + ':'),
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-count-btn moa-count-btn-active' : 'moa-count-btn',
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-hint' }, t('proposersDesc')),
855
+ React.createElement('div', { className: 'moa-section-desc' }, t('proposers.desc')),
683
856
  React.createElement(
684
857
  'div',
685
- { className: 'moa-refs-list' },
858
+ { className: 'moa-cand-list' },
686
859
  currentRefs.map((ref, idx) =>
687
860
  React.createElement(
688
861
  'div',
689
- { key: idx, className: 'moa-ref-row' },
690
- React.createElement('span', { className: 'moa-ref-badge' }, `${t('candidate')} #${idx + 1}:`),
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-ref-picker' },
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-del',
708
- title: t('remove'),
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-add',
893
+ className: 'moa-btn',
894
+ style: { alignSelf: 'flex-start' },
721
895
  onClick: addReference,
722
896
  },
723
- '+ ' + t('addCandidate')
897
+ t('proposers.add_btn')
724
898
  ),
725
899
  React.createElement(
726
900
  'div',
727
- { className: 'moa-field', style: { marginTop: 4, borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 10 } },
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', alignItems: 'center', justifyContent: 'space-between' } },
731
- React.createElement('span', { className: 'moa-label', style: { fontSize: 12 } }, t('refTempLabel') || 'Температура кандидатов (разнообразие решений):'),
732
- React.createElement('span', { style: { fontWeight: 600, fontSize: 13 } }, refTemp)
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-temp-ctrl' },
910
+ { className: 'moa-slider-row' },
737
911
  React.createElement('input', {
738
912
  type: 'range',
739
- className: 'moa-temp-slider',
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('input', {
747
- type: 'number',
748
- className: 'moa-temp-input',
749
- min: '0',
750
- max: '2',
751
- step: '0.05',
752
- value: refTemp,
753
- onChange: (e) => setReferenceTemp(e.target.value),
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('div', { className: 'moa-hint' }, t('refTempHint') || 'Рекомендуется 0.6 – 0.8 для получения независимых и креативных вариантов.')
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
- /* Save Footer */
954
+
955
+ /* ── FOOTER ACTIONS & SAVE ── */
760
956
  React.createElement(
761
957
  'div',
762
- { className: 'moa-foot' },
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-save',
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('loading'))
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(''), 2500)
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(''), 2500)
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, errorInfo: 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
- style: {
1075
- padding: '16px',
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: 700, marginBottom: 8 } }, '⚠ MoA Settings Error'),
1087
- React.createElement('div', null, String(this.state.error)),
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
- style: {
1099
- marginTop: 12, padding: '6px 16px', border: '1px solid #ef4444',
1100
- background: 'transparent', color: '#ef4444', borderRadius: 6,
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', { className: 'moa-hint' }, state.t('loading'))
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
- { className: 'moa-field' },
1149
- React.createElement('div', { className: 'moa-hint' }, state.t('unavailable')),
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-count-btn',
1155
- style: { width: 'fit-content', marginTop: 8 },
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
- const t = makeT(ctx)
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: t('slashMoa'),
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: t('slashMoa'),
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
+