viberadar 0.3.160 → 0.3.162

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.
@@ -1468,6 +1468,43 @@
1468
1468
  .load-btn-run:hover:not(:disabled) { background: #2a4a2a; }
1469
1469
  .load-btn-stop { background: #3a1a1a; border-color: var(--red); color: var(--red); }
1470
1470
  .load-btn-stop:hover:not(:disabled) { background: #4a2a2a; }
1471
+ .load-ai-prompt-wrap { display: flex; gap: 8px; align-items: flex-start; margin-top: 10px; }
1472
+ .load-ai-prompt {
1473
+ flex: 1; min-height: 60px; background: var(--bg); border: 1px solid var(--border);
1474
+ border-radius: 5px; color: var(--text); font-family: inherit; font-size: 12px;
1475
+ padding: 8px 10px; resize: vertical; outline: none; line-height: 1.5;
1476
+ }
1477
+ .load-ai-prompt:focus { border-color: #8b5cf6; }
1478
+ .load-btn-ai { background: #1e1a3a; border-color: #8b5cf6; color: #a78bfa; white-space: nowrap; }
1479
+ .load-btn-ai:hover:not(:disabled) { background: #2a2050; }
1480
+ .load-btn-ai:disabled { opacity: 0.5; cursor: not-allowed; }
1481
+ .load-ai-status { font-size: 11px; color: #a78bfa; margin-top: 5px; min-height: 16px; }
1482
+ .load-save-row { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
1483
+ .load-save-input {
1484
+ flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 5px;
1485
+ color: var(--text); font-size: 12px; padding: 5px 9px; outline: none;
1486
+ }
1487
+ .load-save-input:focus { border-color: var(--blue); }
1488
+ .load-scripts-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
1489
+ .load-script-item {
1490
+ display: flex; align-items: center; gap: 8px; padding: 8px 10px;
1491
+ background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
1492
+ transition: border-color 0.15s;
1493
+ }
1494
+ .load-script-item:hover { border-color: var(--dim); }
1495
+ .load-script-item-name { flex: 1; font-size: 13px; color: var(--text); font-weight: 500; }
1496
+ .load-script-item-meta { font-size: 11px; color: var(--muted); white-space: nowrap; }
1497
+ .load-script-item-actions { display: flex; gap: 5px; }
1498
+ .load-script-load-btn {
1499
+ padding: 3px 10px; border-radius: 4px; border: 1px solid var(--blue);
1500
+ background: #1a2a3a; color: var(--blue); font-size: 11px; cursor: pointer;
1501
+ }
1502
+ .load-script-load-btn:hover { background: #253a4a; }
1503
+ .load-script-del-btn {
1504
+ padding: 3px 8px; border-radius: 4px; border: 1px solid var(--border);
1505
+ background: transparent; color: var(--muted); font-size: 11px; cursor: pointer;
1506
+ }
1507
+ .load-script-del-btn:hover { border-color: var(--red); color: var(--red); }
1471
1508
  .load-charts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
1472
1509
  .load-chart-box { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; }
1473
1510
  .load-chart-label { font-size: 11px; color: var(--muted); margin-bottom: 6px; }
@@ -1623,6 +1660,10 @@ let loadLogLines = []; // log buffer for display
1623
1660
  let loadK6Available = null; // null = unchecked, true/false
1624
1661
  let loadK6Version = '';
1625
1662
  let loadScriptDraft = ''; // editable k6 script
1663
+ let loadAiPromptDraft = ''; // AI prompt textarea draft
1664
+ let loadAiGenerating = false; // waiting for agent to produce script
1665
+ let loadSavedScripts = []; // [{ name, date, script }]
1666
+ let loadScriptNameDraft = ''; // save name input draft
1626
1667
 
1627
1668
  function toggleObsHint(id) {
1628
1669
  document.getElementById(id).classList.toggle('open');
@@ -1710,6 +1751,7 @@ function switchMode(nextMode) {
1710
1751
  clearFeatureHash();
1711
1752
  }
1712
1753
  if (contextMode === 'load' && loadK6Available === null) { checkK6(); }
1754
+ if (contextMode === 'load') { loadRefreshScripts(); }
1713
1755
  setModeRoute(contextMode);
1714
1756
  document.getElementById('searchInput').value = searchQuery;
1715
1757
  document.getElementById('panel').classList.remove('open');
@@ -6917,9 +6959,45 @@ function renderLoad(c) {
6917
6959
  </div>
6918
6960
  </div>
6919
6961
 
6962
+ <div class="load-section">
6963
+ <div class="load-section-title">🤖 AI генерация скрипта</div>
6964
+ <div style="font-size:11px;color:var(--muted);margin-bottom:6px">Опиши сценарий — агент сгенерирует k6-скрипт и вставит его в редактор</div>
6965
+ <div class="load-ai-prompt-wrap">
6966
+ <textarea class="load-ai-prompt" id="loadAiPrompt" placeholder="Например: загрузка аудио .mp3 на /api/transcribe с полем language=ru, нужна авторизация Bearer-токеном, тестировать 20 VU 1 минуту&#10;&#10;Или: чат с RAG — POST /api/chat с body {message, knowledge_base_id, workspace_id}, stream:false, проверить что ответ содержит поле answer">${escapeHtml(loadAiPromptDraft)}</textarea>
6967
+ <button class="load-btn load-btn-ai" id="loadAiGenBtn" onclick="loadAiGenerateScript()" ${agentRunning ? 'disabled' : ''}>🤖 Сгенерировать<br>через AI</button>
6968
+ </div>
6969
+ <div class="load-ai-status" id="loadAiStatus">${loadAiGenerating ? '⏳ Агент генерирует скрипт… следи в терминале ↓' : ''}</div>
6970
+ </div>
6971
+
6920
6972
  <div class="load-section">
6921
6973
  <div class="load-section-title">k6 скрипт <span style="font-weight:400;color:var(--dim)">(редактируемый)</span></div>
6922
6974
  <textarea class="load-script-editor" id="loadScriptEditor" spellcheck="false">${escapeHtml(loadScriptDraft)}</textarea>
6975
+ <div class="load-save-row">
6976
+ <input class="load-save-input" id="loadScriptName" placeholder="Название скрипта, например: transcribe-audio-20vu" value="${escapeHtml(loadScriptNameDraft)}" />
6977
+ <button class="load-btn" style="white-space:nowrap" onclick="loadSaveScript()">💾 Сохранить скрипт</button>
6978
+ </div>
6979
+ </div>
6980
+
6981
+ <div class="load-section">
6982
+ <div class="load-section-title" style="display:flex;align-items:center;gap:8px">
6983
+ 📁 Сохранённые скрипты
6984
+ <span style="font-size:11px;font-weight:400;color:var(--muted)">${loadSavedScripts.length} шт.</span>
6985
+ <button class="load-btn" style="margin-left:auto;padding:2px 8px;font-size:11px" onclick="loadRefreshScripts()">↻ обновить</button>
6986
+ </div>
6987
+ ${loadSavedScripts.length === 0
6988
+ ? `<div style="font-size:12px;color:var(--muted);padding:8px 0">Нет сохранённых скриптов — напиши скрипт выше и нажми «💾 Сохранить»</div>`
6989
+ : `<div class="load-scripts-list">
6990
+ ${loadSavedScripts.map(s => `
6991
+ <div class="load-script-item">
6992
+ <div class="load-script-item-name">${escapeHtml(s.name)}</div>
6993
+ <div class="load-script-item-meta">${escapeHtml(s.date)}</div>
6994
+ <div class="load-script-item-actions">
6995
+ <button class="load-script-load-btn" onclick="loadLoadScript(${JSON.stringify(s.name)})">▶ Загрузить</button>
6996
+ <button class="load-script-del-btn" onclick="loadDeleteScript(${JSON.stringify(s.name)})">✕</button>
6997
+ </div>
6998
+ </div>`).join('')}
6999
+ </div>`
7000
+ }
6923
7001
  </div>
6924
7002
 
6925
7003
  ${(isRunning || isDone || hasErr) ? `<div class="load-section">
@@ -7061,6 +7139,139 @@ ${logs.slice(-50).join('\n')}
7061
7139
  } catch (e) { alert('Ошибка: ' + e.message); }
7062
7140
  }
7063
7141
 
7142
+ async function loadAiGenerateScript() {
7143
+ const promptEl = document.getElementById('loadAiPrompt');
7144
+ const userPrompt = promptEl ? promptEl.value.trim() : loadAiPromptDraft.trim();
7145
+ if (!userPrompt) { alert('Опиши сценарий нагрузочного теста в поле выше'); return; }
7146
+
7147
+ const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:3000';
7148
+ const vus = document.getElementById('loadVus')?.value || '10';
7149
+ const duration = document.getElementById('loadDuration')?.value || '30s';
7150
+
7151
+ const featureList = (D && D.features || []).map(f => `- ${f.label} (key: ${f.key})`).join('\n');
7152
+
7153
+ const task = `Сгенерируй k6 скрипт нагрузочного тестирования.
7154
+
7155
+ Сценарий от пользователя:
7156
+ ${userPrompt}
7157
+
7158
+ Параметры:
7159
+ - Base URL: ${baseUrl}
7160
+ - VUs: ${vus}
7161
+ - Duration: ${duration}
7162
+
7163
+ Доступные фичи проекта:
7164
+ ${featureList || '(нет данных)'}
7165
+
7166
+ Требования к скрипту:
7167
+ 1. Валидный JavaScript для k6 (import from 'k6/http', 'k6', 'k6/data' и т.д.)
7168
+ 2. export const options = { vus, duration, thresholds }
7169
+ 3. export default function() { ... } с проверками check()
7170
+ 4. Если нужна загрузка файла — использовать http.file() и open()
7171
+ 5. Если нужна авторизация — добавить заголовок Authorization
7172
+ 6. Добавить sleep() между запросами
7173
+ 7. ТОЛЬКО код скрипта, никаких пояснений снаружи
7174
+
7175
+ Сохрани скрипт СТРОГО в файл: .viberadar/load-script-generated.js
7176
+ Это важно — без этого файла кнопка "Загрузить скрипт" не сработает.`;
7177
+
7178
+ loadAiGenerating = true;
7179
+ loadAiPromptDraft = userPrompt;
7180
+ const statusEl = document.getElementById('loadAiStatus');
7181
+ const btnEl = document.getElementById('loadAiGenBtn');
7182
+ if (statusEl) statusEl.textContent = '⏳ Агент генерирует скрипт… следи в терминале ↓';
7183
+ if (btnEl) btnEl.disabled = true;
7184
+
7185
+ document.getElementById('agentPanel').classList.add('open');
7186
+ document.getElementById('termBtn').classList.add('term-active');
7187
+
7188
+ try {
7189
+ const r = await fetch('/api/run-agent', {
7190
+ method: 'POST',
7191
+ headers: { 'Content-Type': 'application/json' },
7192
+ body: JSON.stringify({ task: 'custom-prompt', prompt: task }),
7193
+ });
7194
+ if (!r.ok) {
7195
+ const d = await r.json().catch(() => ({}));
7196
+ loadAiGenerating = false;
7197
+ if (statusEl) statusEl.textContent = '❌ Ошибка запуска агента: ' + (d.error || r.status);
7198
+ if (btnEl) btnEl.disabled = false;
7199
+ }
7200
+ } catch (e) {
7201
+ loadAiGenerating = false;
7202
+ if (statusEl) statusEl.textContent = '❌ Ошибка: ' + e.message;
7203
+ if (btnEl) btnEl.disabled = false;
7204
+ }
7205
+ }
7206
+
7207
+ async function loadFetchAiScript() {
7208
+ try {
7209
+ const r = await fetch('/api/load/ai-script');
7210
+ if (!r.ok) return false;
7211
+ const d = await r.json();
7212
+ if (d.script) {
7213
+ loadScriptDraft = d.script;
7214
+ const ta = document.getElementById('loadScriptEditor');
7215
+ if (ta) { ta.value = d.script; ta.style.borderColor = '#8b5cf6'; setTimeout(() => { ta.style.borderColor = ''; }, 2000); }
7216
+ const statusEl = document.getElementById('loadAiStatus');
7217
+ if (statusEl) statusEl.textContent = '✅ Скрипт загружен в редактор';
7218
+ return true;
7219
+ }
7220
+ } catch {}
7221
+ return false;
7222
+ }
7223
+
7224
+ async function loadRefreshScripts() {
7225
+ try {
7226
+ const r = await fetch('/api/load/scripts');
7227
+ if (!r.ok) return;
7228
+ loadSavedScripts = await r.json();
7229
+ renderContent();
7230
+ } catch {}
7231
+ }
7232
+
7233
+ async function loadSaveScript() {
7234
+ const nameEl = document.getElementById('loadScriptName');
7235
+ const name = (nameEl ? nameEl.value : loadScriptNameDraft).trim();
7236
+ if (!name) { alert('Введи название скрипта'); nameEl && nameEl.focus(); return; }
7237
+ const taEl = document.getElementById('loadScriptEditor');
7238
+ const script = taEl ? taEl.value : loadScriptDraft;
7239
+ if (!script.trim()) { alert('Скрипт пустой'); return; }
7240
+ loadScriptNameDraft = name;
7241
+ try {
7242
+ const r = await fetch('/api/load/scripts', {
7243
+ method: 'POST',
7244
+ headers: { 'Content-Type': 'application/json' },
7245
+ body: JSON.stringify({ name, script }),
7246
+ });
7247
+ if (!r.ok) { const d = await r.json().catch(() => ({})); alert('Ошибка: ' + (d.error || r.status)); return; }
7248
+ await loadRefreshScripts();
7249
+ // flash save button
7250
+ const btn = document.querySelector('.load-save-row .load-btn');
7251
+ if (btn) { const orig = btn.textContent; btn.textContent = '✅ Сохранено'; btn.style.color = 'var(--green)'; setTimeout(() => { btn.textContent = orig; btn.style.color = ''; }, 1500); }
7252
+ } catch (e) { alert('Ошибка: ' + e.message); }
7253
+ }
7254
+
7255
+ async function loadLoadScript(name) {
7256
+ const s = loadSavedScripts.find(x => x.name === name);
7257
+ if (!s) return;
7258
+ loadScriptDraft = s.script;
7259
+ loadScriptNameDraft = s.name;
7260
+ const ta = document.getElementById('loadScriptEditor');
7261
+ if (ta) { ta.value = s.script; ta.style.borderColor = 'var(--blue)'; setTimeout(() => { ta.style.borderColor = ''; }, 1500); }
7262
+ const nameEl = document.getElementById('loadScriptName');
7263
+ if (nameEl) nameEl.value = s.name;
7264
+ }
7265
+
7266
+ async function loadDeleteScript(name) {
7267
+ if (!confirm(`Удалить скрипт «${name}»?`)) return;
7268
+ try {
7269
+ const r = await fetch('/api/load/scripts/' + encodeURIComponent(name), { method: 'DELETE' });
7270
+ if (!r.ok) { const d = await r.json().catch(() => ({})); alert('Ошибка: ' + (d.error || r.status)); return; }
7271
+ await loadRefreshScripts();
7272
+ } catch (e) { alert('Ошибка: ' + e.message); }
7273
+ }
7274
+
7064
7275
  function connectSSE() {
7065
7276
  const es = new EventSource('/api/events');
7066
7277
 
@@ -7197,6 +7408,18 @@ function connectSSE() {
7197
7408
  setDocActualizeStatus(null, null);
7198
7409
  docActualizeRunningKey = null;
7199
7410
  }
7411
+ // Auto-load AI-generated k6 script if we were waiting for one
7412
+ if (loadAiGenerating) {
7413
+ loadAiGenerating = false;
7414
+ loadFetchAiScript().then(ok => {
7415
+ if (!ok) {
7416
+ const statusEl = document.getElementById('loadAiStatus');
7417
+ if (statusEl) statusEl.textContent = '⚠️ Агент завершил, но файл не найден — возможно скрипт в терминале, скопируй вручную';
7418
+ }
7419
+ const btnEl = document.getElementById('loadAiGenBtn');
7420
+ if (btnEl) btnEl.disabled = false;
7421
+ });
7422
+ }
7200
7423
  renderContent();
7201
7424
  });
7202
7425
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viberadar",
3
- "version": "0.3.160",
3
+ "version": "0.3.162",
4
4
  "description": "Live module map with test coverage for vibecoding projects",
5
5
  "main": "./dist/cli.js",
6
6
  "bin": {