cohvu 1.0.21 → 1.0.23
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/dist/api.d.ts +6 -1
- package/dist/api.js +58 -33
- package/dist/api.js.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/proxy.js +168 -35
- package/dist/proxy.js.map +1 -1
- package/dist/tui/dashboard.js +271 -127
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/tui/render.js +21 -2
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/state.d.ts +12 -0
- package/dist/tui/state.js +6 -0
- package/dist/tui/state.js.map +1 -1
- package/package.json +1 -1
package/dist/tui/dashboard.js
CHANGED
|
@@ -24,6 +24,29 @@ async function launchDashboard() {
|
|
|
24
24
|
let feedDisconnect = null;
|
|
25
25
|
let copiedTimer = null;
|
|
26
26
|
let inlineErrorTimer = null;
|
|
27
|
+
let toastTimer = null;
|
|
28
|
+
let lastSyncErrorAt = 0;
|
|
29
|
+
// Toast feedback
|
|
30
|
+
function showToast(message, type, durationMs = 3000) {
|
|
31
|
+
if (toastTimer)
|
|
32
|
+
clearTimeout(toastTimer);
|
|
33
|
+
dispatch({ type: 'SET_TOAST', toast: { message, type, expiresAt: Date.now() + durationMs } });
|
|
34
|
+
toastTimer = setTimeout(() => {
|
|
35
|
+
dispatch({ type: 'SET_TOAST', toast: null });
|
|
36
|
+
}, durationMs);
|
|
37
|
+
}
|
|
38
|
+
// Loading indicator for async operations
|
|
39
|
+
async function withOperation(description, fn) {
|
|
40
|
+
dispatch({ type: 'SET_OPERATION', operation: description });
|
|
41
|
+
doRender();
|
|
42
|
+
try {
|
|
43
|
+
return await fn();
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
47
|
+
doRender();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
27
50
|
// Load first-login state
|
|
28
51
|
const savedState = loadState();
|
|
29
52
|
state.firstLogin = !savedState.hasOpenedDashboard;
|
|
@@ -84,13 +107,16 @@ async function launchDashboard() {
|
|
|
84
107
|
// Load knowledge, members, billing, invite links, notifications in parallel
|
|
85
108
|
const projectId = state.activeProjectId;
|
|
86
109
|
if (projectId) {
|
|
110
|
+
let loadError = false;
|
|
87
111
|
const [memResult, members, billing, inviteLinks, notifications] = await Promise.all([
|
|
88
|
-
api.listMemories(projectId, { limit: PAGE_SIZE, offset: 0 }).catch(() => null),
|
|
89
|
-
api.listMembers(projectId).catch(() => []),
|
|
90
|
-
api.getBilling(projectId).catch(() => null),
|
|
112
|
+
api.listMemories(projectId, { limit: PAGE_SIZE, offset: 0 }).catch(() => { loadError = true; return null; }),
|
|
113
|
+
api.listMembers(projectId).catch(() => { loadError = true; return []; }),
|
|
114
|
+
api.getBilling(projectId).catch(() => { loadError = true; return null; }),
|
|
91
115
|
api.listInviteLinks(projectId).catch(() => []),
|
|
92
116
|
api.listNotifications().catch(() => []),
|
|
93
117
|
]);
|
|
118
|
+
if (loadError)
|
|
119
|
+
showToast('Some data failed to load', 'error');
|
|
94
120
|
if (memResult) {
|
|
95
121
|
dispatch({ type: 'SET_MEMORIES', memories: memResult.memories, total: memResult.total });
|
|
96
122
|
}
|
|
@@ -157,19 +183,25 @@ async function launchDashboard() {
|
|
|
157
183
|
const sub = state.billing?.subscription;
|
|
158
184
|
if (!sub || sub.status !== 'active') {
|
|
159
185
|
try {
|
|
186
|
+
showToast('Opening checkout...', 'info');
|
|
160
187
|
const checkout = await api.createCheckout(project.project_id);
|
|
161
188
|
if (checkout.checkout_url)
|
|
162
189
|
openBrowser(checkout.checkout_url);
|
|
163
190
|
}
|
|
164
|
-
catch {
|
|
191
|
+
catch {
|
|
192
|
+
showToast('Failed to open checkout', 'error');
|
|
193
|
+
}
|
|
165
194
|
}
|
|
166
195
|
else {
|
|
167
196
|
try {
|
|
197
|
+
showToast('Opening billing portal...', 'info');
|
|
168
198
|
const portal = await api.getPortalUrl(project.project_id);
|
|
169
199
|
if (portal.url)
|
|
170
200
|
openBrowser(portal.url);
|
|
171
201
|
}
|
|
172
|
-
catch {
|
|
202
|
+
catch {
|
|
203
|
+
showToast('Failed to open billing portal', 'error');
|
|
204
|
+
}
|
|
173
205
|
}
|
|
174
206
|
}
|
|
175
207
|
return;
|
|
@@ -238,14 +270,24 @@ async function launchDashboard() {
|
|
|
238
270
|
// Confirmed — delete
|
|
239
271
|
const projectId = state.activeProjectId;
|
|
240
272
|
if (projectId) {
|
|
273
|
+
let failures = 0;
|
|
274
|
+
dispatch({ type: 'SET_OPERATION', operation: 'Removing memories' });
|
|
275
|
+
doRender();
|
|
241
276
|
await Promise.all([...state.forgetSelected].map(async (id) => {
|
|
242
277
|
try {
|
|
243
278
|
await api.deleteMemory(projectId, id);
|
|
244
279
|
dispatch({ type: 'REMOVE_MEMORY', id });
|
|
245
280
|
}
|
|
246
|
-
catch {
|
|
281
|
+
catch {
|
|
282
|
+
failures++;
|
|
283
|
+
}
|
|
247
284
|
}));
|
|
285
|
+
dispatch({ type: 'SET_OPERATION', operation: null });
|
|
248
286
|
dispatch({ type: 'EXIT_FORGET' });
|
|
287
|
+
if (failures > 0)
|
|
288
|
+
showToast(`${failures} failed to remove`, 'error');
|
|
289
|
+
else
|
|
290
|
+
showToast('Removed', 'success');
|
|
249
291
|
}
|
|
250
292
|
}
|
|
251
293
|
else if (state.forgetConfirming && (key.name === 'escape' || (key.name === 'char' && key.char === 'n'))) {
|
|
@@ -399,8 +441,11 @@ async function launchDashboard() {
|
|
|
399
441
|
await api.cancelApproval(projectId, demoteApproval.id);
|
|
400
442
|
const approvals = await api.listApprovals(projectId);
|
|
401
443
|
dispatch({ type: 'SET_PENDING_APPROVALS', approvals });
|
|
444
|
+
showToast('Approval cancelled', 'success');
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
showToast('Failed to cancel approval', 'error');
|
|
402
448
|
}
|
|
403
|
-
catch { }
|
|
404
449
|
}
|
|
405
450
|
return;
|
|
406
451
|
}
|
|
@@ -439,22 +484,28 @@ async function launchDashboard() {
|
|
|
439
484
|
const projectId = state.activeProjectId;
|
|
440
485
|
if (projectId) {
|
|
441
486
|
try {
|
|
487
|
+
showToast('Opening checkout...', 'info');
|
|
442
488
|
const checkout = await api.createCheckout(projectId);
|
|
443
489
|
if (checkout.checkout_url)
|
|
444
490
|
openBrowser(checkout.checkout_url);
|
|
445
491
|
}
|
|
446
|
-
catch {
|
|
492
|
+
catch {
|
|
493
|
+
showToast('Failed to open checkout', 'error');
|
|
494
|
+
}
|
|
447
495
|
}
|
|
448
496
|
}
|
|
449
497
|
else if (key.name === 'char' && key.char === 'p') {
|
|
450
498
|
const projectId = state.activeProjectId;
|
|
451
499
|
if (projectId) {
|
|
452
500
|
try {
|
|
501
|
+
showToast('Opening billing portal...', 'info');
|
|
453
502
|
const portal = await api.getPortalUrl(projectId);
|
|
454
503
|
if (portal.url)
|
|
455
504
|
openBrowser(portal.url);
|
|
456
505
|
}
|
|
457
|
-
catch {
|
|
506
|
+
catch {
|
|
507
|
+
showToast('Failed to open billing portal', 'error');
|
|
508
|
+
}
|
|
458
509
|
}
|
|
459
510
|
}
|
|
460
511
|
}
|
|
@@ -484,8 +535,11 @@ async function launchDashboard() {
|
|
|
484
535
|
await api.cancelApproval(projectId, projectApproval.id);
|
|
485
536
|
const approvals = await api.listApprovals(projectId);
|
|
486
537
|
dispatch({ type: 'SET_PENDING_APPROVALS', approvals });
|
|
538
|
+
showToast('Approval cancelled', 'success');
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
showToast('Failed to cancel approval', 'error');
|
|
487
542
|
}
|
|
488
|
-
catch { }
|
|
489
543
|
}
|
|
490
544
|
return;
|
|
491
545
|
}
|
|
@@ -679,8 +733,11 @@ async function launchDashboard() {
|
|
|
679
733
|
await api.changeRole(projectId, modal.targetUserId, newRole);
|
|
680
734
|
const members = await api.listMembers(projectId);
|
|
681
735
|
dispatch({ type: 'SET_MEMBERS', members });
|
|
736
|
+
showToast('Role updated', 'success');
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
showToast('Failed to update role', 'error');
|
|
682
740
|
}
|
|
683
|
-
catch { }
|
|
684
741
|
}
|
|
685
742
|
}
|
|
686
743
|
dispatch({ type: 'CLOSE_MODAL' });
|
|
@@ -694,20 +751,32 @@ async function launchDashboard() {
|
|
|
694
751
|
switch (modal.kind) {
|
|
695
752
|
case 'confirm-forget':
|
|
696
753
|
try {
|
|
697
|
-
await
|
|
698
|
-
|
|
754
|
+
await withOperation('Removing memory', async () => {
|
|
755
|
+
await api.deleteMemory(projectId, modal.memoryId);
|
|
756
|
+
dispatch({ type: 'REMOVE_MEMORY', id: modal.memoryId });
|
|
757
|
+
});
|
|
758
|
+
showToast('Memory removed', 'success');
|
|
759
|
+
}
|
|
760
|
+
catch {
|
|
761
|
+
showToast('Failed to remove memory', 'error');
|
|
762
|
+
return;
|
|
699
763
|
}
|
|
700
|
-
catch { }
|
|
701
764
|
break;
|
|
702
765
|
case 'confirm-forget-all':
|
|
703
766
|
case 'confirm-clear': {
|
|
704
767
|
const project = (0, state_1.getActiveProject)(state);
|
|
705
768
|
if (project && modal.input === project.slug) {
|
|
706
769
|
try {
|
|
707
|
-
await
|
|
708
|
-
|
|
770
|
+
await withOperation('Clearing memories', async () => {
|
|
771
|
+
await api.clearMemories(projectId);
|
|
772
|
+
dispatch({ type: 'SET_MEMORIES', memories: [], total: 0 });
|
|
773
|
+
});
|
|
774
|
+
showToast('All memories cleared', 'success');
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
showToast('Failed to clear memories', 'error');
|
|
778
|
+
return;
|
|
709
779
|
}
|
|
710
|
-
catch { }
|
|
711
780
|
}
|
|
712
781
|
break;
|
|
713
782
|
}
|
|
@@ -715,59 +784,72 @@ async function launchDashboard() {
|
|
|
715
784
|
const project = (0, state_1.getActiveProject)(state);
|
|
716
785
|
if (project && modal.input === project.slug) {
|
|
717
786
|
try {
|
|
718
|
-
await
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
787
|
+
await withOperation('Deleting project', async () => {
|
|
788
|
+
await api.deleteProject(projectId);
|
|
789
|
+
const me = await api.me();
|
|
790
|
+
dispatch({ type: 'SET_USER_DATA', me });
|
|
791
|
+
dispatch({ type: 'SWITCH_TAB', tab: 'knowledge' });
|
|
792
|
+
if (state.activeProjectId) {
|
|
793
|
+
connectFeed(state.activeProjectId);
|
|
794
|
+
await loadTabData();
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
dispatch({ type: 'SET_MEMORIES', memories: [], total: 0 });
|
|
798
|
+
dispatch({ type: 'SET_MEMBERS', members: [] });
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
showToast('Project deleted', 'success');
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
showToast('Failed to delete project', 'error');
|
|
805
|
+
return;
|
|
730
806
|
}
|
|
731
|
-
catch { }
|
|
732
807
|
}
|
|
733
808
|
break;
|
|
734
809
|
}
|
|
735
810
|
case 'confirm-remove-member':
|
|
736
811
|
try {
|
|
737
|
-
await
|
|
738
|
-
|
|
739
|
-
|
|
812
|
+
await withOperation('Removing member', async () => {
|
|
813
|
+
await api.removeMember(projectId, modal.userId);
|
|
814
|
+
const members = await api.listMembers(projectId);
|
|
815
|
+
dispatch({ type: 'SET_MEMBERS', members });
|
|
816
|
+
});
|
|
817
|
+
showToast('Member removed', 'success');
|
|
818
|
+
}
|
|
819
|
+
catch {
|
|
820
|
+
showToast('Failed to remove member', 'error');
|
|
821
|
+
return;
|
|
740
822
|
}
|
|
741
|
-
catch { }
|
|
742
823
|
break;
|
|
743
824
|
case 'confirm-leave':
|
|
744
825
|
try {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
try {
|
|
752
|
-
await api.cancelApproval(projectId, approval.id);
|
|
826
|
+
await withOperation('Leaving project', async () => {
|
|
827
|
+
const leavingUserId = state.user.id;
|
|
828
|
+
for (const approval of state.pendingApprovals) {
|
|
829
|
+
const leavingEmail = state.user.email;
|
|
830
|
+
if (approval.initiator_email === leavingEmail) {
|
|
831
|
+
await api.cancelApproval(projectId, approval.id).catch(() => { });
|
|
753
832
|
}
|
|
754
|
-
catch { }
|
|
755
833
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
834
|
+
await api.removeMember(projectId, leavingUserId);
|
|
835
|
+
const me = await api.me();
|
|
836
|
+
dispatch({ type: 'SET_USER_DATA', me });
|
|
837
|
+
dispatch({ type: 'SWITCH_TAB', tab: 'knowledge' });
|
|
838
|
+
if (state.activeProjectId) {
|
|
839
|
+
connectFeed(state.activeProjectId);
|
|
840
|
+
await loadTabData();
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
dispatch({ type: 'SET_MEMORIES', memories: [], total: 0 });
|
|
844
|
+
dispatch({ type: 'SET_MEMBERS', members: [] });
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
showToast('Left project', 'success');
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
showToast('Failed to leave project', 'error');
|
|
851
|
+
return;
|
|
769
852
|
}
|
|
770
|
-
catch { }
|
|
771
853
|
break;
|
|
772
854
|
case 'confirm-logout':
|
|
773
855
|
try {
|
|
@@ -778,6 +860,8 @@ async function launchDashboard() {
|
|
|
778
860
|
}
|
|
779
861
|
}
|
|
780
862
|
catch { }
|
|
863
|
+
// Brief delay for in-flight proxy requests
|
|
864
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
781
865
|
cleanExit();
|
|
782
866
|
break;
|
|
783
867
|
case 'rename': {
|
|
@@ -785,11 +869,17 @@ async function launchDashboard() {
|
|
|
785
869
|
break;
|
|
786
870
|
const slug = modal.input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
787
871
|
try {
|
|
788
|
-
await
|
|
789
|
-
|
|
790
|
-
|
|
872
|
+
await withOperation('Renaming', async () => {
|
|
873
|
+
await api.renameProject(projectId, modal.input, slug);
|
|
874
|
+
const me = await api.me();
|
|
875
|
+
dispatch({ type: 'SET_USER_DATA', me });
|
|
876
|
+
});
|
|
877
|
+
showToast('Project renamed', 'success');
|
|
878
|
+
}
|
|
879
|
+
catch {
|
|
880
|
+
showToast('Failed to rename', 'error');
|
|
881
|
+
return;
|
|
791
882
|
}
|
|
792
|
-
catch { }
|
|
793
883
|
break;
|
|
794
884
|
}
|
|
795
885
|
case 'create-project': {
|
|
@@ -797,40 +887,64 @@ async function launchDashboard() {
|
|
|
797
887
|
break;
|
|
798
888
|
const slug = modal.input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
799
889
|
try {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
890
|
+
await withOperation('Creating project', async () => {
|
|
891
|
+
const project = await api.createProject(modal.input, slug);
|
|
892
|
+
await api.switchProject(project.id);
|
|
893
|
+
const me = await api.me();
|
|
894
|
+
dispatch({ type: 'SET_USER_DATA', me });
|
|
895
|
+
await loadTabData();
|
|
896
|
+
});
|
|
897
|
+
showToast('Project created', 'success');
|
|
898
|
+
}
|
|
899
|
+
catch {
|
|
900
|
+
showToast('Failed to create project', 'error');
|
|
901
|
+
return;
|
|
805
902
|
}
|
|
806
|
-
catch { }
|
|
807
903
|
break;
|
|
808
904
|
}
|
|
809
905
|
case 'initiate-consensus': {
|
|
810
906
|
try {
|
|
811
|
-
await
|
|
812
|
-
|
|
813
|
-
|
|
907
|
+
await withOperation('Initiating approval', async () => {
|
|
908
|
+
await api.initiateApproval(projectId, modal.action, modal.targetUserId);
|
|
909
|
+
const approvals = await api.listApprovals(projectId);
|
|
910
|
+
dispatch({ type: 'SET_PENDING_APPROVALS', approvals });
|
|
911
|
+
});
|
|
912
|
+
showToast('Approval initiated', 'success');
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
showToast('Failed to initiate approval', 'error');
|
|
916
|
+
return;
|
|
814
917
|
}
|
|
815
|
-
catch { }
|
|
816
918
|
break;
|
|
817
919
|
}
|
|
818
920
|
case 'approve-action': {
|
|
819
921
|
try {
|
|
820
|
-
await
|
|
821
|
-
|
|
822
|
-
|
|
922
|
+
await withOperation('Approving', async () => {
|
|
923
|
+
await api.approveAction(projectId, modal.approvalId);
|
|
924
|
+
const approvals = await api.listApprovals(projectId);
|
|
925
|
+
dispatch({ type: 'SET_PENDING_APPROVALS', approvals });
|
|
926
|
+
});
|
|
927
|
+
showToast('Approved', 'success');
|
|
928
|
+
}
|
|
929
|
+
catch {
|
|
930
|
+
showToast('Failed to approve', 'error');
|
|
931
|
+
return;
|
|
823
932
|
}
|
|
824
|
-
catch { }
|
|
825
933
|
break;
|
|
826
934
|
}
|
|
827
935
|
case 'confirm-regen-link': {
|
|
828
936
|
try {
|
|
829
|
-
await
|
|
830
|
-
|
|
831
|
-
|
|
937
|
+
await withOperation('Regenerating link', async () => {
|
|
938
|
+
await api.regenerateInviteLink(projectId, modal.role);
|
|
939
|
+
const links = await api.listInviteLinks(projectId);
|
|
940
|
+
dispatch({ type: 'SET_INVITE_LINKS', links });
|
|
941
|
+
});
|
|
942
|
+
showToast('Link regenerated', 'success');
|
|
943
|
+
}
|
|
944
|
+
catch {
|
|
945
|
+
showToast('Failed to regenerate link', 'error');
|
|
946
|
+
return;
|
|
832
947
|
}
|
|
833
|
-
catch { }
|
|
834
948
|
break;
|
|
835
949
|
}
|
|
836
950
|
}
|
|
@@ -869,7 +983,9 @@ async function launchDashboard() {
|
|
|
869
983
|
}
|
|
870
984
|
}
|
|
871
985
|
}
|
|
872
|
-
catch {
|
|
986
|
+
catch {
|
|
987
|
+
showToast('Failed to load data', 'error');
|
|
988
|
+
}
|
|
873
989
|
}
|
|
874
990
|
async function loadMoreMemories() {
|
|
875
991
|
const projectId = state.activeProjectId;
|
|
@@ -880,7 +996,10 @@ async function launchDashboard() {
|
|
|
880
996
|
const result = await api.listMemories(projectId, { limit: PAGE_SIZE, offset: state.memories.length });
|
|
881
997
|
dispatch({ type: 'SET_MEMORIES', memories: result.memories, total: result.total, append: true });
|
|
882
998
|
}
|
|
883
|
-
catch {
|
|
999
|
+
catch {
|
|
1000
|
+
showToast('Failed to load more', 'error');
|
|
1001
|
+
dispatch({ type: 'SET_LOADING', loading: false });
|
|
1002
|
+
}
|
|
884
1003
|
}
|
|
885
1004
|
async function executeSearch() {
|
|
886
1005
|
const projectId = state.activeProjectId;
|
|
@@ -891,63 +1010,88 @@ async function launchDashboard() {
|
|
|
891
1010
|
const result = await api.searchMemories(projectId, state.searchQuery);
|
|
892
1011
|
dispatch({ type: 'SET_SEARCH_RESULTS', results: result.memories });
|
|
893
1012
|
}
|
|
894
|
-
catch {
|
|
1013
|
+
catch {
|
|
1014
|
+
showToast('Search failed', 'error');
|
|
1015
|
+
}
|
|
895
1016
|
dispatch({ type: 'SET_SEARCHING', searching: false });
|
|
896
1017
|
}
|
|
897
1018
|
// ------ SSE feed ------
|
|
898
1019
|
function connectFeed(projectId) {
|
|
899
1020
|
if (feedDisconnect)
|
|
900
1021
|
feedDisconnect();
|
|
901
|
-
const conn = api.connectFeed(projectId,
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1022
|
+
const conn = api.connectFeed(projectId, {
|
|
1023
|
+
onEvent: (eventType, data) => {
|
|
1024
|
+
if (eventType === 'memory') {
|
|
1025
|
+
const event = data;
|
|
1026
|
+
if (event.operation === 'create') {
|
|
1027
|
+
dispatch({ type: 'ADD_MEMORY', memory: { id: event.id, body: event.body, updated_at: event.updated_at } });
|
|
1028
|
+
dispatch({ type: 'SET_LIVE_DOT', memoryId: event.id });
|
|
1029
|
+
if (liveDotTimer)
|
|
1030
|
+
clearTimeout(liveDotTimer);
|
|
1031
|
+
liveDotTimer = setTimeout(() => {
|
|
1032
|
+
dispatch({ type: 'CLEAR_LIVE_DOT' });
|
|
1033
|
+
}, 10000);
|
|
1034
|
+
}
|
|
1035
|
+
else if (event.operation === 'update') {
|
|
1036
|
+
const updated = { id: event.id, body: event.body, updated_at: event.updated_at };
|
|
1037
|
+
state = {
|
|
1038
|
+
...state,
|
|
1039
|
+
memories: state.memories.map(m => m.id === event.id ? updated : m),
|
|
1040
|
+
searchResults: state.searchResults?.map(m => m.id === event.id ? updated : m) ?? null,
|
|
1041
|
+
};
|
|
1042
|
+
dispatch({ type: 'SET_LIVE_DOT', memoryId: event.id });
|
|
1043
|
+
if (liveDotTimer)
|
|
1044
|
+
clearTimeout(liveDotTimer);
|
|
1045
|
+
liveDotTimer = setTimeout(() => {
|
|
1046
|
+
dispatch({ type: 'CLEAR_LIVE_DOT' });
|
|
1047
|
+
}, 10000);
|
|
1048
|
+
}
|
|
912
1049
|
}
|
|
913
|
-
else if (
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
if (liveDotTimer)
|
|
923
|
-
clearTimeout(liveDotTimer);
|
|
924
|
-
liveDotTimer = setTimeout(() => {
|
|
925
|
-
dispatch({ type: 'CLEAR_LIVE_DOT' });
|
|
926
|
-
}, 10000);
|
|
1050
|
+
else if (eventType === 'role_change') {
|
|
1051
|
+
api.me().then(me => {
|
|
1052
|
+
dispatch({ type: 'SET_USER_DATA', me });
|
|
1053
|
+
}).catch(() => {
|
|
1054
|
+
if (Date.now() - lastSyncErrorAt > 30000) {
|
|
1055
|
+
lastSyncErrorAt = Date.now();
|
|
1056
|
+
showToast('Sync error', 'error');
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
927
1059
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1060
|
+
else if (eventType === 'approval') {
|
|
1061
|
+
api.listApprovals(projectId).then(approvals => {
|
|
1062
|
+
dispatch({ type: 'SET_PENDING_APPROVALS', approvals });
|
|
1063
|
+
}).catch(() => {
|
|
1064
|
+
if (Date.now() - lastSyncErrorAt > 30000) {
|
|
1065
|
+
lastSyncErrorAt = Date.now();
|
|
1066
|
+
showToast('Sync error', 'error');
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
else if (eventType === 'notification') {
|
|
1071
|
+
api.listNotifications().then(notifications => {
|
|
1072
|
+
dispatch({ type: 'SET_NOTIFICATIONS', notifications });
|
|
1073
|
+
api.markNotificationsSeen().catch(() => { });
|
|
1074
|
+
}).catch(() => {
|
|
1075
|
+
if (Date.now() - lastSyncErrorAt > 30000) {
|
|
1076
|
+
lastSyncErrorAt = Date.now();
|
|
1077
|
+
showToast('Sync error', 'error');
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
onConnected: () => {
|
|
1083
|
+
dispatch({ type: 'SET_SSE_CONNECTED', connected: true });
|
|
1084
|
+
// Refresh memories on reconnect to catch anything missed
|
|
1085
|
+
api.listMemories(projectId, { limit: PAGE_SIZE, offset: 0 }).then(result => {
|
|
1086
|
+
if (result)
|
|
1087
|
+
dispatch({ type: 'SET_MEMORIES', memories: result.memories, total: result.total });
|
|
946
1088
|
}).catch(() => { });
|
|
947
|
-
}
|
|
1089
|
+
},
|
|
1090
|
+
onDisconnected: () => {
|
|
1091
|
+
dispatch({ type: 'SET_SSE_CONNECTED', connected: false });
|
|
1092
|
+
},
|
|
948
1093
|
});
|
|
949
1094
|
feedDisconnect = conn.disconnect;
|
|
950
|
-
dispatch({ type: 'SET_SSE_CONNECTED', connected: true });
|
|
951
1095
|
}
|
|
952
1096
|
// ------ File watchers for MCP config changes ------
|
|
953
1097
|
function setupFileWatchers() {
|