@xcanwin/manyoyo 5.7.4 → 5.7.7
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/web/frontend/app.css +264 -20
- package/lib/web/frontend/app.html +1 -0
- package/lib/web/frontend/app.js +504 -26
- package/lib/web/server.js +55 -0
- package/package.json +1 -1
package/lib/web/frontend/app.css
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
--line: #d9c7ad;
|
|
15
15
|
--line-strong: #b59263;
|
|
16
|
+
--line-soft: rgba(181, 146, 99, 0.26);
|
|
16
17
|
|
|
17
18
|
--text: #1f1a14;
|
|
18
19
|
--muted: #6a5f52;
|
|
@@ -415,7 +416,7 @@ textarea:focus-visible {
|
|
|
415
416
|
padding-right: 4px;
|
|
416
417
|
display: flex;
|
|
417
418
|
flex-direction: column;
|
|
418
|
-
gap:
|
|
419
|
+
gap: 10px;
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
#sessionList::-webkit-scrollbar,
|
|
@@ -533,14 +534,126 @@ textarea:focus-visible {
|
|
|
533
534
|
.workbench-group {
|
|
534
535
|
display: flex;
|
|
535
536
|
flex-direction: column;
|
|
536
|
-
gap:
|
|
537
|
+
gap: 10px;
|
|
537
538
|
}
|
|
538
539
|
|
|
539
|
-
.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
540
|
+
button.tree-toggle {
|
|
541
|
+
width: 100%;
|
|
542
|
+
text-align: left;
|
|
543
|
+
color: var(--text);
|
|
543
544
|
background: rgba(255, 250, 242, 0.92);
|
|
545
|
+
border-color: rgba(181, 146, 99, 0.38);
|
|
546
|
+
padding: 10px 12px;
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: flex-start;
|
|
549
|
+
justify-content: space-between;
|
|
550
|
+
gap: 12px;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
button.tree-toggle:hover {
|
|
554
|
+
transform: none;
|
|
555
|
+
background: #fff6eb;
|
|
556
|
+
border-color: #d1aa7f;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
button.tree-toggle:active {
|
|
560
|
+
transform: none;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
button.tree-toggle:focus-visible {
|
|
564
|
+
outline: none;
|
|
565
|
+
box-shadow: 0 0 0 3px rgba(196, 85, 31, 0.14);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.tree-toggle-main {
|
|
569
|
+
min-width: 0;
|
|
570
|
+
display: flex;
|
|
571
|
+
flex-direction: column;
|
|
572
|
+
gap: 4px;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.tree-toggle-meta {
|
|
576
|
+
color: var(--muted);
|
|
577
|
+
font-size: 11px;
|
|
578
|
+
line-height: 1.45;
|
|
579
|
+
word-break: break-word;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.tree-toggle-caret {
|
|
583
|
+
color: #8c7257;
|
|
584
|
+
font-size: 18px;
|
|
585
|
+
line-height: 1;
|
|
586
|
+
flex-shrink: 0;
|
|
587
|
+
transition: transform 140ms ease;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.tree-toggle[aria-expanded="true"] .tree-toggle-caret {
|
|
591
|
+
transform: rotate(90deg);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.workbench-group-head {
|
|
595
|
+
padding: 10px 14px;
|
|
596
|
+
border: 1px solid rgba(181, 146, 99, 0.34);
|
|
597
|
+
border-radius: 999px;
|
|
598
|
+
background: linear-gradient(180deg, rgba(255, 250, 242, 0.98) 0%, rgba(252, 242, 230, 0.96) 100%);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.workbench-path-bar .tree-toggle-main {
|
|
602
|
+
flex: 1;
|
|
603
|
+
min-width: 0;
|
|
604
|
+
display: grid;
|
|
605
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
606
|
+
align-items: center;
|
|
607
|
+
column-gap: 10px;
|
|
608
|
+
row-gap: 3px;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.workbench-path-bar .workbench-group-kicker {
|
|
612
|
+
grid-column: 1;
|
|
613
|
+
grid-row: 1;
|
|
614
|
+
display: inline-flex;
|
|
615
|
+
align-items: center;
|
|
616
|
+
padding: 3px 8px;
|
|
617
|
+
border-radius: 999px;
|
|
618
|
+
border: 1px solid rgba(181, 146, 99, 0.34);
|
|
619
|
+
background: rgba(255, 255, 255, 0.56);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.workbench-path-bar .workbench-group-title {
|
|
623
|
+
grid-column: 2;
|
|
624
|
+
grid-row: 1;
|
|
625
|
+
margin-top: 0;
|
|
626
|
+
min-width: 0;
|
|
627
|
+
overflow: hidden;
|
|
628
|
+
text-overflow: ellipsis;
|
|
629
|
+
white-space: nowrap;
|
|
630
|
+
font-family: var(--font-mono);
|
|
631
|
+
font-size: 12px;
|
|
632
|
+
font-weight: 600;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.workbench-path-bar .tree-toggle-meta {
|
|
636
|
+
grid-column: 2;
|
|
637
|
+
grid-row: 2;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.workbench-group.has-active .workbench-group-head,
|
|
641
|
+
.container-card.has-active {
|
|
642
|
+
border-color: #c68d5a;
|
|
643
|
+
box-shadow: 0 0 0 2px rgba(196, 85, 31, 0.08);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.workbench-group-body {
|
|
647
|
+
display: flex;
|
|
648
|
+
flex-direction: column;
|
|
649
|
+
gap: 10px;
|
|
650
|
+
margin-left: 14px;
|
|
651
|
+
padding-left: 12px;
|
|
652
|
+
border-left: 1px solid var(--line-soft);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.workbench-group-body[hidden] {
|
|
656
|
+
display: none;
|
|
544
657
|
}
|
|
545
658
|
|
|
546
659
|
.workbench-group-kicker,
|
|
@@ -564,61 +677,170 @@ textarea:focus-visible {
|
|
|
564
677
|
.container-stack {
|
|
565
678
|
display: flex;
|
|
566
679
|
flex-direction: column;
|
|
567
|
-
gap:
|
|
680
|
+
gap: 10px;
|
|
568
681
|
}
|
|
569
682
|
|
|
570
683
|
.container-card {
|
|
571
|
-
border: 1px solid
|
|
572
|
-
border-radius:
|
|
573
|
-
background: rgba(255, 255, 255, 0.
|
|
574
|
-
padding:
|
|
684
|
+
border: 1px solid rgba(181, 146, 99, 0.52);
|
|
685
|
+
border-radius: 16px;
|
|
686
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(249, 242, 232, 0.98) 100%);
|
|
687
|
+
padding: 12px;
|
|
575
688
|
display: flex;
|
|
576
689
|
flex-direction: column;
|
|
577
690
|
gap: 10px;
|
|
691
|
+
box-shadow: 0 10px 22px rgba(44, 31, 15, 0.08);
|
|
578
692
|
}
|
|
579
693
|
|
|
580
694
|
.container-card-head {
|
|
695
|
+
display: flex;
|
|
696
|
+
flex-wrap: wrap;
|
|
697
|
+
align-items: flex-start;
|
|
698
|
+
justify-content: space-between;
|
|
699
|
+
gap: 12px;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.container-toggle {
|
|
703
|
+
flex: 1;
|
|
704
|
+
min-width: 0;
|
|
705
|
+
padding: 0;
|
|
706
|
+
border: none;
|
|
707
|
+
background: transparent;
|
|
708
|
+
color: var(--text);
|
|
581
709
|
display: flex;
|
|
582
710
|
align-items: flex-start;
|
|
583
711
|
justify-content: space-between;
|
|
712
|
+
gap: 12px;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
button.container-toggle:hover,
|
|
716
|
+
button.container-toggle:active {
|
|
717
|
+
transform: none;
|
|
718
|
+
background: transparent;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
button.container-toggle:focus-visible {
|
|
722
|
+
outline: none;
|
|
723
|
+
box-shadow: 0 0 0 3px rgba(196, 85, 31, 0.14);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.container-toggle-main {
|
|
727
|
+
min-width: 0;
|
|
728
|
+
flex: 1;
|
|
729
|
+
display: flex;
|
|
730
|
+
flex-direction: column;
|
|
731
|
+
gap: 8px;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.container-title-row {
|
|
735
|
+
display: flex;
|
|
736
|
+
align-items: flex-start;
|
|
584
737
|
gap: 10px;
|
|
738
|
+
min-width: 0;
|
|
585
739
|
}
|
|
586
740
|
|
|
587
|
-
.container-
|
|
741
|
+
.container-title-stack {
|
|
588
742
|
min-width: 0;
|
|
589
743
|
display: flex;
|
|
590
744
|
flex-direction: column;
|
|
591
745
|
gap: 4px;
|
|
592
746
|
}
|
|
593
747
|
|
|
748
|
+
.sidebar-icon {
|
|
749
|
+
width: 32px;
|
|
750
|
+
height: 32px;
|
|
751
|
+
flex: 0 0 auto;
|
|
752
|
+
display: inline-flex;
|
|
753
|
+
align-items: center;
|
|
754
|
+
justify-content: center;
|
|
755
|
+
border-radius: 11px;
|
|
756
|
+
border: 1px solid rgba(181, 146, 99, 0.34);
|
|
757
|
+
background: linear-gradient(180deg, rgba(255, 244, 232, 0.96) 0%, rgba(255, 252, 247, 0.9) 100%);
|
|
758
|
+
color: var(--accent-strong);
|
|
759
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.sidebar-icon svg {
|
|
763
|
+
width: 18px;
|
|
764
|
+
height: 18px;
|
|
765
|
+
display: block;
|
|
766
|
+
}
|
|
767
|
+
|
|
594
768
|
.container-card-meta {
|
|
595
769
|
display: flex;
|
|
596
770
|
flex-wrap: wrap;
|
|
597
|
-
gap:
|
|
771
|
+
gap: 8px;
|
|
598
772
|
align-items: center;
|
|
599
|
-
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.sidebar-badge {
|
|
776
|
+
display: inline-flex;
|
|
777
|
+
align-items: center;
|
|
778
|
+
gap: 4px;
|
|
779
|
+
min-height: 26px;
|
|
780
|
+
padding: 4px 10px;
|
|
781
|
+
border-radius: 999px;
|
|
782
|
+
border: 1px solid transparent;
|
|
783
|
+
font-size: 11px;
|
|
784
|
+
font-weight: 700;
|
|
785
|
+
line-height: 1.2;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.container-status-pill {
|
|
789
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.container-agent-badge {
|
|
793
|
+
background: rgba(15, 124, 114, 0.1);
|
|
794
|
+
border-color: rgba(15, 124, 114, 0.22);
|
|
795
|
+
color: var(--subaccent-strong);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.container-card-info {
|
|
799
|
+
color: #7b6d5d;
|
|
600
800
|
font-size: 11px;
|
|
801
|
+
line-height: 1.45;
|
|
802
|
+
white-space: nowrap;
|
|
803
|
+
overflow: hidden;
|
|
804
|
+
text-overflow: ellipsis;
|
|
601
805
|
}
|
|
602
806
|
|
|
603
807
|
.add-agent-btn {
|
|
604
|
-
|
|
808
|
+
align-self: flex-start;
|
|
809
|
+
padding: 7px 11px;
|
|
605
810
|
font-size: 12px;
|
|
811
|
+
border-radius: 999px;
|
|
812
|
+
white-space: nowrap;
|
|
813
|
+
margin-bottom: 2px;
|
|
606
814
|
}
|
|
607
815
|
|
|
608
816
|
.agent-list {
|
|
609
817
|
display: flex;
|
|
610
818
|
flex-direction: column;
|
|
611
|
-
gap:
|
|
819
|
+
gap: 7px;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.container-card-body {
|
|
823
|
+
margin-top: 2px;
|
|
824
|
+
padding: 10px;
|
|
825
|
+
border: 1px solid rgba(181, 146, 99, 0.28);
|
|
826
|
+
border-top-color: rgba(181, 146, 99, 0.4);
|
|
827
|
+
border-radius: 13px;
|
|
828
|
+
background: linear-gradient(180deg, rgba(244, 238, 228, 0.88) 0%, rgba(255, 255, 255, 0.98) 100%);
|
|
829
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.agent-list[hidden] {
|
|
833
|
+
display: none;
|
|
612
834
|
}
|
|
613
835
|
|
|
614
836
|
.agent-item {
|
|
615
837
|
text-align: left;
|
|
616
838
|
width: 100%;
|
|
617
|
-
border: 1px solid rgba(181, 146, 99, 0.
|
|
618
|
-
background:
|
|
839
|
+
border: 1px solid rgba(181, 146, 99, 0.38);
|
|
840
|
+
background: rgba(255, 255, 255, 0.94);
|
|
619
841
|
color: var(--text);
|
|
620
|
-
border-radius:
|
|
621
|
-
padding: 10px;
|
|
842
|
+
border-radius: 12px;
|
|
843
|
+
padding: 10px 11px;
|
|
622
844
|
animation: itemIn 220ms ease both;
|
|
623
845
|
animation-delay: calc(var(--item-index, 0) * 24ms);
|
|
624
846
|
}
|
|
@@ -1477,6 +1699,28 @@ details.trace-card > .trace-card-summary {
|
|
|
1477
1699
|
}
|
|
1478
1700
|
}
|
|
1479
1701
|
|
|
1702
|
+
.streaming-reply {
|
|
1703
|
+
border-color: var(--subaccent);
|
|
1704
|
+
box-shadow: 0 8px 16px rgba(15, 124, 114, 0.10);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
.streaming-cursor {
|
|
1708
|
+
display: inline-block;
|
|
1709
|
+
width: 7px;
|
|
1710
|
+
height: 16px;
|
|
1711
|
+
margin-left: 2px;
|
|
1712
|
+
vertical-align: text-bottom;
|
|
1713
|
+
background: var(--subaccent);
|
|
1714
|
+
border-radius: 2px;
|
|
1715
|
+
animation: blink-cursor 640ms steps(2, start) infinite;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
@keyframes blink-cursor {
|
|
1719
|
+
to {
|
|
1720
|
+
visibility: hidden;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1480
1724
|
@keyframes shimmer {
|
|
1481
1725
|
100% {
|
|
1482
1726
|
transform: translateX(100%);
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
>更多</button>
|
|
59
59
|
<div class="header-actions" id="headerActions">
|
|
60
60
|
<button type="button" id="refreshBtn" class="secondary">刷新</button>
|
|
61
|
+
<button type="button" id="addAgentBtn" class="secondary">新建AGENT</button>
|
|
61
62
|
<button type="button" id="removeBtn" class="danger-outline">删除容器</button>
|
|
62
63
|
<button type="button" id="removeAllBtn" class="danger">删除对话</button>
|
|
63
64
|
</div>
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -63,6 +63,12 @@
|
|
|
63
63
|
createRuns: {},
|
|
64
64
|
sessionNodeMap: new Map(),
|
|
65
65
|
sessionRenderMode: 'empty',
|
|
66
|
+
sidebarTreeLoaded: false,
|
|
67
|
+
sidebarTree: {
|
|
68
|
+
directories: {},
|
|
69
|
+
containers: {}
|
|
70
|
+
},
|
|
71
|
+
pendingActiveSessionScroll: false,
|
|
66
72
|
directoryPicker: {
|
|
67
73
|
open: false,
|
|
68
74
|
loading: false,
|
|
@@ -110,6 +116,7 @@
|
|
|
110
116
|
const viewDetailBtn = document.getElementById('viewDetailBtn');
|
|
111
117
|
const viewConfigBtn = document.getElementById('viewConfigBtn');
|
|
112
118
|
const viewCheckBtn = document.getElementById('viewCheckBtn');
|
|
119
|
+
const addAgentBtn = document.getElementById('addAgentBtn');
|
|
113
120
|
const mobileSidebarClose = document.getElementById('mobileSidebarClose');
|
|
114
121
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
115
122
|
const openConfigBtn = document.getElementById('openConfigBtn');
|
|
@@ -205,12 +212,63 @@
|
|
|
205
212
|
const GEMINI_YOLO_FLAG = '--yolo';
|
|
206
213
|
const CODEX_DANGEROUS_FLAG = '--dangerously-bypass-approvals-and-sandbox';
|
|
207
214
|
const OPENCODE_PERMISSION_KEY = 'OPENCODE_PERMISSION=';
|
|
215
|
+
const SIDEBAR_TREE_STORAGE_KEY = 'manyoyo.web.sidebarTree.v1';
|
|
208
216
|
const markdownRenderer = window.ManyoyoMarkdown
|
|
209
217
|
&& typeof window.ManyoyoMarkdown.shouldRenderMessage === 'function'
|
|
210
218
|
&& typeof window.ManyoyoMarkdown.render === 'function'
|
|
211
219
|
? window.ManyoyoMarkdown
|
|
212
220
|
: null;
|
|
213
221
|
|
|
222
|
+
function normalizeBooleanMap(source) {
|
|
223
|
+
const result = {};
|
|
224
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
Object.keys(source).forEach(function (key) {
|
|
228
|
+
if (typeof source[key] === 'boolean') {
|
|
229
|
+
result[String(key)] = source[key];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function loadSidebarTreeState() {
|
|
236
|
+
if (state.sidebarTreeLoaded) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
state.sidebarTreeLoaded = true;
|
|
240
|
+
try {
|
|
241
|
+
if (!window.localStorage) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const raw = window.localStorage.getItem(SIDEBAR_TREE_STORAGE_KEY);
|
|
245
|
+
if (!raw) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const parsed = JSON.parse(raw);
|
|
249
|
+
state.sidebarTree = {
|
|
250
|
+
directories: normalizeBooleanMap(parsed && parsed.directories),
|
|
251
|
+
containers: normalizeBooleanMap(parsed && parsed.containers)
|
|
252
|
+
};
|
|
253
|
+
} catch (e) {
|
|
254
|
+
state.sidebarTree = {
|
|
255
|
+
directories: {},
|
|
256
|
+
containers: {}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function persistSidebarTreeState() {
|
|
262
|
+
try {
|
|
263
|
+
if (!window.localStorage) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
window.localStorage.setItem(SIDEBAR_TREE_STORAGE_KEY, JSON.stringify(state.sidebarTree));
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// 忽略浏览器存储异常,避免影响主流程
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
214
272
|
function appendPlainMessageContent(bubble, content) {
|
|
215
273
|
const pre = document.createElement('pre');
|
|
216
274
|
pre.textContent = content == null ? '' : String(content);
|
|
@@ -412,6 +470,9 @@
|
|
|
412
470
|
function roleName(role, message) {
|
|
413
471
|
if (role === 'user') return '我';
|
|
414
472
|
if (role === 'assistant') {
|
|
473
|
+
if (message && message.streamingReply) {
|
|
474
|
+
return 'AGENT 实时回复';
|
|
475
|
+
}
|
|
415
476
|
if (message && message.streamTrace) {
|
|
416
477
|
return 'AGENT 过程';
|
|
417
478
|
}
|
|
@@ -459,6 +520,132 @@
|
|
|
459
520
|
});
|
|
460
521
|
}
|
|
461
522
|
|
|
523
|
+
function getSessionDirectoryPath(session) {
|
|
524
|
+
return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function getSessionContainerName(session) {
|
|
528
|
+
return String(session && session.containerName ? session.containerName : '').trim();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function findSessionByName(sessionName) {
|
|
532
|
+
const target = String(sessionName || '').trim();
|
|
533
|
+
if (!target) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
return state.sessions.find(function (session) {
|
|
537
|
+
return session && session.name === target;
|
|
538
|
+
}) || null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function pruneSidebarTreeState() {
|
|
542
|
+
loadSidebarTreeState();
|
|
543
|
+
|
|
544
|
+
const validDirectories = new Set(state.sessions.map(getSessionDirectoryPath));
|
|
545
|
+
const validContainers = new Set(state.sessions.map(getSessionContainerName).filter(Boolean));
|
|
546
|
+
let changed = false;
|
|
547
|
+
|
|
548
|
+
Object.keys(state.sidebarTree.directories).forEach(function (key) {
|
|
549
|
+
if (!validDirectories.has(key)) {
|
|
550
|
+
delete state.sidebarTree.directories[key];
|
|
551
|
+
changed = true;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
Object.keys(state.sidebarTree.containers).forEach(function (key) {
|
|
556
|
+
if (!validContainers.has(key)) {
|
|
557
|
+
delete state.sidebarTree.containers[key];
|
|
558
|
+
changed = true;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
if (changed) {
|
|
563
|
+
persistSidebarTreeState();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function setSidebarDirectoryExpanded(directoryPath, expanded, options) {
|
|
568
|
+
const path = String(directoryPath || '').trim();
|
|
569
|
+
if (!path) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
loadSidebarTreeState();
|
|
573
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
574
|
+
if (state.sidebarTree.directories[path] === expanded) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
state.sidebarTree.directories[path] = expanded;
|
|
578
|
+
if (opts.persist !== false) {
|
|
579
|
+
persistSidebarTreeState();
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function setSidebarContainerExpanded(containerName, expanded, options) {
|
|
585
|
+
const name = String(containerName || '').trim();
|
|
586
|
+
if (!name) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
loadSidebarTreeState();
|
|
590
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
591
|
+
if (state.sidebarTree.containers[name] === expanded) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
state.sidebarTree.containers[name] = expanded;
|
|
595
|
+
if (opts.persist !== false) {
|
|
596
|
+
persistSidebarTreeState();
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function ensureSessionPathExpanded(sessionName, options) {
|
|
602
|
+
const session = findSessionByName(sessionName);
|
|
603
|
+
if (!session) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
607
|
+
const directoryPath = getSessionDirectoryPath(session);
|
|
608
|
+
const containerName = getSessionContainerName(session);
|
|
609
|
+
const changedDirectory = setSidebarDirectoryExpanded(directoryPath, true, { persist: false });
|
|
610
|
+
const changedContainer = setSidebarContainerExpanded(containerName, true, { persist: false });
|
|
611
|
+
if ((changedDirectory || changedContainer) && opts.persist !== false) {
|
|
612
|
+
persistSidebarTreeState();
|
|
613
|
+
}
|
|
614
|
+
return changedDirectory || changedContainer;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function directoryContainsActiveSession(directoryGroup) {
|
|
618
|
+
const groups = directoryGroup && Array.isArray(directoryGroup.containers) ? directoryGroup.containers : [];
|
|
619
|
+
return groups.some(function (containerGroup) {
|
|
620
|
+
return containerContainsActiveSession(containerGroup);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function containerContainsActiveSession(containerGroup) {
|
|
625
|
+
const sessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
|
|
626
|
+
return sessions.some(function (session) {
|
|
627
|
+
return session && session.name === state.active;
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function isDirectoryExpanded(directoryGroup) {
|
|
632
|
+
loadSidebarTreeState();
|
|
633
|
+
const key = String(directoryGroup && directoryGroup.path ? directoryGroup.path : '').trim();
|
|
634
|
+
if (key && typeof state.sidebarTree.directories[key] === 'boolean') {
|
|
635
|
+
return state.sidebarTree.directories[key];
|
|
636
|
+
}
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function isContainerExpanded(containerGroup) {
|
|
641
|
+
loadSidebarTreeState();
|
|
642
|
+
const key = String(containerGroup && containerGroup.containerName ? containerGroup.containerName : '').trim();
|
|
643
|
+
if (key && typeof state.sidebarTree.containers[key] === 'boolean') {
|
|
644
|
+
return state.sidebarTree.containers[key];
|
|
645
|
+
}
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
|
|
462
649
|
function normalizeSlashPath(value) {
|
|
463
650
|
return String(value || '').replace(/\\/g, '/');
|
|
464
651
|
}
|
|
@@ -1524,6 +1711,9 @@
|
|
|
1524
1711
|
const activeAgentRunning = isAgentRunActiveForSession(state.active);
|
|
1525
1712
|
const busy = state.loadingSessions || state.loadingMessages || state.sending;
|
|
1526
1713
|
refreshBtn.disabled = busy;
|
|
1714
|
+
if (addAgentBtn) {
|
|
1715
|
+
addAgentBtn.disabled = !state.active || busy;
|
|
1716
|
+
}
|
|
1527
1717
|
removeBtn.disabled = !state.active || busy;
|
|
1528
1718
|
removeAllBtn.disabled = !state.active || busy;
|
|
1529
1719
|
sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
|
|
@@ -1966,6 +2156,7 @@
|
|
|
1966
2156
|
disconnectTerminal('会话切换,终端已断开', true);
|
|
1967
2157
|
}
|
|
1968
2158
|
state.active = sessionName;
|
|
2159
|
+
ensureSessionPathExpanded(sessionName);
|
|
1969
2160
|
state.sessionDetail = null;
|
|
1970
2161
|
state.sessionDetailError = '';
|
|
1971
2162
|
if (isMobileLayout()) {
|
|
@@ -2044,13 +2235,160 @@
|
|
|
2044
2235
|
containerGroup.updatedAt = session.updatedAt;
|
|
2045
2236
|
}
|
|
2046
2237
|
});
|
|
2047
|
-
return Array.from(groups.values()).
|
|
2238
|
+
return Array.from(groups.values()).map(function (group) {
|
|
2239
|
+
group.containers = Array.from(group.containers.values()).sort(function (a, b) {
|
|
2240
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2241
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2242
|
+
return timeB - timeA;
|
|
2243
|
+
});
|
|
2244
|
+
group.containers.forEach(function (containerGroup) {
|
|
2245
|
+
containerGroup.sessions.sort(function (a, b) {
|
|
2246
|
+
const timeA = a && a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2247
|
+
const timeB = b && b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2248
|
+
return timeB - timeA;
|
|
2249
|
+
});
|
|
2250
|
+
});
|
|
2251
|
+
return group;
|
|
2252
|
+
}).sort(function (a, b) {
|
|
2048
2253
|
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2049
2254
|
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2050
2255
|
return timeB - timeA;
|
|
2051
2256
|
});
|
|
2052
2257
|
}
|
|
2053
2258
|
|
|
2259
|
+
function createTreeMetaText(parts) {
|
|
2260
|
+
return (parts || []).filter(Boolean).join(' · ');
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
function createTreeToggle(options) {
|
|
2264
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
2265
|
+
const button = document.createElement('button');
|
|
2266
|
+
button.type = 'button';
|
|
2267
|
+
button.className = `tree-toggle ${opts.className || ''}`.trim();
|
|
2268
|
+
button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
|
|
2269
|
+
|
|
2270
|
+
const main = document.createElement('div');
|
|
2271
|
+
main.className = 'tree-toggle-main';
|
|
2272
|
+
|
|
2273
|
+
const kicker = document.createElement('div');
|
|
2274
|
+
kicker.className = opts.kickerClassName || 'workbench-group-kicker';
|
|
2275
|
+
kicker.textContent = opts.kicker || '';
|
|
2276
|
+
main.appendChild(kicker);
|
|
2277
|
+
|
|
2278
|
+
const title = document.createElement('div');
|
|
2279
|
+
title.className = opts.titleClassName || 'workbench-group-title';
|
|
2280
|
+
title.textContent = opts.title || '';
|
|
2281
|
+
title.title = opts.title || '';
|
|
2282
|
+
main.appendChild(title);
|
|
2283
|
+
|
|
2284
|
+
const metaText = createTreeMetaText(opts.metaParts);
|
|
2285
|
+
if (metaText) {
|
|
2286
|
+
const meta = document.createElement('div');
|
|
2287
|
+
meta.className = 'tree-toggle-meta';
|
|
2288
|
+
meta.textContent = metaText;
|
|
2289
|
+
main.appendChild(meta);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
const caret = document.createElement('span');
|
|
2293
|
+
caret.className = 'tree-toggle-caret';
|
|
2294
|
+
caret.setAttribute('aria-hidden', 'true');
|
|
2295
|
+
caret.textContent = '›';
|
|
2296
|
+
|
|
2297
|
+
button.appendChild(main);
|
|
2298
|
+
button.appendChild(caret);
|
|
2299
|
+
|
|
2300
|
+
if (typeof opts.onClick === 'function') {
|
|
2301
|
+
button.addEventListener('click', opts.onClick);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
return button;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
function createSidebarIcon(name) {
|
|
2308
|
+
const icon = document.createElement('span');
|
|
2309
|
+
icon.className = `sidebar-icon ${name ? `sidebar-icon-${name}` : ''}`.trim();
|
|
2310
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
2311
|
+
|
|
2312
|
+
if (name === 'cube') {
|
|
2313
|
+
icon.innerHTML = [
|
|
2314
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">',
|
|
2315
|
+
'<path d="M12 3.5 4.75 7.5 12 11.5l7.25-4-7.25-4Z"></path>',
|
|
2316
|
+
'<path d="M4.75 7.5v9L12 20.5l7.25-4v-9"></path>',
|
|
2317
|
+
'<path d="M12 11.5v9"></path>',
|
|
2318
|
+
'</svg>'
|
|
2319
|
+
].join('');
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
return icon;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function createSidebarBadge(text, className) {
|
|
2326
|
+
const badge = document.createElement('span');
|
|
2327
|
+
badge.className = `sidebar-badge ${className || ''}`.trim();
|
|
2328
|
+
badge.textContent = text || '';
|
|
2329
|
+
return badge;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
function createContainerToggle(containerGroup, expanded) {
|
|
2333
|
+
const status = sessionStatusInfo(containerGroup && containerGroup.status);
|
|
2334
|
+
const button = document.createElement('button');
|
|
2335
|
+
button.type = 'button';
|
|
2336
|
+
button.className = 'tree-toggle container-toggle';
|
|
2337
|
+
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
2338
|
+
|
|
2339
|
+
const main = document.createElement('div');
|
|
2340
|
+
main.className = 'container-toggle-main';
|
|
2341
|
+
|
|
2342
|
+
const titleRow = document.createElement('div');
|
|
2343
|
+
titleRow.className = 'container-title-row';
|
|
2344
|
+
titleRow.appendChild(createSidebarIcon('cube'));
|
|
2345
|
+
|
|
2346
|
+
const titleStack = document.createElement('div');
|
|
2347
|
+
titleStack.className = 'container-title-stack';
|
|
2348
|
+
|
|
2349
|
+
const kicker = document.createElement('div');
|
|
2350
|
+
kicker.className = 'container-card-kicker';
|
|
2351
|
+
kicker.textContent = '容器';
|
|
2352
|
+
|
|
2353
|
+
const title = document.createElement('div');
|
|
2354
|
+
title.className = 'container-card-title';
|
|
2355
|
+
title.textContent = containerGroup && containerGroup.containerName ? containerGroup.containerName : '';
|
|
2356
|
+
title.title = title.textContent;
|
|
2357
|
+
|
|
2358
|
+
titleStack.appendChild(kicker);
|
|
2359
|
+
titleStack.appendChild(title);
|
|
2360
|
+
titleRow.appendChild(titleStack);
|
|
2361
|
+
main.appendChild(titleRow);
|
|
2362
|
+
|
|
2363
|
+
const meta = document.createElement('div');
|
|
2364
|
+
meta.className = 'container-card-meta';
|
|
2365
|
+
meta.appendChild(createSidebarBadge(status.label, `session-status ${status.tone} container-status-pill`));
|
|
2366
|
+
meta.appendChild(createSidebarBadge(`${containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions.length : 0} 个 AGENT`, 'container-agent-badge'));
|
|
2367
|
+
main.appendChild(meta);
|
|
2368
|
+
|
|
2369
|
+
const infoText = createTreeMetaText([
|
|
2370
|
+
containerGroup && containerGroup.image ? containerGroup.image : '',
|
|
2371
|
+
formatDateTime(containerGroup && containerGroup.updatedAt) || '暂无更新'
|
|
2372
|
+
]);
|
|
2373
|
+
if (infoText) {
|
|
2374
|
+
const info = document.createElement('div');
|
|
2375
|
+
info.className = 'container-card-info';
|
|
2376
|
+
info.textContent = infoText;
|
|
2377
|
+
info.title = infoText;
|
|
2378
|
+
main.appendChild(info);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
const caret = document.createElement('span');
|
|
2382
|
+
caret.className = 'tree-toggle-caret';
|
|
2383
|
+
caret.setAttribute('aria-hidden', 'true');
|
|
2384
|
+
caret.textContent = '›';
|
|
2385
|
+
|
|
2386
|
+
button.appendChild(main);
|
|
2387
|
+
button.appendChild(caret);
|
|
2388
|
+
|
|
2389
|
+
return button;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2054
2392
|
function createAgentRow(session, index) {
|
|
2055
2393
|
const status = sessionStatusInfo(session.status);
|
|
2056
2394
|
const btn = document.createElement('button');
|
|
@@ -2091,6 +2429,19 @@
|
|
|
2091
2429
|
return btn;
|
|
2092
2430
|
}
|
|
2093
2431
|
|
|
2432
|
+
function scrollActiveSessionIntoView() {
|
|
2433
|
+
if (!state.pendingActiveSessionScroll || !state.active) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
state.pendingActiveSessionScroll = false;
|
|
2437
|
+
const targetNode = state.sessionNodeMap.get(state.active);
|
|
2438
|
+
if (targetNode && typeof targetNode.scrollIntoView === 'function') {
|
|
2439
|
+
targetNode.scrollIntoView({
|
|
2440
|
+
block: 'nearest'
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2094
2445
|
function renderSessions() {
|
|
2095
2446
|
const containerCount = new Set(state.sessions.map(function (session) {
|
|
2096
2447
|
return session && session.containerName ? session.containerName : '';
|
|
@@ -2123,38 +2474,52 @@
|
|
|
2123
2474
|
let itemIndex = 0;
|
|
2124
2475
|
|
|
2125
2476
|
grouped.forEach(function (directoryGroup) {
|
|
2477
|
+
const directoryExpanded = isDirectoryExpanded(directoryGroup);
|
|
2478
|
+
const directoryHasActive = directoryContainsActiveSession(directoryGroup);
|
|
2126
2479
|
const group = document.createElement('section');
|
|
2127
2480
|
group.className = 'workbench-group';
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
groupHead
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2481
|
+
group.classList.toggle('has-active', directoryHasActive);
|
|
2482
|
+
|
|
2483
|
+
const groupHead = createTreeToggle({
|
|
2484
|
+
className: 'workbench-group-head workbench-path-bar',
|
|
2485
|
+
kickerClassName: 'workbench-group-kicker',
|
|
2486
|
+
titleClassName: 'workbench-group-title',
|
|
2487
|
+
kicker: '路径分组',
|
|
2488
|
+
title: directoryGroup.path,
|
|
2489
|
+
expanded: directoryExpanded,
|
|
2490
|
+
metaParts: [
|
|
2491
|
+
`${directoryGroup.containers.length} 容器`,
|
|
2492
|
+
`${directoryGroup.containers.reduce(function (count, containerGroup) {
|
|
2493
|
+
return count + containerGroup.sessions.length;
|
|
2494
|
+
}, 0)} AGENT`,
|
|
2495
|
+
formatDateTime(directoryGroup.updatedAt) || '暂无更新'
|
|
2496
|
+
],
|
|
2497
|
+
onClick: function () {
|
|
2498
|
+
setSidebarDirectoryExpanded(directoryGroup.path, !directoryExpanded);
|
|
2499
|
+
renderSessions();
|
|
2500
|
+
}
|
|
2501
|
+
});
|
|
2135
2502
|
group.appendChild(groupHead);
|
|
2136
2503
|
|
|
2137
2504
|
const containerStack = document.createElement('div');
|
|
2138
|
-
containerStack.className = 'container-stack';
|
|
2505
|
+
containerStack.className = 'container-stack workbench-group-body';
|
|
2506
|
+
containerStack.hidden = !directoryExpanded;
|
|
2139
2507
|
|
|
2140
|
-
|
|
2141
|
-
const
|
|
2508
|
+
directoryGroup.containers.forEach(function (containerGroup) {
|
|
2509
|
+
const containerExpanded = isContainerExpanded(containerGroup);
|
|
2510
|
+
const containerHasActive = containerContainsActiveSession(containerGroup);
|
|
2142
2511
|
const containerCard = document.createElement('section');
|
|
2143
2512
|
containerCard.className = 'container-card';
|
|
2513
|
+
containerCard.classList.toggle('has-active', containerHasActive);
|
|
2144
2514
|
|
|
2145
2515
|
const containerHead = document.createElement('div');
|
|
2146
2516
|
containerHead.className = 'container-card-head';
|
|
2147
2517
|
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
<div class="container-card-meta">
|
|
2154
|
-
<span class="session-status ${status.tone}">${escapeHtml(status.label)}</span>
|
|
2155
|
-
<span>${escapeHtml(formatDateTime(containerGroup.updatedAt) || '暂无更新')}</span>
|
|
2156
|
-
</div>
|
|
2157
|
-
`;
|
|
2518
|
+
const containerToggle = createContainerToggle(containerGroup, containerExpanded);
|
|
2519
|
+
containerToggle.addEventListener('click', function () {
|
|
2520
|
+
setSidebarContainerExpanded(containerGroup.containerName, !containerExpanded);
|
|
2521
|
+
renderSessions();
|
|
2522
|
+
});
|
|
2158
2523
|
|
|
2159
2524
|
const addAgentBtn = document.createElement('button');
|
|
2160
2525
|
addAgentBtn.type = 'button';
|
|
@@ -2164,12 +2529,13 @@
|
|
|
2164
2529
|
createAgentSession(containerGroup.containerName);
|
|
2165
2530
|
});
|
|
2166
2531
|
|
|
2167
|
-
containerHead.appendChild(
|
|
2168
|
-
containerHead.appendChild(addAgentBtn);
|
|
2532
|
+
containerHead.appendChild(containerToggle);
|
|
2169
2533
|
containerCard.appendChild(containerHead);
|
|
2170
2534
|
|
|
2171
2535
|
const agentList = document.createElement('div');
|
|
2172
|
-
agentList.className = 'agent-list';
|
|
2536
|
+
agentList.className = 'agent-list container-card-body';
|
|
2537
|
+
agentList.hidden = !containerExpanded;
|
|
2538
|
+
agentList.appendChild(addAgentBtn);
|
|
2173
2539
|
containerGroup.sessions.forEach(function (session) {
|
|
2174
2540
|
const row = createAgentRow(session, itemIndex);
|
|
2175
2541
|
state.sessionNodeMap.set(session.name, row);
|
|
@@ -2183,6 +2549,8 @@
|
|
|
2183
2549
|
group.appendChild(containerStack);
|
|
2184
2550
|
sessionList.appendChild(group);
|
|
2185
2551
|
});
|
|
2552
|
+
|
|
2553
|
+
scrollActiveSessionIntoView();
|
|
2186
2554
|
}
|
|
2187
2555
|
|
|
2188
2556
|
function renderMessagesLoading() {
|
|
@@ -2216,6 +2584,11 @@
|
|
|
2216
2584
|
}
|
|
2217
2585
|
|
|
2218
2586
|
function getMessageRenderKey(msg, index) {
|
|
2587
|
+
if (msg && msg.id && msg.streamingReply) {
|
|
2588
|
+
const content = msg.content ? String(msg.content) : '';
|
|
2589
|
+
const timestamp = msg.timestamp ? String(msg.timestamp) : '';
|
|
2590
|
+
return `id:${msg.id}|streaming|${timestamp}|${content}`;
|
|
2591
|
+
}
|
|
2219
2592
|
if (msg && msg.id && msg.streamTrace) {
|
|
2220
2593
|
const content = msg.content ? String(msg.content) : '';
|
|
2221
2594
|
const timestamp = msg.timestamp ? String(msg.timestamp) : '';
|
|
@@ -2267,14 +2640,39 @@
|
|
|
2267
2640
|
const bubble = document.createElement('div');
|
|
2268
2641
|
bubble.className = 'bubble';
|
|
2269
2642
|
|
|
2643
|
+
const isStreamingReply = Boolean(msg && msg.streamingReply);
|
|
2270
2644
|
const shouldRenderStructuredTrace = Boolean(
|
|
2271
2645
|
msg
|
|
2272
2646
|
&& msg.streamTrace
|
|
2273
2647
|
&& Array.isArray(msg.traceEvents)
|
|
2274
2648
|
&& msg.traceEvents.length
|
|
2275
2649
|
);
|
|
2276
|
-
const shouldRenderMarkdown = Boolean(!msg.streamTrace && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
|
|
2277
|
-
if (
|
|
2650
|
+
const shouldRenderMarkdown = Boolean(!msg.streamTrace && !msg.streamingReply && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
|
|
2651
|
+
if (isStreamingReply) {
|
|
2652
|
+
bubble.classList.add('streaming-reply');
|
|
2653
|
+
var replyContent = String(msg.content || '');
|
|
2654
|
+
if (replyContent && markdownRenderer && typeof markdownRenderer.render === 'function') {
|
|
2655
|
+
var mdNode = document.createElement('div');
|
|
2656
|
+
mdNode.className = 'md-content';
|
|
2657
|
+
var rendered = '';
|
|
2658
|
+
try {
|
|
2659
|
+
rendered = String(markdownRenderer.render(replyContent) || '');
|
|
2660
|
+
} catch (e) {
|
|
2661
|
+
rendered = '';
|
|
2662
|
+
}
|
|
2663
|
+
if (rendered) {
|
|
2664
|
+
mdNode.innerHTML = rendered;
|
|
2665
|
+
bubble.appendChild(mdNode);
|
|
2666
|
+
} else {
|
|
2667
|
+
appendPlainMessageContent(bubble, replyContent);
|
|
2668
|
+
}
|
|
2669
|
+
} else {
|
|
2670
|
+
appendPlainMessageContent(bubble, replyContent);
|
|
2671
|
+
}
|
|
2672
|
+
var cursor = document.createElement('span');
|
|
2673
|
+
cursor.className = 'streaming-cursor';
|
|
2674
|
+
bubble.appendChild(cursor);
|
|
2675
|
+
} else if (shouldRenderStructuredTrace) {
|
|
2278
2676
|
appendStructuredTraceContent(bubble, msg);
|
|
2279
2677
|
} else if (shouldRenderMarkdown) {
|
|
2280
2678
|
const markdownNode = document.createElement('div');
|
|
@@ -2365,7 +2763,9 @@
|
|
|
2365
2763
|
}
|
|
2366
2764
|
|
|
2367
2765
|
function applySessionsSnapshot(rawSessions, preferredName) {
|
|
2766
|
+
const previousActive = state.active;
|
|
2368
2767
|
state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
|
|
2768
|
+
pruneSidebarTreeState();
|
|
2369
2769
|
|
|
2370
2770
|
if (typeof preferredName === 'string' && preferredName.trim()) {
|
|
2371
2771
|
state.active = preferredName.trim();
|
|
@@ -2379,6 +2779,10 @@
|
|
|
2379
2779
|
if (!state.active && state.sessions.length) {
|
|
2380
2780
|
state.active = state.sessions[0].name;
|
|
2381
2781
|
}
|
|
2782
|
+
if (state.active && state.active !== previousActive) {
|
|
2783
|
+
ensureSessionPathExpanded(state.active);
|
|
2784
|
+
state.pendingActiveSessionScroll = true;
|
|
2785
|
+
}
|
|
2382
2786
|
if (state.terminal.sessionName && state.terminal.sessionName !== state.active) {
|
|
2383
2787
|
disconnectTerminal('会话已变化,终端已断开', true);
|
|
2384
2788
|
}
|
|
@@ -2638,11 +3042,55 @@
|
|
|
2638
3042
|
});
|
|
2639
3043
|
}
|
|
2640
3044
|
|
|
3045
|
+
function appendStreamingReplyLocal(sessionName) {
|
|
3046
|
+
var replyMessage = {
|
|
3047
|
+
id: createLocalMessageId('local-streaming-reply'),
|
|
3048
|
+
role: 'assistant',
|
|
3049
|
+
content: '',
|
|
3050
|
+
timestamp: new Date().toISOString(),
|
|
3051
|
+
mode: 'agent',
|
|
3052
|
+
streamingReply: true
|
|
3053
|
+
};
|
|
3054
|
+
if (state.active === sessionName) {
|
|
3055
|
+
state.messages.push(replyMessage);
|
|
3056
|
+
}
|
|
3057
|
+
return replyMessage.id;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
function updateStreamingReplyLocal(sessionName, replyMessageId, content) {
|
|
3061
|
+
if (state.active !== sessionName) {
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
for (var i = state.messages.length - 1; i >= 0; i -= 1) {
|
|
3065
|
+
var message = state.messages[i];
|
|
3066
|
+
if (!message || String(message.id || '') !== String(replyMessageId || '')) {
|
|
3067
|
+
continue;
|
|
3068
|
+
}
|
|
3069
|
+
message.content = String(content || '');
|
|
3070
|
+
message.timestamp = new Date().toISOString();
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function removeStreamingReplyLocal(sessionName, replyMessageId) {
|
|
3076
|
+
if (state.active !== sessionName) {
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
for (var i = state.messages.length - 1; i >= 0; i -= 1) {
|
|
3080
|
+
var message = state.messages[i];
|
|
3081
|
+
if (message && String(message.id || '') === String(replyMessageId || '') && message.streamingReply) {
|
|
3082
|
+
state.messages.splice(i, 1);
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
|
|
2641
3088
|
async function sendAgentPromptStream(sessionName, inputText, pendingMessage) {
|
|
2642
3089
|
const traceMessageId = appendAgentTraceMessageLocal(sessionName);
|
|
2643
3090
|
const traceLines = ['[执行过程]', '等待 Agent 启动…'];
|
|
2644
3091
|
let finalResult = null;
|
|
2645
3092
|
let streamError = null;
|
|
3093
|
+
let streamingReplyId = null;
|
|
2646
3094
|
|
|
2647
3095
|
state.agentRun.active = true;
|
|
2648
3096
|
state.agentRun.sessionName = sessionName;
|
|
@@ -2692,6 +3140,20 @@
|
|
|
2692
3140
|
pushTraceLine(event.text || '', event.traceEvent || null);
|
|
2693
3141
|
return;
|
|
2694
3142
|
}
|
|
3143
|
+
if (event.type === 'content_delta') {
|
|
3144
|
+
var content = String(event.content || '').trim();
|
|
3145
|
+
if (!content) {
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (!streamingReplyId) {
|
|
3149
|
+
streamingReplyId = appendStreamingReplyLocal(sessionName);
|
|
3150
|
+
}
|
|
3151
|
+
updateStreamingReplyLocal(sessionName, streamingReplyId, content);
|
|
3152
|
+
if (state.active === sessionName) {
|
|
3153
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
3154
|
+
}
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
2695
3157
|
if (event.type === 'result') {
|
|
2696
3158
|
finalResult = event;
|
|
2697
3159
|
if (event.interrupted) {
|
|
@@ -2711,6 +3173,9 @@
|
|
|
2711
3173
|
streamError = e;
|
|
2712
3174
|
}
|
|
2713
3175
|
} finally {
|
|
3176
|
+
if (streamingReplyId) {
|
|
3177
|
+
removeStreamingReplyLocal(sessionName, streamingReplyId);
|
|
3178
|
+
}
|
|
2714
3179
|
const pendingIndex = confirmPendingUserMessage(sessionName, pendingMessage.id);
|
|
2715
3180
|
if (pendingIndex >= 0 && pendingIndex < state.messageRenderKeys.length) {
|
|
2716
3181
|
if (pendingIndex < messagesNode.children.length) {
|
|
@@ -3099,6 +3564,18 @@
|
|
|
3099
3564
|
});
|
|
3100
3565
|
}
|
|
3101
3566
|
|
|
3567
|
+
if (addAgentBtn) {
|
|
3568
|
+
addAgentBtn.addEventListener('click', function () {
|
|
3569
|
+
const activeSession = getActiveSession();
|
|
3570
|
+
const targetContainer = activeSession ? getSessionContainerName(activeSession) : '';
|
|
3571
|
+
if (!targetContainer) {
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
closeMobileActionsMenu();
|
|
3575
|
+
createAgentSession(targetContainer);
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3102
3579
|
if (configModal) {
|
|
3103
3580
|
configModal.addEventListener('click', function (event) {
|
|
3104
3581
|
if (event.target === configModal && !state.configSaving) {
|
|
@@ -3223,6 +3700,7 @@
|
|
|
3223
3700
|
disconnectTerminal('', true);
|
|
3224
3701
|
});
|
|
3225
3702
|
|
|
3703
|
+
loadSidebarTreeState();
|
|
3226
3704
|
renderSessions();
|
|
3227
3705
|
renderMessages(state.messages);
|
|
3228
3706
|
setMobileSessionPanel(false);
|
package/lib/web/server.js
CHANGED
|
@@ -1053,6 +1053,48 @@ function prepareStructuredTraceEvents(agentProgram, payload, state) {
|
|
|
1053
1053
|
return [];
|
|
1054
1054
|
}
|
|
1055
1055
|
|
|
1056
|
+
function extractContentDeltaFromPayload(agentProgram, payload) {
|
|
1057
|
+
if (!payload || typeof payload !== 'object') {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
if (agentProgram === 'claude') {
|
|
1061
|
+
if (pickFirstString(payload.type) !== 'assistant') {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
const message = toPlainObject(payload.message);
|
|
1065
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
1066
|
+
const text = content
|
|
1067
|
+
.filter(item => item && typeof item === 'object' && item.type === 'text')
|
|
1068
|
+
.map(item => collectStructuredText(item))
|
|
1069
|
+
.filter(Boolean)
|
|
1070
|
+
.join('\n')
|
|
1071
|
+
.trim();
|
|
1072
|
+
if (!text) {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
return { text, reset: true };
|
|
1076
|
+
}
|
|
1077
|
+
if (agentProgram === 'gemini' || agentProgram === 'opencode') {
|
|
1078
|
+
const eventType = pickFirstString(payload.type);
|
|
1079
|
+
if (eventType !== 'message') {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
const role = pickFirstString(payload.role);
|
|
1083
|
+
if (role !== 'assistant') {
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
const text = collectStructuredText(payload.content);
|
|
1087
|
+
if (!text) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
if (payload.delta === true) {
|
|
1091
|
+
return { text, reset: false };
|
|
1092
|
+
}
|
|
1093
|
+
return { text, reset: true };
|
|
1094
|
+
}
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1056
1098
|
function prepareCodexTraceEvent(payload) {
|
|
1057
1099
|
if (!payload || typeof payload !== 'object') {
|
|
1058
1100
|
return null;
|
|
@@ -2281,6 +2323,7 @@ async function execAgentInWebContainerStream(ctx, state, sessionRefOrContainerNa
|
|
|
2281
2323
|
const structuredTraceState = {
|
|
2282
2324
|
toolNamesById: new Map()
|
|
2283
2325
|
};
|
|
2326
|
+
let contentDeltaAccumulator = '';
|
|
2284
2327
|
function appendChunk(chunk, target) {
|
|
2285
2328
|
if (!chunk) return;
|
|
2286
2329
|
const text = chunk.toString('utf-8');
|
|
@@ -2318,6 +2361,18 @@ async function execAgentInWebContainerStream(ctx, state, sessionRefOrContainerNa
|
|
|
2318
2361
|
traceEvent
|
|
2319
2362
|
});
|
|
2320
2363
|
});
|
|
2364
|
+
const deltaContent = extractContentDeltaFromPayload(agentProgram, payload, structuredTraceState);
|
|
2365
|
+
if (deltaContent !== null) {
|
|
2366
|
+
if (deltaContent.reset) {
|
|
2367
|
+
contentDeltaAccumulator = deltaContent.text;
|
|
2368
|
+
} else {
|
|
2369
|
+
contentDeltaAccumulator += deltaContent.text;
|
|
2370
|
+
}
|
|
2371
|
+
onEvent({
|
|
2372
|
+
type: 'content_delta',
|
|
2373
|
+
content: contentDeltaAccumulator
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2321
2376
|
return;
|
|
2322
2377
|
}
|
|
2323
2378
|
if (agentProgram === 'codex' && (/^OpenAI Codex\b/.test(rawLine) || /^tokens used\b/i.test(rawLine))) {
|