@goodandready/dsh-goal 0.1.7 → 0.1.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
@@ -182,6 +182,32 @@ window.__ModuleLoader__.load({
182
182
  display: flex;
183
183
  justify-content: center;
184
184
  }
185
+ .dsh-goal-dock.dsh-goal-dock-quick {
186
+ justify-content: flex-start;
187
+ margin-bottom: 4px;
188
+ }
189
+ .dsh-goal-quicklaunch-btn {
190
+ appearance: none;
191
+ font: inherit;
192
+ background: var(--dsw-alias-bg-layer-2);
193
+ border: 1px solid var(--dsw-alias-border-l2);
194
+ border-radius: 999px;
195
+ padding: 4px 11px;
196
+ color: var(--dsw-alias-label-secondary);
197
+ font-size: 12px;
198
+ font-weight: 500;
199
+ cursor: pointer;
200
+ display: inline-flex;
201
+ align-items: center;
202
+ gap: 6px;
203
+ transition: all 0.15s ease;
204
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
205
+ }
206
+ .dsh-goal-quicklaunch-btn:hover {
207
+ background: var(--dsw-alias-bg-layer-3);
208
+ border-color: var(--dsw-alias-label-tertiary);
209
+ color: var(--dsw-alias-label-primary);
210
+ }
185
211
  .dsh-goal-banner {
186
212
  box-sizing: border-box;
187
213
  width: 100%;
@@ -382,7 +408,7 @@ window.__ModuleLoader__.load({
382
408
  }
383
409
  .dsh-goal-stat-grid {
384
410
  display: grid;
385
- grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
411
+ grid-template-columns: repeat(auto-fit, minmax(95px, 1fr));
386
412
  gap: 10px;
387
413
  }
388
414
  .dsh-goal-stat-box {
@@ -395,13 +421,17 @@ window.__ModuleLoader__.load({
395
421
  gap: 2px;
396
422
  }
397
423
  .dsh-goal-stat-val {
398
- font-size: 16px;
424
+ font-size: 15px;
399
425
  font-weight: 700;
400
426
  color: var(--dsw-alias-label-primary);
427
+ overflow: hidden;
428
+ text-overflow: ellipsis;
429
+ white-space: nowrap;
401
430
  }
402
431
  .dsh-goal-stat-lbl {
403
432
  font-size: 11px;
404
433
  color: var(--dsw-alias-label-secondary);
434
+ white-space: nowrap;
405
435
  }
406
436
  .dsh-goal-milestone-item {
407
437
  display: flex;
@@ -421,6 +451,17 @@ window.__ModuleLoader__.load({
421
451
  justify-content: flex-end;
422
452
  align-items: center;
423
453
  gap: 10px;
454
+ flex-wrap: wrap;
455
+ }
456
+ .dsh-goal-btn-copy {
457
+ background: var(--dsw-alias-bg-layer-2);
458
+ border: 1px solid var(--dsw-alias-border-l2);
459
+ color: var(--dsw-alias-label-primary);
460
+ }
461
+ .dsh-goal-btn-copy.copied {
462
+ background: rgba(16, 185, 129, 0.12);
463
+ border-color: var(--dsw-alias-state-success-primary);
464
+ color: var(--dsw-alias-state-success-primary);
424
465
  }
425
466
 
426
467
  /* Settings Card (clinebot pattern) */
@@ -658,12 +699,77 @@ window.__ModuleLoader__.load({
658
699
  } catch (_) {}
659
700
  }
660
701
 
702
+ // --- ГЕНЕРАТОР MARKDOWN-ОТЧЕТА ---
703
+ function generateMarkdownReport(state) {
704
+ if (!state) return '';
705
+ const title = state.title || 'Goal Report';
706
+ const status = state.state === 'COMPLETED' ? 'COMPLETED' : state.state;
707
+ const elapsed = state.formattedElapsed || '0s';
708
+ const iter = `${state.iterationsCount || 0}/${state.maxIterations || 25}`;
709
+ const totalTokens = state.tokensUsage?.totalTokens || 0;
710
+ const promptTokens = state.tokensUsage?.promptTokens || 0;
711
+ const compTokens = state.tokensUsage?.completionTokens || 0;
712
+
713
+ let md = `# 🎯 Goal Report: ${title}\n\n`;
714
+ md += `**Status:** \`${status}\` | **Duration:** \`${elapsed}\` | **Iterations:** \`${iter}\`\n`;
715
+ if (totalTokens > 0) {
716
+ md += `**Tokens:** \`${totalTokens.toLocaleString()}\` (Prompt: \`${promptTokens.toLocaleString()}\`, Completion: \`${compTokens.toLocaleString()}\`)\n`;
717
+ }
718
+ md += '\n';
719
+
720
+ if (state.description) {
721
+ md += `### Description\n${state.description}\n\n`;
722
+ }
723
+
724
+ if (state.resultSummary) {
725
+ md += `### Summary & Deliverables\n${state.resultSummary}\n\n`;
726
+ }
727
+
728
+ const milestones = state.milestones || [];
729
+ if (milestones.length > 0) {
730
+ md += `### Milestones\n| # | Status | Title | Notes |\n|---|---|---|---|\n`;
731
+ milestones.forEach((m, idx) => {
732
+ const mark = m.status === 'completed' ? '✅ Done' : m.status === 'in_progress' ? '🔄 In Progress' : '⏳ Pending';
733
+ const cleanTitle = (m.title || '').replace(/\|/g, '\\|');
734
+ const cleanNotes = (m.notes || '').replace(/\|/g, '\\|');
735
+ md += `| ${idx + 1} | ${mark} | ${cleanTitle} | ${cleanNotes || '—'} |\n`;
736
+ });
737
+ md += '\n';
738
+ }
739
+
740
+ md += `*Generated by DSH Goal Engine at ${new Date().toISOString()}*\n`;
741
+ return md;
742
+ }
743
+
661
744
  // --- МОДАЛЬНОЕ ОКНО ДЕТАЛЕЙ (MODAL DETAILS) ---
662
745
  function GoalDetailsModal({ state, onClose, onAction, t }) {
663
746
  if (!state) return null;
664
747
 
665
748
  const milestones = state.milestones || [];
666
749
  const isCompleted = state.state === 'COMPLETED';
750
+ const [copied, setCopied] = useState(false);
751
+
752
+ const totalTokens = state.tokensUsage?.totalTokens || 0;
753
+ const promptTokens = state.tokensUsage?.promptTokens || 0;
754
+ const compTokens = state.tokensUsage?.completionTokens || 0;
755
+ const tokensFormatted = totalTokens > 0
756
+ ? (totalTokens >= 1000 ? `${(totalTokens / 1000).toFixed(1)}k` : `${totalTokens}`)
757
+ : '—';
758
+ const tokensTooltip = totalTokens > 0
759
+ ? `Prompt: ${promptTokens.toLocaleString()} | Completion: ${compTokens.toLocaleString()} | Total: ${totalTokens.toLocaleString()}`
760
+ : 'Токены ещё не зафиксированы';
761
+
762
+ const handleCopyReport = () => {
763
+ const md = generateMarkdownReport(state);
764
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
765
+ navigator.clipboard.writeText(md).then(() => {
766
+ setCopied(true);
767
+ setTimeout(() => setCopied(false), 2500);
768
+ }).catch((err) => {
769
+ console.warn('[dsh-goal] Clipboard copy failed:', err);
770
+ });
771
+ }
772
+ };
667
773
 
668
774
  return React.createElement(
669
775
  'div',
@@ -683,14 +789,14 @@ window.__ModuleLoader__.load({
683
789
  isCompleted
684
790
  ? React.createElement(IconCheck, { size: 18, className: 'cb-badge-ok' })
685
791
  : React.createElement(IconTarget, { size: 18 }),
686
- isCompleted ? (t('modalTitleCompleted') || 'Цель выполнена: план и результаты') : (t('modalTitle') || 'План и статус цели'),
792
+ isCompleted ? (t('modalTitleCompleted') || 'Goal completed: plan and results') : (t('modalTitle') || 'Goal plan and status'),
687
793
  ),
688
794
  React.createElement(
689
795
  'button',
690
796
  {
691
797
  className: 'dsh-goal-btn icon-only close',
692
798
  onClick: onClose,
693
- title: t('close') || 'Закрыть',
799
+ title: t('close') || 'Close',
694
800
  },
695
801
  '✕',
696
802
  ),
@@ -711,27 +817,39 @@ window.__ModuleLoader__.load({
711
817
  'div',
712
818
  { className: 'dsh-goal-stat-box' },
713
819
  React.createElement('span', { className: 'dsh-goal-stat-val' }, state.formattedElapsed || '0s'),
714
- React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('time') || 'Время работы'),
820
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('time') || 'Running time'),
821
+ ),
822
+ React.createElement(
823
+ 'div',
824
+ { className: 'dsh-goal-stat-box' },
825
+ React.createElement('span', { className: 'dsh-goal-stat-val' }, state.formattedETA || (isCompleted ? (t('done') || 'Done') : '—')),
826
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('eta') || 'ETA'),
827
+ ),
828
+ React.createElement(
829
+ 'div',
830
+ { className: 'dsh-goal-stat-box', title: tokensTooltip },
831
+ React.createElement('span', { className: 'dsh-goal-stat-val' }, tokensFormatted),
832
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('tokens') || 'Tokens'),
715
833
  ),
716
834
  React.createElement(
717
835
  'div',
718
836
  { className: 'dsh-goal-stat-box' },
719
837
  React.createElement('span', { className: 'dsh-goal-stat-val' }, `${state.iterationsCount || 0}/${state.maxIterations || 25}`),
720
- React.createElement('span', { className: 'dsh-goal-stat-lbl' }, 'Итерации'),
838
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('iterations') || 'Iterations'),
721
839
  ),
722
840
  React.createElement(
723
841
  'div',
724
842
  { className: 'dsh-goal-stat-box' },
725
843
  React.createElement('span', { className: 'dsh-goal-stat-val' }, `${state.progressPercent || 0}%`),
726
- React.createElement('span', { className: 'dsh-goal-stat-lbl' }, 'Прогресс'),
844
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('progress') || 'Progress'),
727
845
  ),
728
846
  ),
729
847
  React.createElement(
730
848
  'div',
731
849
  { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 4 } },
732
- React.createElement('div', { style: { fontWeight: 600, fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, t('milestones') || 'План работ:'),
850
+ React.createElement('div', { style: { fontWeight: 600, fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, t('milestones') || 'Plan of work:'),
733
851
  milestones.length === 0
734
- ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 12, padding: '8px 0' } }, t('noMilestones') || 'Агент формирует план работ...')
852
+ ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 12, padding: '8px 0' } }, t('noMilestones') || 'Agent is preparing the plan of work...')
735
853
  : milestones.map((m, i) =>
736
854
  React.createElement(
737
855
  'div',
@@ -760,6 +878,17 @@ window.__ModuleLoader__.load({
760
878
  React.createElement(
761
879
  'div',
762
880
  { className: 'dsh-goal-modal-foot' },
881
+ isCompleted
882
+ ? React.createElement(
883
+ 'button',
884
+ {
885
+ className: `dsh-goal-btn dsh-goal-btn-copy${copied ? ' copied' : ''}`,
886
+ onClick: handleCopyReport,
887
+ title: t('copyReport') || 'Скопировать отчёт в Markdown',
888
+ },
889
+ copied ? (t('reportCopied') || '✅ Скопировано!') : (t('copyReport') || '📋 Скопировать отчёт в Markdown'),
890
+ )
891
+ : null,
763
892
  isCompleted
764
893
  ? React.createElement(
765
894
  'button',
@@ -786,6 +915,98 @@ window.__ModuleLoader__.load({
786
915
  );
787
916
  }
788
917
 
918
+ // --- МОДАЛЬНОЕ ОКНО БЫСТРОГО ЗАПУСКА ЦЕЛИ (QUICK LAUNCH MODAL) ---
919
+ function QuickLaunchModal({ onClose, onStart, t }) {
920
+ const [title, setTitle] = useState('');
921
+ const inputRef = useRef(null);
922
+
923
+ useEffect(() => {
924
+ if (inputRef.current) inputRef.current.focus();
925
+ }, []);
926
+
927
+ const handleSubmit = (e) => {
928
+ if (e) e.preventDefault();
929
+ const trimmed = title.trim();
930
+ if (trimmed) {
931
+ onStart(trimmed);
932
+ }
933
+ };
934
+
935
+ return React.createElement(
936
+ 'div',
937
+ { className: 'dsh-goal-modal-overlay', onClick: onClose },
938
+ React.createElement(
939
+ 'div',
940
+ {
941
+ className: 'dsh-goal-modal',
942
+ style: { maxWidth: 480 },
943
+ onClick: (e) => e.stopPropagation(),
944
+ },
945
+ React.createElement(
946
+ 'div',
947
+ { className: 'dsh-goal-modal-head' },
948
+ React.createElement(
949
+ 'div',
950
+ { className: 'dsh-goal-modal-title' },
951
+ React.createElement(IconTarget, { size: 18 }),
952
+ t('quickLaunchTitle') || 'Быстрый запуск цели',
953
+ ),
954
+ React.createElement(
955
+ 'button',
956
+ { className: 'dsh-goal-btn icon-only close', onClick: onClose, title: t('close') || 'Закрыть' },
957
+ '✕',
958
+ ),
959
+ ),
960
+ React.createElement(
961
+ 'form',
962
+ { onSubmit: handleSubmit },
963
+ React.createElement(
964
+ 'div',
965
+ { className: 'dsh-goal-modal-body' },
966
+ React.createElement(
967
+ 'div',
968
+ { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)', marginBottom: 8 } },
969
+ t('quickLaunchDesc') || 'Сформулируйте задачу для агента в автономном режиме:',
970
+ ),
971
+ React.createElement('input', {
972
+ ref: inputRef,
973
+ type: 'text',
974
+ className: 'dsh-goal-card-input',
975
+ style: { width: '100%', boxSizing: 'border-box', fontSize: 13, padding: '8px 10px' },
976
+ placeholder: t('quickLaunchPlaceholder') || 'Например: Реализовать экспорт отчетов и покрыть тестами...',
977
+ value: title,
978
+ onChange: (e) => setTitle(e.target.value),
979
+ onKeyDown: (e) => {
980
+ if (e.key === 'Enter' && !e.shiftKey) {
981
+ e.preventDefault();
982
+ handleSubmit();
983
+ }
984
+ },
985
+ }),
986
+ ),
987
+ React.createElement(
988
+ 'div',
989
+ { className: 'dsh-goal-modal-foot' },
990
+ React.createElement(
991
+ 'button',
992
+ { type: 'button', className: 'dsh-goal-btn', onClick: onClose },
993
+ t('close') || 'Отмена',
994
+ ),
995
+ React.createElement(
996
+ 'button',
997
+ {
998
+ type: 'submit',
999
+ className: 'dsh-goal-btn dsh-goal-btn-primary',
1000
+ disabled: !title.trim(),
1001
+ },
1002
+ t('startGoal') || 'Запустить цель',
1003
+ ),
1004
+ ),
1005
+ ),
1006
+ ),
1007
+ );
1008
+ }
1009
+
789
1010
  function sessionIdOf(ctx, props) {
790
1011
  try {
791
1012
  if (props?.session?.id) return String(props.session.id);
@@ -809,18 +1030,39 @@ window.__ModuleLoader__.load({
809
1030
  const sid = sessionIdOf(ctx, props);
810
1031
  const [state, setState] = useState(null);
811
1032
  const [isModalOpen, setIsModalOpen] = useState(false);
1033
+ const [isQuickLaunchOpen, setIsQuickLaunchOpen] = useState(false);
812
1034
  const [now, setNow] = useState(() => Date.now());
813
1035
  const stateRef = useRef(null);
814
1036
  stateRef.current = state;
815
1037
  const lastStateRef = useRef(null);
816
1038
 
817
- const t = (key) => {
1039
+ const resolveLocale = () => {
1040
+ let active = 'en';
818
1041
  if (ctx?.locale?.getSnapshot) {
819
- const snap = ctx.locale.getSnapshot();
820
- const active = snap?.active || 'ru';
821
- return LOCALES[active]?.[key] || LOCALES.ru[key] || LOCALES.en[key] || key;
1042
+ const snapLoc = ctx.locale.getSnapshot();
1043
+ if (snapLoc?.active) {
1044
+ const code = snapLoc.active.toLowerCase();
1045
+ if (code.startsWith('zh')) active = 'zh';
1046
+ else if (code.startsWith('ru')) active = 'ru';
1047
+ else active = 'en';
1048
+ }
1049
+ } else if (typeof navigator !== 'undefined' && navigator.language) {
1050
+ const code = navigator.language.toLowerCase();
1051
+ if (code.startsWith('zh')) active = 'zh';
1052
+ else if (code.startsWith('ru')) active = 'ru';
1053
+ else active = 'en';
822
1054
  }
823
- return LOCALES.ru[key] || LOCALES.en[key] || key;
1055
+ if (state?.lang) {
1056
+ if (state.lang === 'zh') active = 'zh';
1057
+ else if (state.lang === 'ru') active = 'ru';
1058
+ else if (state.lang === 'en') active = 'en';
1059
+ }
1060
+ return active;
1061
+ };
1062
+
1063
+ const t = (key) => {
1064
+ const active = resolveLocale();
1065
+ return LOCALES[active]?.[key] || LOCALES.en?.[key] || LOCALES.ru?.[key] || key;
824
1066
  };
825
1067
 
826
1068
  useEffect(() => {
@@ -1018,8 +1260,38 @@ window.__ModuleLoader__.load({
1018
1260
  } catch (_) {}
1019
1261
  };
1020
1262
 
1263
+ const showQuickLaunch = state ? (state.showQuickLaunchButton !== false) : true;
1264
+
1021
1265
  if (!state || !state.hasActiveGoal) {
1022
- return null;
1266
+ if (!showQuickLaunch) return null;
1267
+ return React.createElement(
1268
+ React.Fragment,
1269
+ null,
1270
+ React.createElement(
1271
+ 'div',
1272
+ { className: 'dsh-goal-dock dsh-goal-dock-quick', 'data-goal-dock': 'true' },
1273
+ React.createElement(
1274
+ 'button',
1275
+ {
1276
+ className: 'dsh-goal-quicklaunch-btn',
1277
+ title: t('quickLaunch') || 'Запустить цель (Goal Mode)',
1278
+ onClick: () => setIsQuickLaunchOpen(true),
1279
+ },
1280
+ React.createElement(IconTarget, { size: 14 }),
1281
+ React.createElement('span', null, t('quickLaunch') || 'Запустить цель'),
1282
+ ),
1283
+ ),
1284
+ isQuickLaunchOpen
1285
+ ? React.createElement(QuickLaunchModal, {
1286
+ onClose: () => setIsQuickLaunchOpen(false),
1287
+ onStart: async (title) => {
1288
+ await handleAction('start', { title });
1289
+ setIsQuickLaunchOpen(false);
1290
+ },
1291
+ t,
1292
+ })
1293
+ : null,
1294
+ );
1023
1295
  }
1024
1296
 
1025
1297
  const isPaused = state.state === 'PAUSED';
@@ -1034,6 +1306,7 @@ window.__ModuleLoader__.load({
1034
1306
  };
1035
1307
 
1036
1308
  const liveElapsedStr = formatLiveElapsed();
1309
+ const etaStr = state?.formattedETA ? ` (ETA ${state.formattedETA})` : '';
1037
1310
 
1038
1311
  return React.createElement(
1039
1312
  React.Fragment,
@@ -1057,7 +1330,7 @@ window.__ModuleLoader__.load({
1057
1330
  {
1058
1331
  className: `dsh-goal-badge ${isCompleted ? 'dsh-goal-badge-ok' : isPaused ? 'dsh-goal-badge-warn' : ''}`,
1059
1332
  },
1060
- isCompleted ? (t('goalCompleted') || 'Цель выполнена') : isPaused ? (t('pause') || 'На паузе') : (t('goalLabel') || 'Текущая цель'),
1333
+ isCompleted ? (t('goalCompleted') || 'Goal completed') : isPaused ? (t('paused') || 'Paused') : (t('goalLabel') || 'Current goal'),
1061
1334
  ),
1062
1335
  React.createElement('span', { className: 'dsh-goal-title', title: state.title }, state.title),
1063
1336
  ),
@@ -1067,14 +1340,14 @@ window.__ModuleLoader__.load({
1067
1340
  React.createElement(
1068
1341
  'span',
1069
1342
  { className: 'dsh-goal-time' },
1070
- `⏱ ${liveElapsedStr}`,
1343
+ `⏱ ${liveElapsedStr}${etaStr}`,
1071
1344
  ),
1072
1345
  !isCompleted
1073
1346
  ? React.createElement(
1074
1347
  'button',
1075
1348
  {
1076
1349
  className: 'dsh-goal-btn icon-only',
1077
- title: isPaused ? (t('resume') || 'Возобновить') : (t('pause') || 'Приостановить'),
1350
+ title: isPaused ? (t('resume') || 'Resume') : (t('pause') || 'Pause'),
1078
1351
  onClick: () => handleAction(isPaused ? 'resume' : 'pause'),
1079
1352
  },
1080
1353
  isPaused ? React.createElement(IconPlay, { size: 14 }) : React.createElement(IconPause, { size: 14 }),
@@ -1084,7 +1357,7 @@ window.__ModuleLoader__.load({
1084
1357
  'button',
1085
1358
  {
1086
1359
  className: 'dsh-goal-btn icon-only',
1087
- title: t('details') || 'Развернуть детали цели',
1360
+ title: t('details') || 'Expand goal details',
1088
1361
  onClick: () => setIsModalOpen(true),
1089
1362
  },
1090
1363
  React.createElement(IconExpand, { size: 14 }),
@@ -1094,7 +1367,7 @@ window.__ModuleLoader__.load({
1094
1367
  'button',
1095
1368
  {
1096
1369
  className: 'dsh-goal-btn icon-only close',
1097
- title: t('closeBanner') || 'Закрыть плашку цели',
1370
+ title: t('closeBanner') || 'Close goal banner',
1098
1371
  onClick: () => handleAction('clear'),
1099
1372
  },
1100
1373
  '✕',
@@ -1119,6 +1392,7 @@ window.__ModuleLoader__.load({
1119
1392
  maxIterations: 25,
1120
1393
  autoDrive: true,
1121
1394
  enableSound: true,
1395
+ showQuickLaunchButton: true,
1122
1396
  };
1123
1397
 
1124
1398
  function parseNumberField(raw) {
@@ -1177,7 +1451,7 @@ window.__ModuleLoader__.load({
1177
1451
  function computeSavePlan(draft, snap) {
1178
1452
  if (!draft) return [];
1179
1453
 
1180
- const fields = ['maxIterations', 'autoDrive', 'enableSound'];
1454
+ const fields = ['maxIterations', 'autoDrive', 'enableSound', 'showQuickLaunchButton'];
1181
1455
  const writes = [];
1182
1456
 
1183
1457
  for (const f of fields) {
@@ -1221,21 +1495,37 @@ window.__ModuleLoader__.load({
1221
1495
  });
1222
1496
  }, [scope]);
1223
1497
 
1224
- const t = (key) => {
1498
+ const resolveLocale = () => {
1499
+ let active = 'en';
1225
1500
  if (ctx?.locale?.getSnapshot) {
1226
1501
  const snapLoc = ctx.locale.getSnapshot();
1227
- const active = snapLoc?.active || 'ru';
1228
- return LOCALES[active]?.[key] || LOCALES.ru[key] || LOCALES.en[key] || key;
1502
+ if (snapLoc?.active) {
1503
+ const code = snapLoc.active.toLowerCase();
1504
+ if (code.startsWith('zh')) active = 'zh';
1505
+ else if (code.startsWith('ru')) active = 'ru';
1506
+ else active = 'en';
1507
+ }
1508
+ } else if (typeof navigator !== 'undefined' && navigator.language) {
1509
+ const code = navigator.language.toLowerCase();
1510
+ if (code.startsWith('zh')) active = 'zh';
1511
+ else if (code.startsWith('ru')) active = 'ru';
1512
+ else active = 'en';
1229
1513
  }
1230
- return LOCALES.ru[key] || LOCALES.en[key] || key;
1514
+ return active;
1515
+ };
1516
+
1517
+ const t = (key) => {
1518
+ const active = resolveLocale();
1519
+ return LOCALES[active]?.[key] || LOCALES.en[key] || LOCALES.ru?.[key] || key;
1231
1520
  };
1232
1521
 
1233
1522
  const status = (snap && snap.status) || 'ready';
1234
1523
  const maxIterStatus = getFieldStatus('maxIterations', draft?.maxIterations, snap);
1235
1524
  const autoDriveStatus = getFieldStatus('autoDrive', draft?.autoDrive, snap);
1236
1525
  const enableSoundStatus = getFieldStatus('enableSound', draft?.enableSound, snap);
1526
+ const quickLaunchStatus = getFieldStatus('showQuickLaunchButton', draft?.showQuickLaunchButton, snap);
1237
1527
 
1238
- const dirty = maxIterStatus.isDirty || autoDriveStatus.isDirty || enableSoundStatus.isDirty;
1528
+ const dirty = maxIterStatus.isDirty || autoDriveStatus.isDirty || enableSoundStatus.isDirty || quickLaunchStatus.isDirty;
1239
1529
  const invalid = maxIterStatus.invalid;
1240
1530
  const disabled = !scope || saving || snap?.writable === false;
1241
1531
 
@@ -1245,6 +1535,7 @@ window.__ModuleLoader__.load({
1245
1535
  maxIterations: prev?.maxIterations !== undefined ? prev.maxIterations : maxIterStatus.value,
1246
1536
  autoDrive: prev?.autoDrive !== undefined ? prev.autoDrive : autoDriveStatus.value,
1247
1537
  enableSound: prev?.enableSound !== undefined ? prev.enableSound : enableSoundStatus.value,
1538
+ showQuickLaunchButton: prev?.showQuickLaunchButton !== undefined ? prev.showQuickLaunchButton : quickLaunchStatus.value,
1248
1539
  [field]: val,
1249
1540
  }));
1250
1541
  };
@@ -1418,6 +1709,33 @@ window.__ModuleLoader__.load({
1418
1709
  )
1419
1710
  : null,
1420
1711
  ),
1712
+ React.createElement(
1713
+ 'div',
1714
+ { className: 'dsh-goal-check-row' },
1715
+ React.createElement(
1716
+ 'label',
1717
+ { className: 'dsh-goal-check' },
1718
+ React.createElement('input', {
1719
+ type: 'checkbox',
1720
+ checked: quickLaunchStatus.value !== false,
1721
+ disabled,
1722
+ onChange: (e) => edit('showQuickLaunchButton', e.target.checked),
1723
+ }),
1724
+ t('quickLaunchLabel') || 'Кнопка быстрого запуска цели над полем ввода',
1725
+ ),
1726
+ quickLaunchStatus.isOverridden
1727
+ ? React.createElement(
1728
+ 'button',
1729
+ {
1730
+ type: 'button',
1731
+ className: 'dsh-goal-btn-inline-reset',
1732
+ title: t('resetField') || 'Сбросить к значению по умолчанию',
1733
+ onClick: () => resetFieldToDefault('showQuickLaunchButton'),
1734
+ },
1735
+ React.createElement(IconRotateCcw, { size: 12 }),
1736
+ )
1737
+ : null,
1738
+ ),
1421
1739
  React.createElement(
1422
1740
  'div',
1423
1741
  { className: 'dsh-goal-foot' },
@@ -1450,45 +1768,23 @@ window.__ModuleLoader__.load({
1450
1768
 
1451
1769
  // --- СЛОВАРИ ЛОКАЛИЗАЦИИ ---
1452
1770
  const LOCALES = {
1453
- ru: {
1454
- goalLabel: 'Текущая цель',
1455
- goalCompleted: 'Цель выполнена',
1456
- clearGoal: 'Очистить цель',
1457
- closeBanner: 'Закрыть плашку цели',
1458
- pause: 'Приостановить',
1459
- resume: 'Возобновить',
1460
- details: 'Развернуть детали цели',
1461
- modalTitle: 'План и статус цели',
1462
- modalTitleCompleted: 'Цель выполнена: план и результаты',
1463
- time: 'Время работы',
1464
- milestones: 'План работ:',
1465
- noMilestones: 'Агент формирует план работ...',
1466
- close: 'Закрыть',
1467
- pluginTitle: 'Цели и автономный режим (Goal Mode)',
1468
- pluginDesc: 'Панель цели над полем ввода, декомпозиция вех и авто-драйв',
1469
- maxIterLabel: 'Максимум итераций (Safety Limit):',
1470
- autoDriveLabel: 'Авто-драйв: продолжать цикл автоматически',
1471
- soundLabel: 'Звук по завершении цели',
1472
- save: 'Сохранить',
1473
- saving: 'Сохранение…',
1474
- discard: 'Отменить правки',
1475
- resetField: 'По умолчанию',
1476
- overridden: 'изменено',
1477
- saveFailed: 'Не сохранено — исправьте и повторите',
1478
- loading: 'Загрузка настроек…',
1479
- unavailable: 'Настройки недоступны',
1480
- },
1481
1771
  en: {
1482
1772
  goalLabel: 'Current goal',
1483
1773
  goalCompleted: 'Goal completed',
1484
1774
  clearGoal: 'Clear goal',
1485
1775
  closeBanner: 'Close goal banner',
1486
1776
  pause: 'Pause',
1777
+ paused: 'Paused',
1487
1778
  resume: 'Resume',
1488
1779
  details: 'Expand Goal Details',
1489
1780
  modalTitle: 'Goal Plan & Status',
1490
1781
  modalTitleCompleted: 'Goal Completed: Plan & Results',
1491
1782
  time: 'Running time',
1783
+ eta: 'ETA',
1784
+ tokens: 'Tokens',
1785
+ iterations: 'Iterations',
1786
+ progress: 'Progress',
1787
+ done: 'Done',
1492
1788
  milestones: 'Plan of work:',
1493
1789
  noMilestones: 'Agent is preparing the plan of work...',
1494
1790
  close: 'Close',
@@ -1497,6 +1793,14 @@ window.__ModuleLoader__.load({
1497
1793
  maxIterLabel: 'Max iterations (Safety Limit):',
1498
1794
  autoDriveLabel: 'Auto-drive: keep the loop running automatically',
1499
1795
  soundLabel: 'Sound when a goal completes',
1796
+ quickLaunchLabel: 'Quick launch goal button above composer dock',
1797
+ quickLaunch: 'Start Goal',
1798
+ quickLaunchTitle: 'Quick Launch Goal',
1799
+ quickLaunchDesc: 'Define the objective for the agent in autonomous mode:',
1800
+ quickLaunchPlaceholder: 'e.g. Implement report export and cover with unit tests...',
1801
+ startGoal: 'Start Goal',
1802
+ copyReport: '📋 Copy Report in Markdown',
1803
+ reportCopied: '✅ Copied!',
1500
1804
  save: 'Save',
1501
1805
  saving: 'Saving…',
1502
1806
  discard: 'Discard changes',
@@ -1506,6 +1810,90 @@ window.__ModuleLoader__.load({
1506
1810
  loading: 'Loading settings…',
1507
1811
  unavailable: 'Settings unavailable',
1508
1812
  },
1813
+ zh: {
1814
+ goalLabel: '当前目标',
1815
+ goalCompleted: '目标已完成',
1816
+ clearGoal: '清除目标',
1817
+ closeBanner: '关闭目标横幅',
1818
+ pause: '暂停',
1819
+ paused: '已暂停',
1820
+ resume: '恢复',
1821
+ details: '展开目标详情',
1822
+ modalTitle: '目标计划与状态',
1823
+ modalTitleCompleted: '目标已完成:计划与成果',
1824
+ time: '运行时间',
1825
+ eta: '预估剩余 (ETA)',
1826
+ tokens: 'Token 消耗',
1827
+ iterations: '迭代次数',
1828
+ progress: '进度',
1829
+ done: '已完成',
1830
+ milestones: '工作计划:',
1831
+ noMilestones: '智能体正在制定工作计划...',
1832
+ close: '关闭',
1833
+ pluginTitle: '目标模式与自主循环 (Goal Mode)',
1834
+ pluginDesc: '输入框上方常驻目标横幅、里程碑拆解与自主循环',
1835
+ maxIterLabel: '最大迭代次数 (Safety Limit):',
1836
+ autoDriveLabel: '自动执行:在各轮次间自动保持循环',
1837
+ soundLabel: '目标完成时播放提示音',
1838
+ quickLaunchLabel: '在输入框上方显示快速启动目标按钮',
1839
+ quickLaunch: '启动目标',
1840
+ quickLaunchTitle: '快速启动目标',
1841
+ quickLaunchDesc: '为自主模式下的智能体设定任务目标:',
1842
+ quickLaunchPlaceholder: '例如:实现报表导出功能并编写单元测试...',
1843
+ startGoal: '启动目标',
1844
+ copyReport: '📋 复制 Markdown 报告',
1845
+ reportCopied: '✅ 已复制!',
1846
+ save: '保存',
1847
+ saving: '保存中…',
1848
+ discard: '放弃更改',
1849
+ resetField: '恢复默认',
1850
+ overridden: '已修改',
1851
+ saveFailed: '保存失败 — 请检查并重试',
1852
+ loading: '正在加载设置…',
1853
+ unavailable: '设置不可用',
1854
+ },
1855
+ ru: {
1856
+ goalLabel: 'Текущая цель',
1857
+ goalCompleted: 'Цель выполнена',
1858
+ clearGoal: 'Очистить цель',
1859
+ closeBanner: 'Закрыть плашку цели',
1860
+ pause: 'Приостановить',
1861
+ paused: 'На паузе',
1862
+ resume: 'Возобновить',
1863
+ details: 'Развернуть детали цели',
1864
+ modalTitle: 'План и статус цели',
1865
+ modalTitleCompleted: 'Цель выполнена: план и результаты',
1866
+ time: 'Время работы',
1867
+ eta: 'Оценка (ETA)',
1868
+ tokens: 'Токены',
1869
+ iterations: 'Итерации',
1870
+ progress: 'Прогресс',
1871
+ done: 'Готово',
1872
+ milestones: 'План работ:',
1873
+ noMilestones: 'Агент формирует план работ...',
1874
+ close: 'Закрыть',
1875
+ pluginTitle: 'Цели и автономный режим (Goal Mode)',
1876
+ pluginDesc: 'Панель цели над полем ввода, декомпозиция вех и авто-драйв',
1877
+ maxIterLabel: 'Максимум итераций (Safety Limit):',
1878
+ autoDriveLabel: 'Авто-драйв: продолжать цикл автоматически',
1879
+ soundLabel: 'Звук по завершении цели',
1880
+ quickLaunchLabel: 'Кнопка быстрого запуска цели над полем ввода',
1881
+ quickLaunch: 'Запустить цель',
1882
+ quickLaunchTitle: 'Быстрый запуск цели',
1883
+ quickLaunchDesc: 'Сформулируйте задачу для агента в автономном режиме:',
1884
+ quickLaunchPlaceholder: 'Например: Реализовать экспорт отчетов и покрыть тестами...',
1885
+ startGoal: 'Запустить цель',
1886
+ copyReport: '📋 Скопировать отчёт в Markdown',
1887
+ reportCopied: '✅ Скопировано!',
1888
+ save: 'Сохранить',
1889
+ saving: 'Сохранение…',
1890
+ discard: 'Отменить правки',
1891
+ resetField: 'По умолчанию',
1892
+ overridden: 'изменено',
1893
+ saveFailed: 'Не сохранено — исправьте и повторите',
1894
+ loading: 'Загрузка настроек…',
1895
+ unavailable: 'Настройки недоступны',
1896
+ },
1509
1897
  };
1510
1898
 
1511
1899
  module.exports.inject = ['slots', 'locale', 'settingsScope'];