nothumanallowed 13.2.24 → 13.2.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.2.24",
3
+ "version": "13.2.26",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2562,19 +2562,36 @@ Example output:
2562
2562
  {"steps":[{"icon":"🔍","agent":"WebSearchAgent","label":"Cerca notizie","prompt":"Cerca le ultime notizie su intelligenza artificiale oggi"},{"icon":"📰","agent":"HERALD","label":"Analisi notizie","prompt":"Analizza le notizie trovate e crea un briefing esecutivo"},{"icon":"📊","agent":"CanvasAgent","label":"Dashboard HTML","prompt":"Crea una dashboard HTML visuale con i risultati"}]}`;
2563
2563
 
2564
2564
  try {
2565
- const planRaw = await callLLM(config, 'You are a JSON workflow planner. Respond only with valid JSON, no markdown, no explanation.', planPrompt, { max_tokens: 1200 });
2565
+ // Force thinking OFF for planner we need deterministic JSON, not reasoning chains
2566
+ const planConfig = Object.assign({}, config, { thinking: 'off' });
2567
+ const planRaw = await callLLM(planConfig, 'You are a JSON workflow planner. Output ONLY valid JSON. No thinking, no explanation, no markdown.', planPrompt, { max_tokens: 1500 });
2568
+ process.stderr.write('[STUDIO PLAN RAW] ' + planRaw.slice(0, 400) + '\n');
2566
2569
  let steps;
2567
2570
  try {
2568
- // Strip <think>...</think> blocks and markdown fences before parsing
2569
- let clean = planRaw.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
2570
- clean = clean.replace(/^```[\w]*\n?/,'').replace(/\n?```$/,'').trim();
2571
+ // Strip ALL <think>...</think> blocks (greedy handles nested/multiple)
2572
+ let clean = planRaw;
2573
+ let prev = '';
2574
+ while (prev !== clean) { prev = clean; clean = clean.replace(/<think>[\s\S]*?<\/think>/g, ''); }
2575
+ clean = clean.trim();
2576
+ // Strip markdown fences
2577
+ clean = clean.replace(/^```[\w]*\r?\n?/,'').replace(/\r?\n?```$/,'').trim();
2578
+ // Extract first complete JSON object
2571
2579
  const jsonMatch = clean.match(/\{[\s\S]*\}/);
2572
2580
  const parsed = JSON.parse(jsonMatch ? jsonMatch[0] : clean);
2573
2581
  steps = parsed.steps;
2574
- } catch {
2575
- sendJSON(res, 500, { error: 'Failed to parse workflow plan' });
2576
- logRequest(method, pathname, 500, Date.now() - start);
2577
- return;
2582
+ } catch (parseErr) {
2583
+ process.stderr.write('[STUDIO PLAN PARSE ERR] ' + parseErr.message + '\n');
2584
+ // Fallback: build a sensible default plan from the task keywords
2585
+ const hasEmail = /email|mail/i.test(task);
2586
+ const hasCalendar = /calendar|agenda|calendari/i.test(task);
2587
+ const hasSearch = /cerca|search|notizie|news/i.test(task);
2588
+ const hasCanvas = /html|dashboard|visua|report/i.test(task);
2589
+ steps = [];
2590
+ if (hasEmail) steps.push({icon:'📧',agent:'EmailAgent',label:'Controlla email',prompt:task});
2591
+ if (hasCalendar) steps.push({icon:'📅',agent:'CalendarAgent',label:'Rivedi calendario',prompt:task});
2592
+ if (hasSearch || steps.length === 0) steps.push({icon:'🔍',agent:'WebSearchAgent',label:'Ricerca web',prompt:task});
2593
+ steps.push({icon:'📰',agent:'HERALD',label:'Analisi e briefing',prompt:task});
2594
+ if (hasCanvas) steps.push({icon:'📊',agent:'CanvasAgent',label:'Dashboard HTML',prompt:task});
2578
2595
  }
2579
2596
  if (!Array.isArray(steps) || !steps.length) {
2580
2597
  sendJSON(res, 500, { error: 'Empty workflow plan' });
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '13.2.24';
8
+ export const VERSION = '13.2.26';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -2275,8 +2275,8 @@ function renderSettings(el) {
2275
2275
  ['profile-notes', 'Notes', 'Anything else agents should know about you'],
2276
2276
  ]) +
2277
2277
  '<div style="padding:12px 16px;margin-bottom:16px;background:var(--amberdim);border:1px solid var(--amber3);border-radius:8px"><span style="font-family:var(--term);color:var(--amber);font-size:13px;font-weight:700">NHA Free (Liara)</span><div style="font-size:11px;color:var(--dim);margin:4px 0 8px">Powered by Qwen3 32B. Free, no API key needed. Slower (5-15s).</div><button onclick="apiPost(\\x27/api/config\\x27,{key:\\x27provider\\x27,value:\\x27nha\\x27}).then(function(){location.reload()})" style="padding:6px 16px;background:var(--amber3);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-family:var(--mono);font-size:11px;font-weight:700">Use NHA Free</button></div>' +
2278
- settingsSection('language', 'Language / Lingua', 'Set the UI language. Applies on reload.', [
2279
- ['lang', 'Language', 'en / it / es / fr / de / pt / zh / ja / ar / hi / ru / nl / pl / tr / ko / sv / da / fi / no / cs'],
2278
+ settingsSection('language', 'Language / Lingua', 'Select the language used by all agents and Studio workflows.', [
2279
+ ['lang', 'Language', ''],
2280
2280
  ]) +
2281
2281
  settingsSection('llm', 'LLM Provider', 'Or use your own API key for faster, more capable responses.', [
2282
2282
  ['provider', 'Provider', 'nha (free) / anthropic / openai / gemini / deepseek / grok / mistral'],
@@ -2345,6 +2345,35 @@ function settingsSection(id, title, desc, fields) {
2345
2345
  h += '<option value="' + providers[pi].value + '"' + sel + '>' + providers[pi].label + '</option>';
2346
2346
  }
2347
2347
  h += '</select>';
2348
+ } else if (key === 'lang') {
2349
+ var langs = [
2350
+ {value:'it',label:'🇮🇹 Italiano'},
2351
+ {value:'en',label:'🇬🇧 English'},
2352
+ {value:'es',label:'🇪🇸 Español'},
2353
+ {value:'fr',label:'🇫🇷 Français'},
2354
+ {value:'de',label:'🇩🇪 Deutsch'},
2355
+ {value:'pt',label:'🇵🇹 Português'},
2356
+ {value:'nl',label:'🇳🇱 Nederlands'},
2357
+ {value:'pl',label:'🇵🇱 Polski'},
2358
+ {value:'ru',label:'🇷🇺 Русский'},
2359
+ {value:'zh',label:'🇨🇳 中文'},
2360
+ {value:'ja',label:'🇯🇵 日本語'},
2361
+ {value:'ko',label:'🇰🇷 한국어'},
2362
+ {value:'ar',label:'🇸🇦 العربية'},
2363
+ {value:'hi',label:'🇮🇳 हिन्दी'},
2364
+ {value:'tr',label:'🇹🇷 Türkçe'},
2365
+ {value:'sv',label:'🇸🇪 Svenska'},
2366
+ {value:'da',label:'🇩🇰 Dansk'},
2367
+ {value:'fi',label:'🇫🇮 Suomi'},
2368
+ {value:'cs',label:'🇨🇿 Čeština'},
2369
+ ];
2370
+ var curLang = currentVal || 'it';
2371
+ h += '<select style="width:100%;padding:8px 12px;font-size:13px;background:var(--bg);color:var(--fg);border:1px solid var(--border2);border-radius:var(--r)" data-config-key="lang" data-section="' + esc(id) + '">';
2372
+ for (var li=0;li<langs.length;li++) {
2373
+ var lsel = curLang === langs[li].value ? ' selected' : '';
2374
+ h += '<option value="' + langs[li].value + '"' + lsel + '>' + langs[li].label + '</option>';
2375
+ }
2376
+ h += '</select>';
2348
2377
  } else if (key === 'thinking') {
2349
2378
  // Dropdown for thinking toggle
2350
2379
  h += '<select style="width:100%;padding:8px 12px;font-size:13px;background:var(--bg);color:var(--fg);border:1px solid var(--border2);border-radius:var(--r)" data-config-key="thinking" data-section="' + esc(id) + '">' +
@@ -2404,13 +2433,22 @@ function saveSettingsSection(sectionId) {
2404
2433
  var allOk = results.every(function(r) { return r; });
2405
2434
  if (statusEl) {
2406
2435
  if (allOk) {
2407
- statusEl.textContent = 'Saved!';
2436
+ if (sectionId === 'language') {
2437
+ var langNames = {it:'Italiano',en:'English',es:'Español',fr:'Français',de:'Deutsch',pt:'Português',nl:'Nederlands',pl:'Polski',ru:'Русский',zh:'中文',ja:'日本語',ko:'한국어',ar:'العربية',hi:'हिन्दी',tr:'Türkçe',sv:'Svenska',da:'Dansk',fi:'Suomi',cs:'Čeština'};
2438
+ try {
2439
+ var cfg2 = JSON.parse(localStorage.getItem('nha_config_cache') || '{}');
2440
+ var ln = langNames[cfg2.lang] || cfg2.lang || 'Italian';
2441
+ statusEl.textContent = '✓ Language set to ' + ln + '. All agents and Studio will now respond in ' + ln + '.';
2442
+ } catch(e) { statusEl.textContent = '✓ Saved!'; }
2443
+ } else {
2444
+ statusEl.textContent = 'Saved!';
2445
+ }
2408
2446
  statusEl.style.color = 'var(--green)';
2409
2447
  } else {
2410
2448
  statusEl.textContent = 'Some fields failed to save.';
2411
2449
  statusEl.style.color = 'var(--red)';
2412
2450
  }
2413
- setTimeout(function() { statusEl.textContent = ''; }, 3000);
2451
+ setTimeout(function() { statusEl.textContent = ''; }, 4000);
2414
2452
  }
2415
2453
  });
2416
2454
  }
@@ -2711,6 +2749,104 @@ function stopVoiceInput() {
2711
2749
  }
2712
2750
 
2713
2751
  // ---- STUDIO ----
2752
+ // ── i18n — UI string translations ────────────────────────────────────────────
2753
+ var I18N = {
2754
+ en: {
2755
+ chat:'Chat', studio:'Studio', settings:'Settings', agents:'Agents',
2756
+ run:'▶ Run', stop:'⬛ Stop', reset:'New workflow',
2757
+ placeholder_chat:'Message NHA... (Enter to send)',
2758
+ placeholder_studio:'Describe what you want to accomplish... (Ctrl+Enter to run)',
2759
+ planning:'Planning...', workflow_complete:'Workflow complete.',
2760
+ workflow_stopped:'Workflow stopped by user.',
2761
+ canvas_open:'Open Canvas', canvas_generated:'HTML Dashboard generated in Canvas panel.',
2762
+ saved:'Saved!', lang_set:'Language set to',
2763
+ agents_respond:'All agents will respond in',
2764
+ examples:'Examples', recent_sessions:'Recent sessions',
2765
+ restore:'Restore', delete:'Delete',
2766
+ send:'Send', attach:'Attach',
2767
+ settings_save:'Save',
2768
+ no_output:'(no output)', done:'(done)',
2769
+ token_label:'Tokens',
2770
+ },
2771
+ it: {
2772
+ chat:'Chat', studio:'Studio', settings:'Impostazioni', agents:'Agenti',
2773
+ run:'▶ Avvia', stop:'⬛ Ferma', reset:'Nuovo workflow',
2774
+ placeholder_chat:'Scrivi a NHA... (Invio per inviare)',
2775
+ placeholder_studio:'Descrivi cosa vuoi fare... (Ctrl+Invio per avviare)',
2776
+ planning:'Pianificazione...', workflow_complete:'Workflow completato.',
2777
+ workflow_stopped:'Workflow fermato dall\'utente.',
2778
+ canvas_open:'Apri Canvas', canvas_generated:'Dashboard HTML generata nel pannello Canvas.',
2779
+ saved:'Salvato!', lang_set:'Lingua impostata su',
2780
+ agents_respond:'Tutti gli agenti risponderanno in',
2781
+ examples:'Esempi', recent_sessions:'Sessioni recenti',
2782
+ restore:'Ripristina', delete:'Elimina',
2783
+ send:'Invia', attach:'Allega',
2784
+ settings_save:'Salva',
2785
+ no_output:'(nessun output)', done:'(completato)',
2786
+ token_label:'Token',
2787
+ },
2788
+ es: {
2789
+ chat:'Chat', studio:'Studio', settings:'Configuración', agents:'Agentes',
2790
+ run:'▶ Ejecutar', stop:'⬛ Detener', reset:'Nuevo flujo',
2791
+ placeholder_chat:'Mensaje a NHA... (Enter para enviar)',
2792
+ placeholder_studio:'Describe lo que quieres hacer... (Ctrl+Enter para ejecutar)',
2793
+ planning:'Planificando...', workflow_complete:'Flujo completado.',
2794
+ workflow_stopped:'Flujo detenido por el usuario.',
2795
+ canvas_open:'Abrir Canvas', canvas_generated:'Panel HTML generado en Canvas.',
2796
+ saved:'¡Guardado!', lang_set:'Idioma establecido en',
2797
+ agents_respond:'Todos los agentes responderán en',
2798
+ examples:'Ejemplos', recent_sessions:'Sesiones recientes',
2799
+ restore:'Restaurar', delete:'Eliminar',
2800
+ send:'Enviar', attach:'Adjuntar',
2801
+ settings_save:'Guardar',
2802
+ no_output:'(sin salida)', done:'(hecho)',
2803
+ token_label:'Tokens',
2804
+ },
2805
+ fr: {
2806
+ chat:'Chat', studio:'Studio', settings:'Paramètres', agents:'Agents',
2807
+ run:'▶ Lancer', stop:'⬛ Arrêter', reset:'Nouveau flux',
2808
+ placeholder_chat:'Message à NHA... (Entrée pour envoyer)',
2809
+ placeholder_studio:'Décrivez ce que vous voulez faire... (Ctrl+Entrée pour lancer)',
2810
+ planning:'Planification...', workflow_complete:'Flux terminé.',
2811
+ workflow_stopped:'Flux arrêté par l\'utilisateur.',
2812
+ canvas_open:'Ouvrir Canvas', canvas_generated:'Tableau de bord HTML généré dans Canvas.',
2813
+ saved:'Sauvegardé!', lang_set:'Langue définie sur',
2814
+ agents_respond:'Tous les agents répondront en',
2815
+ examples:'Exemples', recent_sessions:'Sessions récentes',
2816
+ restore:'Restaurer', delete:'Supprimer',
2817
+ send:'Envoyer', attach:'Joindre',
2818
+ settings_save:'Sauvegarder',
2819
+ no_output:'(aucune sortie)', done:'(terminé)',
2820
+ token_label:'Tokens',
2821
+ },
2822
+ de: {
2823
+ chat:'Chat', studio:'Studio', settings:'Einstellungen', agents:'Agenten',
2824
+ run:'▶ Starten', stop:'⬛ Stopp', reset:'Neuer Workflow',
2825
+ placeholder_chat:'Nachricht an NHA... (Enter zum Senden)',
2826
+ placeholder_studio:'Beschreibe was du tun möchtest... (Strg+Enter zum Starten)',
2827
+ planning:'Planung...', workflow_complete:'Workflow abgeschlossen.',
2828
+ workflow_stopped:'Workflow vom Benutzer gestoppt.',
2829
+ canvas_open:'Canvas öffnen', canvas_generated:'HTML-Dashboard im Canvas-Panel generiert.',
2830
+ saved:'Gespeichert!', lang_set:'Sprache auf',
2831
+ agents_respond:'Alle Agenten antworten auf',
2832
+ examples:'Beispiele', recent_sessions:'Letzte Sitzungen',
2833
+ restore:'Wiederherstellen', delete:'Löschen',
2834
+ send:'Senden', attach:'Anhängen',
2835
+ settings_save:'Speichern',
2836
+ no_output:'(keine Ausgabe)', done:'(erledigt)',
2837
+ token_label:'Token',
2838
+ },
2839
+ };
2840
+ // Fallback to 'en' for unmapped languages
2841
+ function t(key) {
2842
+ try {
2843
+ var cfg = JSON.parse(localStorage.getItem('nha_config_cache') || '{}');
2844
+ var lang = (cfg.lang || 'it').slice(0,2);
2845
+ var map = I18N[lang] || I18N.en;
2846
+ return map[key] || I18N.en[key] || key;
2847
+ } catch(e) { return I18N.en[key] || key; }
2848
+ }
2849
+
2714
2850
  var studioState = {
2715
2851
  task: '',
2716
2852
  nodes: [], // [{icon,agent,label,status:'waiting'|'running'|'done'|'error'}]
@@ -2727,10 +2863,10 @@ function stopStudio() {
2727
2863
  if (studioAbortController) { try { studioAbortController.abort(); } catch(e) {} studioAbortController = null; }
2728
2864
  studioState.running = false;
2729
2865
  var btn = document.getElementById('studioRunBtn');
2730
- if (btn) { btn.disabled = false; btn.textContent = '▶ Run'; }
2866
+ if (btn) { btn.disabled = false; btn.textContent = t('run'); }
2731
2867
  var stopBtn = document.getElementById('studioStopBtn');
2732
2868
  if (stopBtn) stopBtn.style.display = 'none';
2733
- studioLog('Studio', '⬛', 'Workflow stopped by user.', 'system');
2869
+ studioLog('Studio', '⬛', t('workflow_stopped'), 'system');
2734
2870
  // Mark any still-running nodes as error
2735
2871
  studioState.nodes.forEach(function(n) { if (n.status === 'running') n.status = 'error'; });
2736
2872
  renderStudioNodes();
@@ -2837,7 +2973,7 @@ function renderStudioResult() {
2837
2973
  el.style.display = 'block';
2838
2974
  var isHtml = studioState.result.trimStart().startsWith('<');
2839
2975
  var body = isHtml
2840
- ? '<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap"><span style="color:var(--dim);font-size:13px">&#10003; Dashboard HTML generata nel pannello Canvas.</span><button onclick="openCanvasPanel()" style="padding:6px 14px;background:var(--greendim);border:1px solid var(--green3);border-radius:8px;color:var(--green);font-size:12px;cursor:pointer;font-weight:700">&#x25A3; Apri Canvas</button></div>'
2976
+ ? '<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap"><span style="color:var(--dim);font-size:13px">&#10003; ' + t('canvas_generated') + '</span><button onclick="openCanvasPanel()" style="padding:6px 14px;background:var(--greendim);border:1px solid var(--green3);border-radius:8px;color:var(--green);font-size:12px;cursor:pointer;font-weight:700">&#x25A3; ' + t('canvas_open') + '</button></div>'
2841
2977
  : '<div class="md-body">' + renderMd(studioState.result) + '</div>';
2842
2978
  el.innerHTML = '<div class="studio-result__title">&#10003; Workflow completato</div>' + body;
2843
2979
  }
@@ -2868,7 +3004,7 @@ async function runStudio() {
2868
3004
  studioAbortController = new AbortController();
2869
3005
 
2870
3006
  var btn = document.getElementById('studioRunBtn');
2871
- if (btn) { btn.disabled = true; btn.textContent = 'Planning...'; }
3007
+ if (btn) { btn.disabled = true; btn.textContent = t('planning'); }
2872
3008
  var stopBtn = document.getElementById('studioStopBtn');
2873
3009
  if (stopBtn) stopBtn.style.display = '';
2874
3010
 
@@ -2931,7 +3067,7 @@ async function runStudio() {
2931
3067
  // Final result is the last step's output
2932
3068
  studioState.result = context;
2933
3069
  renderStudioResult();
2934
- studioLog('Studio', '&#127881;', 'Workflow complete.', 'system');
3070
+ studioLog('Studio', '&#127881;', t('workflow_complete'), 'system');
2935
3071
 
2936
3072
  // Save session to localStorage for reuse in Chat
2937
3073
  saveStudioSession(task, studioState.nodes, studioState.log, context);
@@ -2977,7 +3113,7 @@ function renderStudioSessionsBar() {
2977
3113
  var sessions = loadStudioSessions();
2978
3114
  if (!sessions.length) { el.style.display = 'none'; return; }
2979
3115
  el.style.display = 'block';
2980
- el.innerHTML = '<div style="font-size:10px;color:var(--dim);margin-bottom:8px;text-transform:uppercase;letter-spacing:1px">Recent sessions</div>' +
3116
+ el.innerHTML = '<div style="font-size:10px;color:var(--dim);margin-bottom:8px;text-transform:uppercase;letter-spacing:1px">' + t('recent_sessions') + '</div>' +
2981
3117
  '<div style="max-height:220px;overflow-y:auto;padding-right:4px">' +
2982
3118
  sessions.map(function(s,i) {
2983
3119
  return '<div class="studio-session-item">' +
@@ -3042,8 +3178,13 @@ var studioTokens = {in: 0, out: 0};
3042
3178
  function studioAddTokens(inp, out) {
3043
3179
  studioTokens.in += (inp||0);
3044
3180
  studioTokens.out += (out||0);
3181
+ studioUpdateTokenBar();
3182
+ }
3183
+ function studioUpdateTokenBar() {
3045
3184
  var el = document.getElementById('studioTokenBar');
3046
- if (el) el.textContent = 'Tokens: ' + studioTokens.in + ' in / ' + studioTokens.out + ' out';
3185
+ if (!el) return;
3186
+ if (studioTokens.in === 0 && studioTokens.out === 0) { el.textContent = ''; return; }
3187
+ el.textContent = '⬆ ' + studioTokens.in.toLocaleString() + ' in ⬇ ' + studioTokens.out.toLocaleString() + ' out';
3047
3188
  }
3048
3189
 
3049
3190
  function runStudioStep(idx, node, task, context, stepDef, signal) {
@@ -3104,6 +3245,7 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
3104
3245
  if (scb) scb.style.display = '';
3105
3246
  }
3106
3247
  if (ev.usage) { studioAddTokens(ev.usage.input||0, ev.usage.output||0); }
3248
+ else if (ev.token && !isStatus) { studioTokens.out += Math.ceil(ev.token.length/4); studioUpdateTokenBar(); }
3107
3249
  if (ev.done) { resolve({output: output || '(no output)', canvas: canvasHtml}); return; }
3108
3250
  if (ev.error) { resolve({error: ev.error}); return; }
3109
3251
  } catch(e) {}
@@ -3218,15 +3360,15 @@ function renderStudio(el) {
3218
3360
  // ── AUTO MODE ──
3219
3361
  '<div id="studioAutoMode">' +
3220
3362
  '<div style="margin-bottom:10px">' +
3221
- '<div style="font-size:10px;color:var(--dim);margin-bottom:6px;text-transform:uppercase;letter-spacing:1px">Examples</div>' +
3363
+ '<div style="font-size:10px;color:var(--dim);margin-bottom:6px;text-transform:uppercase;letter-spacing:1px">' + t('examples') + '</div>' +
3222
3364
  examplesHtml +
3223
3365
  '</div>' +
3224
3366
  '<div class="studio-input-row">' +
3225
- '<textarea id="studioTaskInput" placeholder="Describe what you want to accomplish... (Ctrl+Enter to run)" onkeydown="if(event.key===\\x27Enter\\x27&&(event.ctrlKey||event.metaKey)){runStudio();event.preventDefault()}">' + esc(studioState.task) + '</textarea>' +
3367
+ '<textarea id="studioTaskInput" placeholder="' + t('placeholder_studio') + '" onkeydown="if(event.key===\\x27Enter\\x27&&(event.ctrlKey||event.metaKey)){runStudio();event.preventDefault()}">' + esc(studioState.task) + '</textarea>' +
3226
3368
  '<div style="display:flex;gap:6px">' +
3227
- '<button id="studioRunBtn" class="studio-run-btn" onclick="runStudio()" style="flex:1" ' + (studioState.running ? 'disabled' : '') + '>&#9654; Run</button>' +
3228
- '<button id="studioStopBtn" onclick="stopStudio()" title="Stop workflow" style="padding:8px 14px;background:#7f1d1d;border:1px solid #ef4444;border-radius:8px;color:#ef4444;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap;' + (studioState.running ? '' : 'display:none') + '">&#9632; Stop</button>' +
3229
- '<button onclick="studioReset()" title="New workflow" style="padding:8px 12px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);cursor:pointer;font-size:16px;line-height:1" ' + (studioState.running ? 'disabled' : '') + '>&#8635;</button>' +
3369
+ '<button id="studioRunBtn" class="studio-run-btn" onclick="runStudio()" style="flex:1" ' + (studioState.running ? 'disabled' : '') + '>' + t('run') + '</button>' +
3370
+ '<button id="studioStopBtn" onclick="stopStudio()" title="' + t('stop') + '" style="padding:8px 14px;background:#7f1d1d;border:1px solid #ef4444;border-radius:8px;color:#ef4444;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap;' + (studioState.running ? '' : 'display:none') + '">&#9632; ' + t('stop') + '</button>' +
3371
+ '<button onclick="studioReset()" title="' + t('reset') + '" style="padding:8px 12px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);cursor:pointer;font-size:16px;line-height:1" ' + (studioState.running ? 'disabled' : '') + '>&#8635;</button>' +
3230
3372
  '</div>' +
3231
3373
  '</div>' +
3232
3374
  '</div>' +