total-recall-brain 3.6.1 → 3.6.3
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/pages/HealthPage.tsx +45 -1
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +1 -1
- package/src/core/runtime.mjs +1 -1
|
@@ -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
|
+
|
|
@@ -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/core/runtime.mjs
CHANGED
|
@@ -43,7 +43,7 @@ export function findBinaryInPath(binaryName) {
|
|
|
43
43
|
|
|
44
44
|
const DEFAULT_AGENTS = [
|
|
45
45
|
{ name: 'antigravity', binary: 'antigravity', flags: '--sandbox=false --yolo -o json', priority: 1, enabled: true, exec: 'flag' },
|
|
46
|
-
{ name: 'claude', binary: 'claude', flags: '--output-format json --permission-mode bypassPermissions', priority: 3, enabled: true, exec: 'flag' },
|
|
46
|
+
{ name: 'claude', binary: 'claude', flags: '--output-format json --permission-mode bypassPermissions --setting-sources local --tools ""', priority: 3, enabled: true, exec: 'flag' },
|
|
47
47
|
{ name: 'codex', binary: 'codex', flags: '--full-auto --json --skip-git-repo-check', priority: 4, enabled: true, exec: 'subcommand' },
|
|
48
48
|
];
|
|
49
49
|
|