total-recall-brain 3.6.2 โ 3.6.4
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/extension/sidepanel/sidepanel.css +234 -0
- package/extension/sidepanel/sidepanel.html +72 -0
- package/extension/sidepanel/sidepanel.js +410 -0
- package/frontend/src/api.ts +7 -0
- package/frontend/src/pages/CollabPage.tsx +7 -7
- package/frontend/src/pages/DeploymentsPage.tsx +68 -59
- package/frontend/src/pages/HealthPage.tsx +45 -1
- package/package.json +1 -1
- package/src/server/rest.mjs +19 -0
- package/src/server/route-manifest.json +4 -0
|
@@ -735,3 +735,237 @@ body {
|
|
|
735
735
|
::-webkit-scrollbar-thumb:hover {
|
|
736
736
|
background: #45475a;
|
|
737
737
|
}
|
|
738
|
+
|
|
739
|
+
/* ==================== PANEL 5: COLLABORATION ==================== */
|
|
740
|
+
|
|
741
|
+
.collab-section {
|
|
742
|
+
display: flex;
|
|
743
|
+
flex-direction: column;
|
|
744
|
+
padding: 16px;
|
|
745
|
+
height: 100%;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.collab-subtext {
|
|
749
|
+
font-size: 12px;
|
|
750
|
+
color: #a6adc8;
|
|
751
|
+
margin-bottom: 16px;
|
|
752
|
+
line-height: 1.4;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
.collab-form {
|
|
756
|
+
display: flex;
|
|
757
|
+
flex-direction: column;
|
|
758
|
+
gap: 10px;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.collab-form input {
|
|
762
|
+
width: 100%;
|
|
763
|
+
padding: 10px 14px;
|
|
764
|
+
background: #1e1e2e;
|
|
765
|
+
border: 1px solid #313244;
|
|
766
|
+
border-radius: 10px;
|
|
767
|
+
color: #cdd6f4;
|
|
768
|
+
font-size: 14px;
|
|
769
|
+
outline: none;
|
|
770
|
+
transition: border-color 0.2s ease;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.collab-form input:focus {
|
|
774
|
+
border-color: #cba6f7;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.collab-user-bar {
|
|
778
|
+
display: flex;
|
|
779
|
+
justify-content: space-between;
|
|
780
|
+
align-items: center;
|
|
781
|
+
padding: 8px 10px;
|
|
782
|
+
background: #181825;
|
|
783
|
+
border: 1px solid #313244;
|
|
784
|
+
border-radius: 10px;
|
|
785
|
+
margin-bottom: 12px;
|
|
786
|
+
font-size: 13px;
|
|
787
|
+
color: #cdd6f4;
|
|
788
|
+
flex-shrink: 0;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.collab-logout-btn {
|
|
792
|
+
background: transparent;
|
|
793
|
+
border: none;
|
|
794
|
+
color: #f38ba8;
|
|
795
|
+
font-size: 11px;
|
|
796
|
+
font-weight: 600;
|
|
797
|
+
cursor: pointer;
|
|
798
|
+
padding: 2px 6px;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.collab-logout-btn:hover {
|
|
802
|
+
text-decoration: underline;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.collab-control-group {
|
|
806
|
+
display: flex;
|
|
807
|
+
flex-direction: column;
|
|
808
|
+
gap: 6px;
|
|
809
|
+
margin-bottom: 8px;
|
|
810
|
+
flex-shrink: 0;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
.collab-control-group label {
|
|
814
|
+
font-size: 10px;
|
|
815
|
+
font-weight: 700;
|
|
816
|
+
text-transform: uppercase;
|
|
817
|
+
color: #6c7086;
|
|
818
|
+
letter-spacing: 0.5px;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.collab-panel-header {
|
|
822
|
+
display: flex;
|
|
823
|
+
justify-content: space-between;
|
|
824
|
+
align-items: center;
|
|
825
|
+
margin-top: 12px;
|
|
826
|
+
margin-bottom: 6px;
|
|
827
|
+
flex-shrink: 0;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.collab-panel-header h4 {
|
|
831
|
+
font-size: 11px;
|
|
832
|
+
font-weight: 700;
|
|
833
|
+
text-transform: uppercase;
|
|
834
|
+
color: #6c7086;
|
|
835
|
+
letter-spacing: 0.5px;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.collab-list {
|
|
839
|
+
flex: 1;
|
|
840
|
+
min-height: 80px;
|
|
841
|
+
max-height: 140px;
|
|
842
|
+
overflow-y: auto;
|
|
843
|
+
border: 1px solid #313244;
|
|
844
|
+
background: #181825;
|
|
845
|
+
border-radius: 10px;
|
|
846
|
+
padding: 8px 10px;
|
|
847
|
+
display: flex;
|
|
848
|
+
flex-direction: column;
|
|
849
|
+
gap: 6px;
|
|
850
|
+
margin-bottom: 8px;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.collab-annotation-item {
|
|
854
|
+
background: #1e1e2e;
|
|
855
|
+
border: 1px solid #313244;
|
|
856
|
+
border-radius: 8px;
|
|
857
|
+
padding: 8px 10px;
|
|
858
|
+
font-size: 12px;
|
|
859
|
+
line-height: 1.4;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.collab-annotation-item .author {
|
|
863
|
+
font-size: 10px;
|
|
864
|
+
color: #89b4fa;
|
|
865
|
+
font-weight: 600;
|
|
866
|
+
margin-bottom: 2px;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.collab-annotation-item .text {
|
|
870
|
+
color: #cdd6f4;
|
|
871
|
+
word-break: break-word;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.collab-annotation-item .excerpt {
|
|
875
|
+
font-size: 11px;
|
|
876
|
+
color: #a6adc8;
|
|
877
|
+
font-style: italic;
|
|
878
|
+
border-left: 2px solid #89b4fa;
|
|
879
|
+
padding-left: 6px;
|
|
880
|
+
margin-bottom: 4px;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.collab-annotation-item .time {
|
|
884
|
+
font-size: 9px;
|
|
885
|
+
color: #585b70;
|
|
886
|
+
text-align: right;
|
|
887
|
+
margin-top: 4px;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.collab-annotation-form {
|
|
891
|
+
display: flex;
|
|
892
|
+
flex-direction: column;
|
|
893
|
+
flex-shrink: 0;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.collab-annotation-form textarea {
|
|
897
|
+
width: 100%;
|
|
898
|
+
height: 48px;
|
|
899
|
+
min-height: 48px;
|
|
900
|
+
padding: 6px 8px;
|
|
901
|
+
background: #1e1e2e;
|
|
902
|
+
border: 1px solid #313244;
|
|
903
|
+
border-radius: 8px;
|
|
904
|
+
color: #cdd6f4;
|
|
905
|
+
font-size: 12px;
|
|
906
|
+
resize: none;
|
|
907
|
+
outline: none;
|
|
908
|
+
font-family: inherit;
|
|
909
|
+
transition: border-color 0.2s ease;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.collab-annotation-form textarea:focus {
|
|
913
|
+
border-color: #cba6f7;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.collab-chat-box {
|
|
917
|
+
flex: 2;
|
|
918
|
+
min-height: 100px;
|
|
919
|
+
max-height: 200px;
|
|
920
|
+
border: 1px solid #313244;
|
|
921
|
+
background: #181825;
|
|
922
|
+
border-radius: 10px;
|
|
923
|
+
padding: 10px;
|
|
924
|
+
display: flex;
|
|
925
|
+
flex-direction: column;
|
|
926
|
+
gap: 8px;
|
|
927
|
+
overflow-y: auto;
|
|
928
|
+
margin-bottom: 6px;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.collab-input-bar {
|
|
932
|
+
padding: 0 !important;
|
|
933
|
+
border-top: none !important;
|
|
934
|
+
margin-bottom: 0 !important;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.collab-input-bar textarea {
|
|
938
|
+
height: 34px !important;
|
|
939
|
+
max-height: 34px !important;
|
|
940
|
+
font-size: 12px !important;
|
|
941
|
+
border-radius: 8px !important;
|
|
942
|
+
padding: 8px !important;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.collab-input-bar button {
|
|
946
|
+
height: 34px !important;
|
|
947
|
+
width: 50px !important;
|
|
948
|
+
border-radius: 8px !important;
|
|
949
|
+
font-size: 11px !important;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/* Pulse animation for presence badge */
|
|
953
|
+
.pulse {
|
|
954
|
+
animation: pulse-glow 2s infinite;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
@keyframes pulse-glow {
|
|
958
|
+
0% {
|
|
959
|
+
transform: scale(0.95);
|
|
960
|
+
box-shadow: 0 0 0 0 rgba(166, 227, 161, 0.7);
|
|
961
|
+
}
|
|
962
|
+
70% {
|
|
963
|
+
transform: scale(1);
|
|
964
|
+
box-shadow: 0 0 0 5px rgba(166, 227, 161, 0);
|
|
965
|
+
}
|
|
966
|
+
100% {
|
|
967
|
+
transform: scale(0.95);
|
|
968
|
+
box-shadow: 0 0 0 0 rgba(166, 227, 161, 0);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
<button class="tab-btn" data-tab="chat">๐ฌ Chat</button>
|
|
23
23
|
<button class="tab-btn" data-tab="research">๐ฌ Research</button>
|
|
24
24
|
<button class="tab-btn" data-tab="settings">โ๏ธ Settings</button>
|
|
25
|
+
<button class="tab-btn" data-tab="collab">๐ฅ Collab</button>
|
|
25
26
|
</nav>
|
|
26
27
|
|
|
27
28
|
<!-- Tab Panels Container -->
|
|
@@ -128,6 +129,77 @@
|
|
|
128
129
|
</div>
|
|
129
130
|
</section>
|
|
130
131
|
|
|
132
|
+
<!-- Panel 5: Collaboration -->
|
|
133
|
+
<section id="tab-collab" class="tab-panel">
|
|
134
|
+
<!-- Auth View -->
|
|
135
|
+
<div id="collab-auth-view" class="collab-section">
|
|
136
|
+
<h3>๐ฅ Join Collab Network</h3>
|
|
137
|
+
<p class="collab-subtext">Register or log in to sync annotations and chat on websites live.</p>
|
|
138
|
+
<div class="collab-form">
|
|
139
|
+
<input type="text" id="collab-username" placeholder="Username" />
|
|
140
|
+
<input type="password" id="collab-password" placeholder="Password" />
|
|
141
|
+
<div style="display: flex; gap: 8px; margin-top: 8px;">
|
|
142
|
+
<button id="btn-collab-login" class="action-btn accent" style="flex: 1;">Login</button>
|
|
143
|
+
<button id="btn-collab-register" class="action-btn" style="flex: 1;">Register</button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<!-- Main Collaboration View -->
|
|
149
|
+
<div id="collab-main-view" class="collab-section hidden" style="display: flex; flex-direction: column; height: 100%;">
|
|
150
|
+
<div class="collab-user-bar">
|
|
151
|
+
<span>๐ค <strong id="collab-user-display"></strong></span>
|
|
152
|
+
<button id="btn-collab-logout" class="collab-logout-btn">Logout</button>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div class="collab-control-group">
|
|
156
|
+
<label for="collab-group-select">Workspace Group</label>
|
|
157
|
+
<select id="collab-group-select" class="settings-select"></select>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="collab-action-grid" style="display: flex; gap: 8px; margin-bottom: 8px; margin-top: 4px;">
|
|
161
|
+
<button id="btn-collab-show-join" class="action-btn" style="padding: 6px; font-size: 11px;">Join Group</button>
|
|
162
|
+
<button id="btn-collab-show-create" class="action-btn" style="padding: 6px; font-size: 11px;">Create Group</button>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div id="collab-join-group-row" class="collab-action-row hidden" style="display: flex; gap: 6px; margin-bottom: 8px;">
|
|
166
|
+
<input type="text" id="collab-join-code" placeholder="Invite Code" style="flex: 1; padding: 6px; font-size: 12px; background: #1e1e2e; border: 1px solid #313244; color: #cdd6f4; border-radius: 6px;" />
|
|
167
|
+
<button id="btn-collab-submit-join" class="action-btn accent" style="flex-shrink: 0; padding: 6px 12px; font-size: 11px; height: 28px;">Join</button>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div id="collab-create-group-row" class="collab-action-row hidden" style="display: flex; gap: 6px; margin-bottom: 8px;">
|
|
171
|
+
<input type="text" id="collab-create-name" placeholder="Group Name" style="flex: 1; padding: 6px; font-size: 12px; background: #1e1e2e; border: 1px solid #313244; color: #cdd6f4; border-radius: 6px;" />
|
|
172
|
+
<button id="btn-collab-submit-create" class="action-btn accent" style="flex-shrink: 0; padding: 6px 12px; font-size: 11px; height: 28px;">Create</button>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<!-- Pinned Annotations -->
|
|
176
|
+
<div class="collab-panel-header">
|
|
177
|
+
<h4>๐ Notes on Page</h4>
|
|
178
|
+
</div>
|
|
179
|
+
<div id="collab-annotations-list" class="collab-list"></div>
|
|
180
|
+
|
|
181
|
+
<!-- Add Annotation Form -->
|
|
182
|
+
<div class="collab-annotation-form">
|
|
183
|
+
<textarea id="collab-note-input" placeholder="Pin a note to this page..."></textarea>
|
|
184
|
+
<button id="btn-collab-save-note" class="action-btn accent" style="width: 100%; margin-top: 4px; padding: 6px; font-size: 12px;">Pin Note</button>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<!-- Real-time Site Chat -->
|
|
188
|
+
<div class="collab-panel-header" style="margin-top: 8px;">
|
|
189
|
+
<h4>๐ฌ Site Chat Room</h4>
|
|
190
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
191
|
+
<span class="pulse" style="width: 6px; height: 6px; background: #a6e3a1; box-shadow: 0 0 4px #a6e3a1; border-radius: 50%;"></span>
|
|
192
|
+
<span id="collab-presence-count" style="font-size: 10px; color: #a6e3a1;">1 online</span>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
<div id="collab-chat-messages" class="chat-messages collab-chat-box"></div>
|
|
196
|
+
<div class="chat-input-bar collab-input-bar">
|
|
197
|
+
<textarea id="collab-chat-input" placeholder="Send a message to team..."></textarea>
|
|
198
|
+
<button id="btn-collab-send-chat" class="chat-send-btn">Send</button>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</section>
|
|
202
|
+
|
|
131
203
|
</div>
|
|
132
204
|
</div>
|
|
133
205
|
<script src="../lib/preconfigured.js"></script>
|
|
@@ -676,6 +676,413 @@ Provide helpful analysis grounded on both the user's brain memory and this page
|
|
|
676
676
|
}
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
+
// ==================== COLLABORATION MODULE ====================
|
|
680
|
+
const collabAuthView = document.getElementById('collab-auth-view');
|
|
681
|
+
const collabMainView = document.getElementById('collab-main-view');
|
|
682
|
+
const collabUsernameInput = document.getElementById('collab-username');
|
|
683
|
+
const collabPasswordInput = document.getElementById('collab-password');
|
|
684
|
+
const btnCollabLogin = document.getElementById('btn-collab-login');
|
|
685
|
+
const btnCollabRegister = document.getElementById('btn-collab-register');
|
|
686
|
+
|
|
687
|
+
const collabUserDisplay = document.getElementById('collab-user-display');
|
|
688
|
+
const btnCollabLogout = document.getElementById('btn-collab-logout');
|
|
689
|
+
const collabGroupSelect = document.getElementById('collab-group-select');
|
|
690
|
+
|
|
691
|
+
const btnCollabShowJoin = document.getElementById('btn-collab-show-join');
|
|
692
|
+
const btnCollabShowCreate = document.getElementById('btn-collab-show-create');
|
|
693
|
+
const collabJoinGroupRow = document.getElementById('collab-join-group-row');
|
|
694
|
+
const collabJoinCode = document.getElementById('collab-join-code');
|
|
695
|
+
const btnCollabSubmitJoin = document.getElementById('btn-collab-submit-join');
|
|
696
|
+
|
|
697
|
+
const collabCreateGroupRow = document.getElementById('collab-create-group-row');
|
|
698
|
+
const collabCreateName = document.getElementById('collab-create-name');
|
|
699
|
+
const btnCollabSubmitCreate = document.getElementById('btn-collab-submit-create');
|
|
700
|
+
|
|
701
|
+
const collabAnnotationsList = document.getElementById('collab-annotations-list');
|
|
702
|
+
const collabNoteInput = document.getElementById('collab-note-input');
|
|
703
|
+
const btnCollabSaveNote = document.getElementById('btn-collab-save-note');
|
|
704
|
+
|
|
705
|
+
const collabPresenceCount = document.getElementById('collab-presence-count');
|
|
706
|
+
const collabChatMessages = document.getElementById('collab-chat-messages');
|
|
707
|
+
const collabChatInput = document.getElementById('collab-chat-input');
|
|
708
|
+
const btnCollabSendChat = document.getElementById('btn-collab-send-chat');
|
|
709
|
+
|
|
710
|
+
let collabToken = '';
|
|
711
|
+
let collabUsername = '';
|
|
712
|
+
let collabActiveUrl = '';
|
|
713
|
+
let collabSocket = null;
|
|
714
|
+
let collabGroups = [];
|
|
715
|
+
let collabSelectedGroup = null;
|
|
716
|
+
|
|
717
|
+
async function initCollab() {
|
|
718
|
+
const data = await chrome.storage.local.get(['collabToken', 'collabUsername']);
|
|
719
|
+
if (data.collabToken && data.collabUsername) {
|
|
720
|
+
collabToken = data.collabToken;
|
|
721
|
+
collabUsername = data.collabUsername;
|
|
722
|
+
showCollabMain();
|
|
723
|
+
} else {
|
|
724
|
+
showCollabAuth();
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function showCollabAuth() {
|
|
729
|
+
collabAuthView.classList.remove('hidden');
|
|
730
|
+
collabMainView.classList.add('hidden');
|
|
731
|
+
closeCollabSocket();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async function showCollabMain() {
|
|
735
|
+
collabAuthView.classList.add('hidden');
|
|
736
|
+
collabMainView.classList.remove('hidden');
|
|
737
|
+
collabUserDisplay.textContent = collabUsername;
|
|
738
|
+
await fetchCollabGroups();
|
|
739
|
+
updateCollabActiveUrl();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async function fetchCollabGroups() {
|
|
743
|
+
try {
|
|
744
|
+
const config = await self.BrainClient.getConfig();
|
|
745
|
+
const res = await fetch(`${config.brainUrl}/api/collab/groups`, {
|
|
746
|
+
headers: { 'Authorization': `Bearer ${collabToken}` }
|
|
747
|
+
});
|
|
748
|
+
if (res.ok) {
|
|
749
|
+
collabGroups = await res.json();
|
|
750
|
+
renderCollabGroupsDropdown();
|
|
751
|
+
} else if (res.status === 401) {
|
|
752
|
+
logoutCollab();
|
|
753
|
+
}
|
|
754
|
+
} catch (err) {
|
|
755
|
+
console.error('Failed to fetch collab groups:', err);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function renderCollabGroupsDropdown() {
|
|
760
|
+
collabGroupSelect.innerHTML = collabGroups.map(g =>
|
|
761
|
+
`<option value="${g.code}">${escapeHtml(g.name)} (${g.code})</option>`
|
|
762
|
+
).join('');
|
|
763
|
+
if (collabGroups.length > 0) {
|
|
764
|
+
const activeCode = collabGroupSelect.value;
|
|
765
|
+
collabSelectedGroup = collabGroups.find(g => g.code === activeCode) || collabGroups[0];
|
|
766
|
+
} else {
|
|
767
|
+
collabSelectedGroup = null;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async function updateCollabActiveUrl() {
|
|
772
|
+
try {
|
|
773
|
+
const tab = await getCurrentTab();
|
|
774
|
+
if (tab && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('about:') && !tab.url.startsWith('chrome-extension://')) {
|
|
775
|
+
const cleanUrl = tab.url.split('#')[0];
|
|
776
|
+
if (cleanUrl !== collabActiveUrl) {
|
|
777
|
+
collabActiveUrl = cleanUrl;
|
|
778
|
+
if (collabToken) {
|
|
779
|
+
await fetchCollabAnnotations();
|
|
780
|
+
connectCollabSocket();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
} else {
|
|
784
|
+
collabActiveUrl = '';
|
|
785
|
+
collabAnnotationsList.innerHTML = '<div class="memories-empty">No active web tab</div>';
|
|
786
|
+
closeCollabSocket();
|
|
787
|
+
}
|
|
788
|
+
} catch (err) {
|
|
789
|
+
console.error(err);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
async function fetchCollabAnnotations() {
|
|
794
|
+
if (!collabActiveUrl || !collabToken) return;
|
|
795
|
+
try {
|
|
796
|
+
const config = await self.BrainClient.getConfig();
|
|
797
|
+
const res = await fetch(`${config.brainUrl}/api/collab/annotations?url=${encodeURIComponent(collabActiveUrl)}`, {
|
|
798
|
+
headers: { 'Authorization': `Bearer ${collabToken}` }
|
|
799
|
+
});
|
|
800
|
+
if (res.ok) {
|
|
801
|
+
const annotations = await res.json();
|
|
802
|
+
renderCollabAnnotations(annotations);
|
|
803
|
+
}
|
|
804
|
+
} catch (err) {
|
|
805
|
+
console.error('Failed to fetch annotations:', err);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function renderCollabAnnotations(list) {
|
|
810
|
+
if (!list.length) {
|
|
811
|
+
collabAnnotationsList.innerHTML = '<div class="memories-empty">No notes on this page</div>';
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
collabAnnotationsList.innerHTML = list.map(a => `
|
|
815
|
+
<div class="collab-annotation-item">
|
|
816
|
+
<div class="author">๐ค ${escapeHtml(a.author)}</div>
|
|
817
|
+
${a.excerpt ? `<div class="excerpt">${escapeHtml(a.excerpt)}</div>` : ''}
|
|
818
|
+
<div class="text">${escapeHtml(a.text)}</div>
|
|
819
|
+
<div class="time">${formatTime(a.created_at)}</div>
|
|
820
|
+
</div>
|
|
821
|
+
`).join('');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function connectCollabSocket() {
|
|
825
|
+
if (!collabToken || !collabActiveUrl) return;
|
|
826
|
+
closeCollabSocket();
|
|
827
|
+
|
|
828
|
+
self.BrainClient.getConfig().then(config => {
|
|
829
|
+
const serverUrl = new URL(config.brainUrl);
|
|
830
|
+
const wsProto = serverUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
831
|
+
const wsUrl = `${wsProto}//${serverUrl.host}/collab-ws?token=${collabToken}`;
|
|
832
|
+
|
|
833
|
+
const ws = new WebSocket(wsUrl);
|
|
834
|
+
collabSocket = ws;
|
|
835
|
+
|
|
836
|
+
ws.onopen = () => {
|
|
837
|
+
ws.send(JSON.stringify({ type: 'SUBSCRIBE', url: collabActiveUrl }));
|
|
838
|
+
collabPresenceCount.textContent = 'Connected';
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
ws.onmessage = (event) => {
|
|
842
|
+
try {
|
|
843
|
+
const data = JSON.parse(event.data);
|
|
844
|
+
if (data.type === 'CHAT_MESSAGE') {
|
|
845
|
+
appendCollabChatBubble(data.username, data.text, data.created_at);
|
|
846
|
+
} else if (data.type === 'USER_JOINED') {
|
|
847
|
+
appendCollabChatBubble('System', `${data.username} joined this page`, new Date().toISOString(), true);
|
|
848
|
+
} else if (data.type === 'USER_LEFT') {
|
|
849
|
+
appendCollabChatBubble('System', `${data.username} left`, new Date().toISOString(), true);
|
|
850
|
+
} else if (data.type === 'ANNOTATION_ADDED') {
|
|
851
|
+
fetchCollabAnnotations();
|
|
852
|
+
}
|
|
853
|
+
} catch (err) {
|
|
854
|
+
console.error(err);
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
ws.onclose = () => {
|
|
859
|
+
collabPresenceCount.textContent = 'Disconnected';
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
ws.onerror = () => {
|
|
863
|
+
collabPresenceCount.textContent = 'Error';
|
|
864
|
+
};
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function closeCollabSocket() {
|
|
869
|
+
if (collabSocket) {
|
|
870
|
+
try {
|
|
871
|
+
collabSocket.close();
|
|
872
|
+
} catch {}
|
|
873
|
+
collabSocket = null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function appendCollabChatBubble(sender, text, timestamp, isSystem = false) {
|
|
878
|
+
const bubble = document.createElement('div');
|
|
879
|
+
if (isSystem) {
|
|
880
|
+
bubble.className = 'chat-bubble system';
|
|
881
|
+
bubble.textContent = text;
|
|
882
|
+
} else {
|
|
883
|
+
const isMe = sender.toLowerCase() === collabUsername.toLowerCase();
|
|
884
|
+
bubble.className = `chat-bubble ${isMe ? 'user' : 'assistant'}`;
|
|
885
|
+
bubble.innerHTML = `<span style="font-size: 10px; opacity: 0.8; font-weight: bold; display: block; margin-bottom: 2px;">${escapeHtml(sender)}</span>${escapeHtml(text)}`;
|
|
886
|
+
}
|
|
887
|
+
collabChatMessages.appendChild(bubble);
|
|
888
|
+
collabChatMessages.scrollTop = collabChatMessages.scrollHeight;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async function registerCollab(username, password) {
|
|
892
|
+
try {
|
|
893
|
+
const config = await self.BrainClient.getConfig();
|
|
894
|
+
const res = await fetch(`${config.brainUrl}/api/collab/auth/register`, {
|
|
895
|
+
method: 'POST',
|
|
896
|
+
headers: { 'Content-Type': 'application/json' },
|
|
897
|
+
body: JSON.stringify({ username, password })
|
|
898
|
+
});
|
|
899
|
+
const data = await res.json();
|
|
900
|
+
if (res.ok) {
|
|
901
|
+
collabToken = data.token;
|
|
902
|
+
collabUsername = data.username;
|
|
903
|
+
await chrome.storage.local.set({ collabToken: data.token, collabUsername: data.username });
|
|
904
|
+
showToast('Registered successfully!');
|
|
905
|
+
showCollabMain();
|
|
906
|
+
} else {
|
|
907
|
+
showToast('Registration failed: ' + (data.error || 'Unknown error'), true);
|
|
908
|
+
}
|
|
909
|
+
} catch (err) {
|
|
910
|
+
showToast('Registration error: ' + err.message, true);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
async function loginCollab(username, password) {
|
|
915
|
+
try {
|
|
916
|
+
const config = await self.BrainClient.getConfig();
|
|
917
|
+
const res = await fetch(`${config.brainUrl}/api/collab/auth/login`, {
|
|
918
|
+
method: 'POST',
|
|
919
|
+
headers: { 'Content-Type': 'application/json' },
|
|
920
|
+
body: JSON.stringify({ username, password })
|
|
921
|
+
});
|
|
922
|
+
const data = await res.json();
|
|
923
|
+
if (res.ok) {
|
|
924
|
+
collabToken = data.token;
|
|
925
|
+
collabUsername = data.username;
|
|
926
|
+
await chrome.storage.local.set({ collabToken: data.token, collabUsername: data.username });
|
|
927
|
+
showToast('Logged in successfully!');
|
|
928
|
+
showCollabMain();
|
|
929
|
+
} else {
|
|
930
|
+
showToast('Login failed: ' + (data.error || 'Unknown error'), true);
|
|
931
|
+
}
|
|
932
|
+
} catch (err) {
|
|
933
|
+
showToast('Login error: ' + err.message, true);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async function logoutCollab() {
|
|
938
|
+
collabToken = '';
|
|
939
|
+
collabUsername = '';
|
|
940
|
+
await chrome.storage.local.remove(['collabToken', 'collabUsername']);
|
|
941
|
+
showCollabAuth();
|
|
942
|
+
showToast('Logged out');
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// --- Buttons / Forms bindings ---
|
|
946
|
+
btnCollabLogin.addEventListener('click', () => {
|
|
947
|
+
const u = collabUsernameInput.value.trim();
|
|
948
|
+
const p = collabPasswordInput.value.trim();
|
|
949
|
+
if (u && p) loginCollab(u, p);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
btnCollabRegister.addEventListener('click', () => {
|
|
953
|
+
const u = collabUsernameInput.value.trim();
|
|
954
|
+
const p = collabPasswordInput.value.trim();
|
|
955
|
+
if (u && p) registerCollab(u, p);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
btnCollabLogout.addEventListener('click', logoutCollab);
|
|
959
|
+
|
|
960
|
+
btnCollabShowJoin.addEventListener('click', () => {
|
|
961
|
+
collabJoinGroupRow.classList.toggle('hidden');
|
|
962
|
+
collabCreateGroupRow.classList.add('hidden');
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
btnCollabShowCreate.addEventListener('click', () => {
|
|
966
|
+
collabCreateGroupRow.classList.toggle('hidden');
|
|
967
|
+
collabJoinGroupRow.classList.add('hidden');
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
btnCollabSubmitJoin.addEventListener('click', async () => {
|
|
971
|
+
const code = collabJoinCode.value.trim();
|
|
972
|
+
if (!code) return;
|
|
973
|
+
try {
|
|
974
|
+
const config = await self.BrainClient.getConfig();
|
|
975
|
+
const res = await fetch(`${config.brainUrl}/api/collab/groups/join`, {
|
|
976
|
+
method: 'POST',
|
|
977
|
+
headers: {
|
|
978
|
+
'Content-Type': 'application/json',
|
|
979
|
+
'Authorization': `Bearer ${collabToken}`
|
|
980
|
+
},
|
|
981
|
+
body: JSON.stringify({ code })
|
|
982
|
+
});
|
|
983
|
+
const data = await res.json();
|
|
984
|
+
if (res.ok) {
|
|
985
|
+
showToast('Joined group!');
|
|
986
|
+
collabJoinCode.value = '';
|
|
987
|
+
collabJoinGroupRow.classList.add('hidden');
|
|
988
|
+
await fetchCollabGroups();
|
|
989
|
+
} else {
|
|
990
|
+
showToast('Join error: ' + data.error, true);
|
|
991
|
+
}
|
|
992
|
+
} catch (err) {
|
|
993
|
+
showToast('Failed to join: ' + err.message, true);
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
btnCollabSubmitCreate.addEventListener('click', async () => {
|
|
998
|
+
const name = collabCreateName.value.trim();
|
|
999
|
+
if (!name) return;
|
|
1000
|
+
try {
|
|
1001
|
+
const config = await self.BrainClient.getConfig();
|
|
1002
|
+
const res = await fetch(`${config.brainUrl}/api/collab/groups`, {
|
|
1003
|
+
method: 'POST',
|
|
1004
|
+
headers: {
|
|
1005
|
+
'Content-Type': 'application/json',
|
|
1006
|
+
'Authorization': `Bearer ${collabToken}`
|
|
1007
|
+
},
|
|
1008
|
+
body: JSON.stringify({ name })
|
|
1009
|
+
});
|
|
1010
|
+
const data = await res.json();
|
|
1011
|
+
if (res.ok) {
|
|
1012
|
+
showToast('Group created!');
|
|
1013
|
+
collabCreateName.value = '';
|
|
1014
|
+
collabCreateGroupRow.classList.add('hidden');
|
|
1015
|
+
await fetchCollabGroups();
|
|
1016
|
+
} else {
|
|
1017
|
+
showToast('Create error: ' + data.error, true);
|
|
1018
|
+
}
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
showToast('Failed to create group: ' + err.message, true);
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
collabGroupSelect.addEventListener('change', () => {
|
|
1025
|
+
const activeCode = collabGroupSelect.value;
|
|
1026
|
+
collabSelectedGroup = collabGroups.find(g => g.code === activeCode) || null;
|
|
1027
|
+
fetchCollabAnnotations();
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
btnCollabSaveNote.addEventListener('click', async () => {
|
|
1031
|
+
const text = collabNoteInput.value.trim();
|
|
1032
|
+
if (!text || !collabActiveUrl || !collabSelectedGroup) return;
|
|
1033
|
+
try {
|
|
1034
|
+
const config = await self.BrainClient.getConfig();
|
|
1035
|
+
const res = await fetch(`${config.brainUrl}/api/collab/annotations`, {
|
|
1036
|
+
method: 'POST',
|
|
1037
|
+
headers: {
|
|
1038
|
+
'Content-Type': 'application/json',
|
|
1039
|
+
'Authorization': `Bearer ${collabToken}`
|
|
1040
|
+
},
|
|
1041
|
+
body: JSON.stringify({
|
|
1042
|
+
url: collabActiveUrl,
|
|
1043
|
+
groupCode: collabSelectedGroup.code,
|
|
1044
|
+
text
|
|
1045
|
+
})
|
|
1046
|
+
});
|
|
1047
|
+
if (res.ok) {
|
|
1048
|
+
showToast('Note pinned!');
|
|
1049
|
+
collabNoteInput.value = '';
|
|
1050
|
+
fetchCollabAnnotations();
|
|
1051
|
+
} else {
|
|
1052
|
+
const errData = await res.json();
|
|
1053
|
+
showToast('Pin note error: ' + errData.error, true);
|
|
1054
|
+
}
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
showToast('Failed to pin note: ' + err.message, true);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
btnCollabSendChat.addEventListener('click', () => {
|
|
1061
|
+
const text = collabChatInput.value.trim();
|
|
1062
|
+
if (text && collabSocket && collabSocket.readyState === WebSocket.OPEN) {
|
|
1063
|
+
collabSocket.send(JSON.stringify({ type: 'CHAT_MESSAGE', text }));
|
|
1064
|
+
collabChatInput.value = '';
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
collabChatInput.addEventListener('keydown', (e) => {
|
|
1069
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1070
|
+
e.preventDefault();
|
|
1071
|
+
btnCollabSendChat.click();
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
// Listen for tab active changes
|
|
1076
|
+
chrome.tabs.onActivated.addListener(() => {
|
|
1077
|
+
updateCollabActiveUrl();
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
1081
|
+
if (changeInfo.url) {
|
|
1082
|
+
updateCollabActiveUrl();
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
|
|
679
1086
|
// ---- Event listeners ----
|
|
680
1087
|
searchInput.addEventListener('input', (e) => debounceSearch(e.target.value));
|
|
681
1088
|
|
|
@@ -684,6 +1091,7 @@ Provide helpful analysis grounded on both the user's brain memory and this page
|
|
|
684
1091
|
loadSettingsState().then(() => {
|
|
685
1092
|
loadRecentCaptures();
|
|
686
1093
|
recordRememberInvariants();
|
|
1094
|
+
initCollab();
|
|
687
1095
|
});
|
|
688
1096
|
|
|
689
1097
|
// Set intervals
|
|
@@ -696,3 +1104,5 @@ Provide helpful analysis grounded on both the user's brain memory and this page
|
|
|
696
1104
|
}
|
|
697
1105
|
}, 30000);
|
|
698
1106
|
})();
|
|
1107
|
+
|
|
1108
|
+
|
package/frontend/src/api.ts
CHANGED
|
@@ -530,6 +530,13 @@ export async function triggerDream(): Promise<{ success: boolean; status: string
|
|
|
530
530
|
return res.json()
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
+
export async function runAgentDiagnostics(): Promise<{ success: boolean; output: string }> {
|
|
534
|
+
const res = await apiFetch(`${API_BASE}/api/diagnostics/agents`, { method: 'POST' })
|
|
535
|
+
if (!res.ok) throw new Error(`Diagnostics API error: ${res.status}`)
|
|
536
|
+
return res.json()
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
533
540
|
export async function fetchGraph(): Promise<{ nodes: MemoryNode[]; routes: unknown[] }> {
|
|
534
541
|
const res = await apiFetch(`${API_BASE}/api/graph`)
|
|
535
542
|
if (!res.ok) throw new Error(`Graph API error: ${res.status}`)
|
|
@@ -166,7 +166,7 @@ function CollabDashboard({ token, username, onLogout, onSelectSandbox }: { token
|
|
|
166
166
|
<p style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>Share annotations and real-time page chat rooms</p>
|
|
167
167
|
</div>
|
|
168
168
|
<div style={{ display: 'flex', gap: '10px' }}>
|
|
169
|
-
<button className="btn btn-purple" onClick={onSelectSandbox}>๐ Open
|
|
169
|
+
<button className="btn btn-purple" onClick={onSelectSandbox}>๐ Open Browse Workspace</button>
|
|
170
170
|
<button className="btn btn-ghost" onClick={onLogout} style={{ color: '#f87171' }}>Logout</button>
|
|
171
171
|
</div>
|
|
172
172
|
</div>
|
|
@@ -219,7 +219,7 @@ function CollabDashboard({ token, username, onLogout, onSelectSandbox }: { token
|
|
|
219
219
|
function CollabSandbox({ token, username, onBack }: { token: string, username: string, onBack: () => void }) {
|
|
220
220
|
const [groups, setGroups] = useState<CollabGroup[]>([]);
|
|
221
221
|
const [selectedGroup, setSelectedGroup] = useState<CollabGroup | null>(null);
|
|
222
|
-
const [
|
|
222
|
+
const [activeBrowseUrl, setActiveBrowseUrl] = useState('https://news.ycombinator.com');
|
|
223
223
|
const [activeUrl, setActiveUrl] = useState('https://news.ycombinator.com');
|
|
224
224
|
const [annotations, setAnnotations] = useState<Annotation[]>([]);
|
|
225
225
|
const [noteText, setNoteText] = useState('');
|
|
@@ -313,9 +313,9 @@ function CollabSandbox({ token, username, onBack }: { token: string, username: s
|
|
|
313
313
|
|
|
314
314
|
const handleUrlSubmit = (e: React.FormEvent) => {
|
|
315
315
|
e.preventDefault();
|
|
316
|
-
if (!
|
|
316
|
+
if (!activeBrowseUrl.trim()) return;
|
|
317
317
|
setChatMessages([]); setSystemLogs([]); setActiveUsers([]);
|
|
318
|
-
setActiveUrl(
|
|
318
|
+
setActiveUrl(activeBrowseUrl);
|
|
319
319
|
};
|
|
320
320
|
|
|
321
321
|
const handleAddAnnotation = async (e: React.FormEvent) => {
|
|
@@ -348,14 +348,14 @@ function CollabSandbox({ token, username, onBack }: { token: string, username: s
|
|
|
348
348
|
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', justifyContent: 'space-between', background: 'rgba(255,255,255,0.02)', padding: '12px 20px', borderRadius: '8px', border: '1px solid var(--border)', flexWrap: 'wrap' }}>
|
|
349
349
|
<button className="btn btn-ghost" onClick={onBack}>โ Back to Workspaces</button>
|
|
350
350
|
<form onSubmit={handleUrlSubmit} style={{ display: 'flex', gap: '10px', flex: '1', minWidth: '250px' }}>
|
|
351
|
-
<input type="text" value={
|
|
352
|
-
<button type="submit" className="btn btn-purple">
|
|
351
|
+
<input type="text" value={activeBrowseUrl} onChange={e => setActiveBrowseUrl(e.target.value)} style={{ background: 'rgba(0,0,0,0.2)', border: '1px solid var(--border)', color: '#fff', padding: '6px 12px', borderRadius: '6px', flex: '1', fontFamily: 'monospace' }} />
|
|
352
|
+
<button type="submit" className="btn btn-purple">Connect URL</button>
|
|
353
353
|
</form>
|
|
354
354
|
<div style={{ fontSize: '13px', color: wsConnected ? '#34d399' : '#f87171' }}>โ {wsConnected ? 'Connected' : 'Disconnected'}</div>
|
|
355
355
|
</div>
|
|
356
356
|
|
|
357
357
|
<div style={{ fontSize: '13px', color: 'var(--text-secondary)' }}>
|
|
358
|
-
|
|
358
|
+
Active browse context URL: <strong style={{ color: 'var(--purple-primary)', fontFamily: 'monospace' }}>{activeUrl}</strong>
|
|
359
359
|
</div>
|
|
360
360
|
|
|
361
361
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 340px', gap: '20px', height: '500px' }}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { fetchHealth, fetchLogs } from '../api';
|
|
2
|
+
import { fetchHealth, fetchLogs, runAgentDiagnostics } from '../api';
|
|
3
3
|
import type { HealthData } from '../types';
|
|
4
4
|
|
|
5
5
|
export default function DeploymentsPage() {
|
|
@@ -9,9 +9,9 @@ export default function DeploymentsPage() {
|
|
|
9
9
|
const [error, setError] = useState<string | null>(null);
|
|
10
10
|
const [success, setSuccess] = useState<string | null>(null);
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
const [
|
|
14
|
-
const [
|
|
12
|
+
// CLI Agent Diagnostics state
|
|
13
|
+
const [runningDiagnostics, setRunningDiagnostics] = useState(false);
|
|
14
|
+
const [diagnosticLogs, setDiagnosticLogs] = useState<string | null>(null);
|
|
15
15
|
|
|
16
16
|
const consoleEndRef = useRef<HTMLDivElement>(null);
|
|
17
17
|
|
|
@@ -44,29 +44,36 @@ export default function DeploymentsPage() {
|
|
|
44
44
|
}
|
|
45
45
|
}, [logs]);
|
|
46
46
|
|
|
47
|
-
const
|
|
47
|
+
const handleRunDiagnostics = async (e: React.FormEvent) => {
|
|
48
48
|
e.preventDefault();
|
|
49
|
-
|
|
49
|
+
setRunningDiagnostics(true);
|
|
50
50
|
setError(null);
|
|
51
51
|
setSuccess(null);
|
|
52
|
+
setDiagnosticLogs(null);
|
|
52
53
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const res = await runAgentDiagnostics();
|
|
55
|
+
setDiagnosticLogs(res.output);
|
|
56
|
+
if (res.success) {
|
|
57
|
+
setSuccess('CLI Agent diagnostics completed successfully.');
|
|
58
|
+
} else {
|
|
59
|
+
setError('CLI Agent diagnostics finished with warnings or missing binaries.');
|
|
60
|
+
}
|
|
61
|
+
void fetchSystemData();
|
|
56
62
|
} catch (err: unknown) {
|
|
57
|
-
setError((err as Error).message || '
|
|
63
|
+
setError((err as Error).message || 'Diagnostics execution failed.');
|
|
58
64
|
} finally {
|
|
59
|
-
|
|
65
|
+
setRunningDiagnostics(false);
|
|
60
66
|
}
|
|
61
67
|
};
|
|
62
68
|
|
|
63
|
-
const
|
|
64
|
-
{ id: '
|
|
65
|
-
{ id: '
|
|
66
|
-
{ id: '
|
|
67
|
-
{ id: '
|
|
69
|
+
const AGENTS_LIST = [
|
|
70
|
+
{ id: 'antigravity', name: 'Antigravity (Gemini SDK)', desc: 'Primary core developer agent' },
|
|
71
|
+
{ id: 'gemini', name: 'Gemini CLI', desc: 'Direct Gemini assistant binary' },
|
|
72
|
+
{ id: 'claude', name: 'Claude Code', desc: 'Anthropic developer CLI wrapper' },
|
|
73
|
+
{ id: 'codex', name: 'Codex CLI', desc: 'OpenAI agent binary integration' }
|
|
68
74
|
];
|
|
69
75
|
|
|
76
|
+
|
|
70
77
|
return (
|
|
71
78
|
<div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
|
|
72
79
|
<div className="page-header" style={{ marginBottom: 24 }}>
|
|
@@ -146,7 +153,7 @@ export default function DeploymentsPage() {
|
|
|
146
153
|
|
|
147
154
|
</div>
|
|
148
155
|
|
|
149
|
-
|
|
156
|
+
{/* CLI Agents Catalog & Diagnostics */}
|
|
150
157
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 24 }}>
|
|
151
158
|
|
|
152
159
|
{/* Catalog */}
|
|
@@ -154,31 +161,31 @@ export default function DeploymentsPage() {
|
|
|
154
161
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
|
|
155
162
|
<span style={{ fontSize: 20 }}>๐ง </span>
|
|
156
163
|
<div>
|
|
157
|
-
<h3 style={{ fontSize: 14, fontWeight: 600 }}>
|
|
158
|
-
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
164
|
+
<h3 style={{ fontSize: 14, fontWeight: 600 }}>CLI Reasoning Agents</h3>
|
|
165
|
+
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Headless CLI reasoning agents configured on system</p>
|
|
159
166
|
</div>
|
|
160
167
|
</div>
|
|
161
168
|
|
|
162
169
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
163
|
-
{
|
|
164
|
-
const
|
|
170
|
+
{AGENTS_LIST.map((a) => {
|
|
171
|
+
const activeAgent = health?.cli_agents?.includes(a.id);
|
|
165
172
|
return (
|
|
166
|
-
<div key={
|
|
173
|
+
<div key={a.id} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 8, border: '1px solid var(--border)' }}>
|
|
167
174
|
<div style={{ flex: 1 }}>
|
|
168
175
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
169
|
-
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{
|
|
176
|
+
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{a.name}</span>
|
|
170
177
|
<span className="badge" style={{
|
|
171
178
|
fontSize: 9,
|
|
172
179
|
padding: '2px 6px',
|
|
173
|
-
background:
|
|
174
|
-
color:
|
|
175
|
-
border:
|
|
180
|
+
background: activeAgent ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
|
|
181
|
+
color: activeAgent ? '#10b981' : '#ef4444',
|
|
182
|
+
border: activeAgent ? '1px solid rgba(16, 185, 129, 0.2)' : '1px solid rgba(239, 68, 68, 0.2)'
|
|
176
183
|
}}>
|
|
177
|
-
{
|
|
184
|
+
{activeAgent ? 'available' : 'missing'}
|
|
178
185
|
</span>
|
|
179
186
|
</div>
|
|
180
|
-
<div style={{ fontSize: 10, color: 'var(--text-secondary)', marginTop: 2 }}>{
|
|
181
|
-
<div style={{ fontSize: 9, color: 'var(--text-tertiary)', marginTop: 4 }}>ID: {
|
|
187
|
+
<div style={{ fontSize: 10, color: 'var(--text-secondary)', marginTop: 2 }}>{a.desc}</div>
|
|
188
|
+
<div style={{ fontSize: 9, color: 'var(--text-tertiary)', marginTop: 4 }}>Registry ID: {a.id}</div>
|
|
182
189
|
</div>
|
|
183
190
|
</div>
|
|
184
191
|
);
|
|
@@ -186,42 +193,24 @@ export default function DeploymentsPage() {
|
|
|
186
193
|
</div>
|
|
187
194
|
</div>
|
|
188
195
|
|
|
189
|
-
{/*
|
|
196
|
+
{/* Diagnostics Form */}
|
|
190
197
|
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
191
198
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
|
|
192
199
|
<span style={{ fontSize: 20 }}>๐ก</span>
|
|
193
200
|
<div>
|
|
194
|
-
<h3 style={{ fontSize: 14, fontWeight: 600 }}>
|
|
195
|
-
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Trigger
|
|
201
|
+
<h3 style={{ fontSize: 14, fontWeight: 600 }}>CLI Agent Diagnostics</h3>
|
|
202
|
+
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Trigger active paths check and verify registered agents</p>
|
|
196
203
|
</div>
|
|
197
204
|
</div>
|
|
198
205
|
|
|
199
|
-
<
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
id="model_pull"
|
|
204
|
-
value={modelToPull}
|
|
205
|
-
onChange={(e) => setModelToPull(e.target.value)}
|
|
206
|
-
style={{
|
|
207
|
-
background: 'var(--bg-tertiary)',
|
|
208
|
-
color: 'var(--text-primary)',
|
|
209
|
-
border: '1px solid var(--border)',
|
|
210
|
-
padding: '8px 12px',
|
|
211
|
-
borderRadius: 6,
|
|
212
|
-
outline: 'none',
|
|
213
|
-
fontSize: 13
|
|
214
|
-
}}
|
|
215
|
-
>
|
|
216
|
-
<option value="gemma4:26b">Gemma 4 (26B Parameter) โ 15.6 GB</option>
|
|
217
|
-
<option value="gemma5:32b">Gemma 5 Pro (32B Parameter) โ 19.2 GB</option>
|
|
218
|
-
<option value="llama3:70b">Llama 3 (70B Parameter) โ 40 GB</option>
|
|
219
|
-
</select>
|
|
220
|
-
</div>
|
|
206
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
207
|
+
<p style={{ fontSize: 12, lineHeight: 1.5, color: 'var(--text-secondary)' }}>
|
|
208
|
+
This executes a diagnostic check on registered reasoning agents (equivalent to running <code>npx total-recall upgrade --agents</code> in the background) to audit paths and verify binaries.
|
|
209
|
+
</p>
|
|
221
210
|
|
|
222
211
|
<button
|
|
223
|
-
|
|
224
|
-
disabled={
|
|
212
|
+
onClick={handleRunDiagnostics}
|
|
213
|
+
disabled={runningDiagnostics}
|
|
225
214
|
style={{
|
|
226
215
|
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
227
216
|
color: '#fff',
|
|
@@ -230,12 +219,32 @@ export default function DeploymentsPage() {
|
|
|
230
219
|
fontWeight: 500,
|
|
231
220
|
border: 'none',
|
|
232
221
|
marginTop: 8,
|
|
233
|
-
cursor:
|
|
222
|
+
cursor: runningDiagnostics ? 'not-allowed' : 'pointer'
|
|
234
223
|
}}
|
|
235
224
|
>
|
|
236
|
-
{
|
|
225
|
+
{runningDiagnostics ? 'โณ Running Audit...' : '๐ Run Diagnostics Audit'}
|
|
237
226
|
</button>
|
|
238
|
-
</
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{diagnosticLogs && (
|
|
230
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 10 }}>
|
|
231
|
+
<span style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Diagnostics Console Output:</span>
|
|
232
|
+
<pre style={{
|
|
233
|
+
background: '#07070a',
|
|
234
|
+
border: '1px solid var(--border)',
|
|
235
|
+
padding: 10,
|
|
236
|
+
borderRadius: 6,
|
|
237
|
+
fontFamily: 'var(--font-mono)',
|
|
238
|
+
fontSize: 11,
|
|
239
|
+
color: '#e6edf3',
|
|
240
|
+
whiteSpace: 'pre-wrap',
|
|
241
|
+
maxHeight: 150,
|
|
242
|
+
overflowY: 'auto'
|
|
243
|
+
}}>
|
|
244
|
+
{diagnosticLogs}
|
|
245
|
+
</pre>
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
239
248
|
</div>
|
|
240
249
|
|
|
241
250
|
</div>
|
|
@@ -21,6 +21,26 @@ export default function HealthPage() {
|
|
|
21
21
|
const [isUpdating, setIsUpdating] = useState(false)
|
|
22
22
|
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
|
23
23
|
const [updateError, setUpdateError] = useState('')
|
|
24
|
+
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)
|
|
25
|
+
const [checkMessage, setCheckMessage] = useState('')
|
|
26
|
+
|
|
27
|
+
const handleManualCheckUpdate = async () => {
|
|
28
|
+
setIsCheckingUpdate(true)
|
|
29
|
+
setCheckMessage('')
|
|
30
|
+
setUpdateError('')
|
|
31
|
+
try {
|
|
32
|
+
const data = await checkUpdate()
|
|
33
|
+
setUpdateInfo(data)
|
|
34
|
+
if (!data.updateAvailable) {
|
|
35
|
+
setCheckMessage('System is up to date!')
|
|
36
|
+
setTimeout(() => setCheckMessage(''), 3000)
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
setUpdateError(`Failed to check updates: ${(e as Error).message}`)
|
|
40
|
+
} finally {
|
|
41
|
+
setIsCheckingUpdate(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
24
44
|
|
|
25
45
|
useEffect(() => {
|
|
26
46
|
let active = true
|
|
@@ -168,7 +188,7 @@ export default function HealthPage() {
|
|
|
168
188
|
<h1>System Health</h1>
|
|
169
189
|
<p>Live monitoring of your sovereign brain {lastPoll && <span>ยท Last poll: {lastPoll.toLocaleTimeString()}</span>}</p>
|
|
170
190
|
</div>
|
|
171
|
-
{updateInfo?.updateAvailable
|
|
191
|
+
{updateInfo?.updateAvailable ? (
|
|
172
192
|
<div className="badge badge-success" style={{
|
|
173
193
|
display: 'flex',
|
|
174
194
|
alignItems: 'center',
|
|
@@ -199,6 +219,30 @@ export default function HealthPage() {
|
|
|
199
219
|
Update Now
|
|
200
220
|
</button>
|
|
201
221
|
</div>
|
|
222
|
+
) : (
|
|
223
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
224
|
+
{checkMessage && (
|
|
225
|
+
<span style={{ color: '#3fb950', fontSize: 14, fontWeight: 500, animation: 'fadeIn 0.3s ease' }}>
|
|
226
|
+
{checkMessage}
|
|
227
|
+
</span>
|
|
228
|
+
)}
|
|
229
|
+
<button
|
|
230
|
+
className="btn btn-secondary"
|
|
231
|
+
disabled={isCheckingUpdate}
|
|
232
|
+
onClick={handleManualCheckUpdate}
|
|
233
|
+
style={{
|
|
234
|
+
fontSize: 13,
|
|
235
|
+
padding: '8px 16px',
|
|
236
|
+
borderRadius: 8,
|
|
237
|
+
cursor: 'pointer',
|
|
238
|
+
display: 'flex',
|
|
239
|
+
alignItems: 'center',
|
|
240
|
+
gap: 6
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
{isCheckingUpdate ? 'Checking...' : 'Check for Updates'}
|
|
244
|
+
</button>
|
|
245
|
+
</div>
|
|
202
246
|
)}
|
|
203
247
|
</div>
|
|
204
248
|
|
package/package.json
CHANGED
package/src/server/rest.mjs
CHANGED
|
@@ -468,6 +468,25 @@ router.get('/api/vault/status', requireAuth, requireScope('memory:read'), async
|
|
|
468
468
|
}
|
|
469
469
|
});
|
|
470
470
|
|
|
471
|
+
/**
|
|
472
|
+
* POST /api/diagnostics/agents
|
|
473
|
+
* Run `upgrade --agents` diagnostics checks and return the console text output.
|
|
474
|
+
*/
|
|
475
|
+
router.post('/api/diagnostics/agents', requireAuth, requireScope('health:read'), async (req, res) => {
|
|
476
|
+
try {
|
|
477
|
+
const result = spawnSync('node', [path.join(ROOT, 'bin', 'total-recall.mjs'), 'upgrade', '--agents'], {
|
|
478
|
+
encoding: 'utf8',
|
|
479
|
+
cwd: ROOT,
|
|
480
|
+
env: { ...process.env }
|
|
481
|
+
});
|
|
482
|
+
const output = (result.stdout || '') + (result.stderr || '');
|
|
483
|
+
res.json({ success: result.status === 0, output });
|
|
484
|
+
} catch (err) {
|
|
485
|
+
serverError(res, err);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
|
|
471
490
|
// โโโ Keys โโโโโโโโโโโ (moved to ./routes/keys.mjs)
|
|
472
491
|
// โโโ Sessions โโโโโโโ (moved to ./routes/sessions.mjs)
|
|
473
492
|
// โโโ Sandbox โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|