@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/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>)',
17
76
  }
18
77
 
19
- function FallbackChevron({ className }) {
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
- .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,57 +770,42 @@ 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
  }),
654
806
  React.createElement(
655
807
  'div',
656
- { style: { marginTop: 8, display: 'flex', flexDirection: 'column', gap: 4 } },
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('askQuestionsLabel') || 'Спрашивать уточняющие вопросы при неясных задачах (Опросник)'
818
+ t('aggregator.questions_label')
667
819
  ),
668
820
  React.createElement(
669
821
  'div',
670
- { className: 'moa-hint', style: { marginLeft: 22 } },
671
- t('askQuestionsHint') || 'Если включено: при коротких задачах судья сформирует опросник. Если выключено: MoA сразу переходит к генерации решения.'
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
- /* Proposers / Advisors Box with Temperature Slider */
828
+
829
+ /* ── SECTION 3: CANDIDATE PROPOSERS ── */
677
830
  React.createElement(
678
831
  'div',
679
- { className: 'moa-box' },
832
+ { className: 'moa-section-card' },
680
833
  React.createElement(
681
834
  'div',
682
- { className: 'moa-box-title' },
683
- React.createElement('span', null, t('proposersTitle')),
835
+ { className: 'moa-section-title' },
836
+ React.createElement('span', null, t('proposers.title')),
684
837
  React.createElement(
685
838
  'div',
686
- { className: 'moa-count-bar' },
687
- 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') + ':'),
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-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',
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-hint' }, t('proposersDesc')),
855
+ React.createElement('div', { className: 'moa-section-desc' }, t('proposers.desc')),
703
856
  React.createElement(
704
857
  'div',
705
- { className: 'moa-refs-list' },
858
+ { className: 'moa-cand-list' },
706
859
  currentRefs.map((ref, idx) =>
707
860
  React.createElement(
708
861
  'div',
709
- { key: idx, className: 'moa-ref-row' },
710
- 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}`),
711
864
  React.createElement(
712
865
  'div',
713
- { className: 'moa-ref-picker' },
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-del',
728
- title: t('remove'),
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-add',
893
+ className: 'moa-btn',
894
+ style: { alignSelf: 'flex-start' },
741
895
  onClick: addReference,
742
896
  },
743
- '+ ' + t('addCandidate')
897
+ t('proposers.add_btn')
744
898
  ),
745
899
  React.createElement(
746
900
  'div',
747
- { 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 } },
748
902
  React.createElement(
749
903
  'div',
750
- { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
751
- React.createElement('span', { className: 'moa-label', style: { fontSize: 12 } }, t('refTempLabel') || 'Температура кандидатов (разнообразие решений):'),
752
- 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'))
753
907
  ),
754
908
  React.createElement(
755
909
  'div',
756
- { className: 'moa-temp-ctrl' },
910
+ { className: 'moa-slider-row' },
757
911
  React.createElement('input', {
758
912
  type: 'range',
759
- className: 'moa-temp-slider',
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('input', {
767
- type: 'number',
768
- className: 'moa-temp-input',
769
- min: '0',
770
- max: '2',
771
- step: '0.05',
772
- value: refTemp,
773
- onChange: (e) => setReferenceTemp(e.target.value),
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('div', { className: 'moa-hint' }, t('refTempHint') || 'Рекомендуется 0.6 – 0.8 для получения независимых и креативных вариантов.')
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
- /* Save Footer */
954
+
955
+ /* ── FOOTER ACTIONS & SAVE ── */
780
956
  React.createElement(
781
957
  'div',
782
- { className: 'moa-foot' },
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-save',
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('loading'))
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(''), 2500)
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(''), 2500)
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, errorInfo: 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
- style: {
1095
- padding: '16px',
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: 700, marginBottom: 8 } }, '⚠ MoA Settings Error'),
1107
- React.createElement('div', null, String(this.state.error)),
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
- style: {
1119
- marginTop: 12, padding: '6px 16px', border: '1px solid #ef4444',
1120
- background: 'transparent', color: '#ef4444', borderRadius: 6,
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', { 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…')
1165
1192
  : state.status === 'unavailable'
1166
1193
  ? React.createElement(
1167
1194
  'div',
1168
- { className: 'moa-field' },
1169
- 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'),
1170
1197
  React.createElement(
1171
1198
  'button',
1172
1199
  {
1173
1200
  type: 'button',
1174
- className: 'moa-count-btn',
1175
- style: { width: 'fit-content', marginTop: 8 },
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
- const t = makeT(ctx)
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: t('slashMoa'),
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: t('slashMoa'),
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
+