@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/README.md +118 -44
- package/README.zh.md +217 -0
- package/docs/design/DESIGN.md +20 -0
- package/lib/client.js +442 -54
- package/lib/command-handler.js +80 -32
- package/lib/goal-engine.js +138 -5
- package/lib/index.js +41 -9
- package/package.json +8 -7
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(
|
|
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:
|
|
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') || '
|
|
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
|
|
1039
|
+
const resolveLocale = () => {
|
|
1040
|
+
let active = 'en';
|
|
818
1041
|
if (ctx?.locale?.getSnapshot) {
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
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
|
-
|
|
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') || '
|
|
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') || '
|
|
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
|
|
1498
|
+
const resolveLocale = () => {
|
|
1499
|
+
let active = 'en';
|
|
1225
1500
|
if (ctx?.locale?.getSnapshot) {
|
|
1226
1501
|
const snapLoc = ctx.locale.getSnapshot();
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|
|
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'];
|