ausername-12345 commited on
Commit
eeb3363
·
1 Parent(s): 76078c0

remove group calling, fix video toggle visibility, fix empty Google setup container

Browse files

- Removed all group call code (backend handlers, frontend logic,
WebSocket dispatch, HTML) — only 1-to-1 calling remains
- Video toggle button now properly shown/hidden in setupCallMedia
(visible during video calls, hidden during audio calls)
- Empty tab container during Google account setup is now hidden
(auth-tabs div hidden in showGoogleSetup, restored in showTab)

Files changed (4) hide show
  1. src/main.py +0 -8
  2. src/static/app.js +11 -292
  3. src/static/index.html +1 -10
  4. src/ws_manager.py +1 -90
src/main.py CHANGED
@@ -65,14 +65,6 @@ async def websocket_endpoint(websocket: WebSocket, token: str, db: Session = Dep
65
  await manager.handle_ice_candidate(msg, user)
66
  elif msg_type == "call_end":
67
  await manager.handle_call_end(msg, user)
68
- elif msg_type == "group_call_start":
69
- await manager.handle_group_call_start(msg, user, db)
70
- elif msg_type == "group_call_accept":
71
- await manager.handle_group_call_accept(msg, user, db)
72
- elif msg_type == "group_call_leave":
73
- await manager.handle_group_call_leave(msg, user)
74
- elif msg_type == "group_call_end":
75
- await manager.handle_group_call_end(msg, user)
76
 
77
  except WebSocketDisconnect:
78
  manager.disconnect(user.id)
 
65
  await manager.handle_ice_candidate(msg, user)
66
  elif msg_type == "call_end":
67
  await manager.handle_call_end(msg, user)
 
 
 
 
 
 
 
 
68
 
69
  except WebSocketDisconnect:
70
  manager.disconnect(user.id)
src/static/app.js CHANGED
@@ -34,12 +34,7 @@ const ICE_SERVERS = {
34
  ]
35
  };
36
 
37
- // ── Group Call State ──────────────────────────────────────────────────────────
38
- let activeGroupCallId = null;
39
- let activeGroupCallType = null;
40
- let groupCallParticipants = {}; // userId -> { display_name, avatar_color, pc, stream }
41
- let groupCallLocalStream = null;
42
- let groupCallRinging = false;
43
 
44
  // ── Bootstrap ───────────────────────────────────────────────────────────────
45
 
@@ -133,6 +128,7 @@ async function apiFetch(path, tok, method = "GET", body = null) {
133
  // ── Auth ─────────────────────────────────────────────────────────────────────
134
 
135
  function showTab(tab) {
 
136
  document.getElementById("form-login").classList.toggle("hidden", tab !== "login");
137
  document.getElementById("form-register").classList.toggle("hidden", tab !== "register");
138
  document.getElementById("form-google-setup").classList.add("hidden");
@@ -213,8 +209,7 @@ function googleLogin() {
213
  }
214
 
215
  function showGoogleSetup() {
216
- document.getElementById("tab-login").classList.add("hidden");
217
- document.getElementById("tab-register").classList.add("hidden");
218
  document.getElementById("form-login").classList.add("hidden");
219
  document.getElementById("form-register").classList.add("hidden");
220
  document.getElementById("form-google-setup").classList.remove("hidden");
@@ -351,33 +346,13 @@ async function handleWsMessage(evt) {
351
  } else if (msg.type === "read_receipt") {
352
  handleReadReceipt(msg);
353
  } else if (msg.type === "call_offer") {
354
- if (msg.group_id) {
355
- handleGroupCallOffer(msg.from, msg.sdp);
356
- } else {
357
- handleCallOffer(msg);
358
- }
359
  } else if (msg.type === "call_answer") {
360
- if (msg.group_id) {
361
- handleGroupCallAnswer(msg.from, msg.sdp);
362
- } else {
363
- handleCallAnswer(msg);
364
- }
365
  } else if (msg.type === "ice_candidate") {
366
  handleIceCandidate(msg);
367
  } else if (msg.type === "call_end") {
368
  handleCallEnd(msg);
369
- } else if (msg.type === "group_call_started") {
370
- handleGroupCallStarted(msg);
371
- } else if (msg.type === "group_call_ringing") {
372
- handleGroupCallRinging(msg);
373
- } else if (msg.type === "group_call_joined") {
374
- handleGroupCallJoined(msg);
375
- } else if (msg.type === "group_participant_joined") {
376
- handleGroupParticipantJoined(msg);
377
- } else if (msg.type === "group_participant_left") {
378
- handleGroupParticipantLeft(msg);
379
- } else if (msg.type === "group_call_ended") {
380
- handleGroupCallEnded(msg);
381
  }
382
  }
383
 
@@ -523,13 +498,8 @@ function hideIncomingCall() {
523
  }
524
 
525
  async function startCall(type) {
 
526
  document.getElementById("call-dropdown").classList.add("hidden");
527
- if (!currentChat) return;
528
- if (currentChat.type === "group") {
529
- await startGroupCall(type);
530
- return;
531
- }
532
- if (!currentChat.other_user) return;
533
  callType = type;
534
  callTarget = currentChat.other_user;
535
  callState = "calling";
@@ -568,8 +538,7 @@ async function startCall(type) {
568
  }
569
 
570
  function handleCallOffer(msg) {
571
- if (msg.group_id) return;
572
- if (callState || activeGroupCallId) {
573
  ws.send(JSON.stringify({ type: "call_end", target_id: msg.from }));
574
  return;
575
  }
@@ -589,10 +558,6 @@ function handleCallOffer(msg) {
589
 
590
  async function acceptCall() {
591
  hideIncomingCall();
592
- if (groupCallRinging) {
593
- await acceptGroupCall();
594
- return;
595
- }
596
  callState = "connected";
597
  try {
598
  const constraints = { audio: true, video: callType === "video" };
@@ -632,10 +597,6 @@ async function acceptCall() {
632
 
633
  function rejectCall() {
634
  hideIncomingCall();
635
- if (groupCallRinging) {
636
- endGroupCall();
637
- return;
638
- }
639
  if (ws && ws.readyState === WebSocket.OPEN && callTarget) {
640
  ws.send(JSON.stringify({ type: "call_end", target_id: callTarget.id }));
641
  }
@@ -666,16 +627,9 @@ function handleCallEnd(msg) {
666
  }
667
  cleanupCall();
668
  }
669
- if (activeGroupCallId) {
670
- endGroupCall();
671
- }
672
  }
673
 
674
  function endCall() {
675
- if (activeGroupCallId) {
676
- endGroupCall();
677
- return;
678
- }
679
  if (ws && ws.readyState === WebSocket.OPEN && callTarget) {
680
  ws.send(JSON.stringify({ type: "call_end", target_id: callTarget.id }));
681
  }
@@ -703,12 +657,15 @@ function setupCallMedia() {
703
  if (lv) lv.srcObject = localStream;
704
  const videoContent = document.getElementById("call-video-content");
705
  const audioContent = document.getElementById("call-audio-content");
 
706
  if (callType === "video") {
707
  if (videoContent) videoContent.classList.remove("hidden");
708
  if (audioContent) audioContent.classList.add("hidden");
 
709
  } else {
710
  if (videoContent) videoContent.classList.add("hidden");
711
  if (audioContent) audioContent.classList.remove("hidden");
 
712
  const av = document.getElementById("call-audio-avatar");
713
  if (av) {
714
  av.textContent = callTarget.display_name[0].toUpperCase();
@@ -737,244 +694,6 @@ function toggleVideo() {
737
  }
738
  }
739
 
740
- // ── Group Call Functions ──────────────────────────────────────────────────────
741
-
742
- async function startGroupCall(type) {
743
- document.getElementById("call-dropdown").classList.add("hidden");
744
- if (activeGroupCallId) return;
745
- activeGroupCallId = currentChat.id;
746
- activeGroupCallType = type;
747
- try {
748
- groupCallLocalStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: type === "video" });
749
- ws.send(JSON.stringify({ type: "group_call_start", group_id: activeGroupCallId, call_type: type }));
750
- showCallModal();
751
- document.getElementById("call-status").textContent = "Calling everyone…";
752
- document.getElementById("call-peer-name").textContent = currentChat.name || "Group Call";
753
- document.getElementById("group-video-content").classList.remove("hidden");
754
- document.getElementById("call-video-content").classList.add("hidden");
755
- document.getElementById("call-audio-content").classList.add("hidden");
756
- document.getElementById("call-video-btn").classList.remove("hidden");
757
- const lv = document.getElementById("group-local-video");
758
- if (lv) lv.srcObject = groupCallLocalStream;
759
- showGroupParticipants();
760
- } catch (e) {
761
- console.error("startGroupCall error:", e);
762
- endGroupCall();
763
- }
764
- }
765
-
766
- function handleGroupCallStarted(msg) {
767
- // We're the initiator — participant list tells us who's in the group
768
- activeGroupCallId = msg.group_id;
769
- showGroupCallUI();
770
- }
771
-
772
- function handleGroupCallRinging(msg) {
773
- // Someone else started a call — show incoming notification
774
- if (activeGroupCallId) { ws.send(JSON.stringify({ type: "group_call_end", group_id: msg.group_id })); return; }
775
- activeGroupCallId = msg.group_id;
776
- activeGroupCallType = msg.call_type;
777
- groupCallRinging = true;
778
- document.getElementById("incoming-caller-name").textContent = `${msg.initiator_name} started a group call`;
779
- document.getElementById("incoming-call-type").textContent = msg.call_type === "video" ? "Video Call" : "Audio Call";
780
- document.getElementById("incoming-call-type-icon").textContent = msg.call_type === "video" ? "📹" : "📞";
781
- const av = document.getElementById("incoming-avatar");
782
- av.textContent = msg.initiator_name[0].toUpperCase();
783
- av.style.background = msg.initiator_avatar || "#6366f1";
784
- showIncomingCall();
785
- }
786
-
787
- async function acceptGroupCall() {
788
- hideIncomingCall();
789
- if (!activeGroupCallId) return;
790
- groupCallRinging = false;
791
- try {
792
- groupCallLocalStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: activeGroupCallType === "video" });
793
- ws.send(JSON.stringify({ type: "group_call_accept", group_id: activeGroupCallId }));
794
- showCallModal();
795
- document.getElementById("call-status").textContent = "Connecting…";
796
- document.getElementById("call-peer-name").textContent = "Group Call";
797
- setupGroupCallModal();
798
- } catch (e) {
799
- console.error("acceptGroupCall error:", e);
800
- endGroupCall();
801
- }
802
- }
803
-
804
- function handleGroupCallJoined(msg) {
805
- // We joined successfully — create peer connections to existing participants
806
- document.getElementById("call-status").textContent = "Connected";
807
- for (const p of msg.participants) {
808
- if (p.id !== me.id) {
809
- createGroupPeerConnection(p.id, p.display_name, p.avatar_color, true);
810
- }
811
- }
812
- showGroupParticipants();
813
- }
814
-
815
- function handleGroupParticipantJoined(msg) {
816
- const uid = msg.user_id;
817
- if (uid === me.id) return;
818
- // Check if we already have a connection
819
- if (groupCallParticipants[uid]) return;
820
- // If we have a lower ID, send offer; otherwise wait for their offer
821
- if (me.id < uid) {
822
- createGroupPeerConnection(uid, msg.display_name, msg.avatar_color, true);
823
- }
824
- showGroupParticipants();
825
- }
826
-
827
- function handleGroupParticipantLeft(msg) {
828
- closeGroupPeerConnection(msg.user_id);
829
- showGroupParticipants();
830
- }
831
-
832
- function handleGroupCallEnded(msg) {
833
- endGroupCall();
834
- document.getElementById("call-status").textContent = "Call ended";
835
- setTimeout(hideCallModal, 1500);
836
- }
837
-
838
- async function createGroupPeerConnection(uid, displayName, avatarColor, sendOffer) {
839
- if (groupCallParticipants[uid]) return;
840
- const pc = new RTCPeerConnection(ICE_SERVERS);
841
- if (groupCallLocalStream) {
842
- groupCallLocalStream.getTracks().forEach(t => pc.addTrack(t, groupCallLocalStream));
843
- }
844
- const stream = new MediaStream();
845
- pc.ontrack = (event) => {
846
- event.streams[0].getTracks().forEach(t => stream.addTrack(t));
847
- const container = document.getElementById("group-remote-videos");
848
- if (container) {
849
- let vid = container.querySelector(`[data-uid="${uid}"]`);
850
- if (!vid) {
851
- showGroupParticipants();
852
- } else {
853
- vid.srcObject = stream;
854
- }
855
- }
856
- };
857
- pc.onicecandidate = (event) => {
858
- if (event.candidate && ws && ws.readyState === WebSocket.OPEN) {
859
- ws.send(JSON.stringify({ type: "ice_candidate", target_id: uid, candidate: event.candidate, group_id: activeGroupCallId }));
860
- }
861
- };
862
- pc.oniceconnectionstatechange = () => {
863
- if (pc.connectionState === "disconnected" || pc.connectionState === "failed") {
864
- closeGroupPeerConnection(uid);
865
- }
866
- };
867
- groupCallParticipants[uid] = { display_name: displayName, avatar_color: avatarColor, pc, stream };
868
-
869
- if (sendOffer) {
870
- try {
871
- const offer = await pc.createOffer();
872
- await pc.setLocalDescription(offer);
873
- ws.send(JSON.stringify({ type: "call_offer", target_id: uid, sdp: pc.localDescription, group_id: activeGroupCallId }));
874
- } catch (e) {
875
- console.error("createOffer error:", e);
876
- }
877
- }
878
- return pc;
879
- }
880
-
881
- function handleGroupCallOffer(uid, sdp) {
882
- // Only create PC if we don't have one and we have a higher ID (we answer)
883
- if (groupCallParticipants[uid]) {
884
- // Existing PC, might be a renegotiation
885
- const pc = groupCallParticipants[uid].pc;
886
- if (pc.signalingState !== "stable") return;
887
- pc.setRemoteDescription(new RTCSessionDescription(sdp)).catch(() => {});
888
- return;
889
- }
890
- if (me.id < uid) return; // We should be sending offer, not receiving
891
- createGroupPeerConnection(uid, "", "", false).then(async (pc) => {
892
- if (!pc) return;
893
- await pc.setRemoteDescription(new RTCSessionDescription(sdp));
894
- const answer = await pc.createAnswer();
895
- await pc.setLocalDescription(answer);
896
- ws.send(JSON.stringify({ type: "call_answer", target_id: uid, sdp: pc.localDescription, group_id: activeGroupCallId }));
897
- });
898
- }
899
-
900
- function handleGroupCallAnswer(uid, sdp) {
901
- const p = groupCallParticipants[uid];
902
- if (p && p.pc && p.pc.signalingState !== "closed") {
903
- p.pc.setRemoteDescription(new RTCSessionDescription(sdp)).catch(() => {});
904
- }
905
- }
906
-
907
- function closeGroupPeerConnection(uid) {
908
- const p = groupCallParticipants[uid];
909
- if (p) {
910
- if (p.pc) p.pc.close();
911
- if (p.stream) p.stream.getTracks().forEach(t => t.stop());
912
- delete groupCallParticipants[uid];
913
- }
914
- }
915
-
916
- function showGroupParticipants() {
917
- const container = document.getElementById("group-remote-videos");
918
- if (!container) return;
919
- let html = "";
920
- for (const [uid, p] of Object.entries(groupCallParticipants)) {
921
- const name = p.display_name || "User";
922
- html += `<div class="relative glass rounded-2xl overflow-hidden aspect-video flex items-center justify-center">
923
- <video data-uid="${uid}" autoplay playsinline class="absolute inset-0 w-full h-full object-cover"></video>
924
- <div class="absolute bottom-2 left-2 text-xs text-white/80 bg-black/40 px-2 py-0.5 rounded-lg">${escHtml(name)}</div>
925
- </div>`;
926
- }
927
- if (!html) html = '<div class="col-span-full flex items-center justify-center text-gray-500 text-sm">Waiting for participants…</div>';
928
- container.innerHTML = html;
929
- // Attach streams to video elements
930
- for (const [uid, p] of Object.entries(groupCallParticipants)) {
931
- const vid = container.querySelector(`[data-uid="${uid}"]`);
932
- if (vid && p.stream) vid.srcObject = p.stream;
933
- }
934
- }
935
-
936
- function setupGroupCallModal() {
937
- const lv = document.getElementById("group-local-video");
938
- if (lv) lv.srcObject = groupCallLocalStream;
939
- document.getElementById("group-video-content").classList.remove("hidden");
940
- document.getElementById("call-video-content").classList.add("hidden");
941
- document.getElementById("call-audio-content").classList.add("hidden");
942
- document.getElementById("call-video-btn").classList.remove("hidden");
943
- document.getElementById("call-status").textContent = "Connected";
944
- }
945
-
946
- function showGroupCallUI() {
947
- showCallModal();
948
- document.getElementById("call-status").textContent = "Connected";
949
- document.getElementById("call-peer-name").textContent = currentChat?.name || "Group Call";
950
- const lv = document.getElementById("group-local-video");
951
- if (lv) lv.srcObject = groupCallLocalStream;
952
- document.getElementById("group-video-content").classList.remove("hidden");
953
- document.getElementById("call-video-content").classList.add("hidden");
954
- document.getElementById("call-audio-content").classList.add("hidden");
955
- document.getElementById("call-video-btn").classList.remove("hidden");
956
- showGroupParticipants();
957
- }
958
-
959
- function endGroupCall() {
960
- for (const uid of Object.keys(groupCallParticipants)) {
961
- closeGroupPeerConnection(parseInt(uid));
962
- }
963
- groupCallParticipants = {};
964
- if (groupCallLocalStream) {
965
- groupCallLocalStream.getTracks().forEach(t => t.stop());
966
- groupCallLocalStream = null;
967
- }
968
- if (activeGroupCallId && ws && ws.readyState === WebSocket.OPEN) {
969
- ws.send(JSON.stringify({ type: groupCallRinging ? "group_call_end" : "group_call_leave", group_id: activeGroupCallId }));
970
- }
971
- activeGroupCallId = null;
972
- activeGroupCallType = null;
973
- groupCallRinging = false;
974
- hideCallModal();
975
- hideIncomingCall();
976
- }
977
-
978
  async function loadChats() {
979
  [conversations, groups] = await Promise.all([
980
  apiFetch("/chat/conversations"),
@@ -1080,7 +799,7 @@ async function openGroupChat(group) {
1080
  document.getElementById("chat-avatar").style.background = full.avatar_color;
1081
  document.getElementById("chat-name").textContent = full.name;
1082
  document.getElementById("chat-sub").textContent = `${full.members.length} members`;
1083
- document.getElementById("call-btn").classList.remove("hidden");
1084
  document.getElementById("call-dropdown").classList.add("hidden");
1085
  document.getElementById("group-settings-btn").classList.remove("hidden");
1086
 
 
34
  ]
35
  };
36
 
37
+
 
 
 
 
 
38
 
39
  // ── Bootstrap ───────────────────────────────────────────────────────────────
40
 
 
128
  // ── Auth ─────────────────────────────────────────────────────────────────────
129
 
130
  function showTab(tab) {
131
+ document.getElementById("auth-tabs").classList.remove("hidden");
132
  document.getElementById("form-login").classList.toggle("hidden", tab !== "login");
133
  document.getElementById("form-register").classList.toggle("hidden", tab !== "register");
134
  document.getElementById("form-google-setup").classList.add("hidden");
 
209
  }
210
 
211
  function showGoogleSetup() {
212
+ document.getElementById("auth-tabs").classList.add("hidden");
 
213
  document.getElementById("form-login").classList.add("hidden");
214
  document.getElementById("form-register").classList.add("hidden");
215
  document.getElementById("form-google-setup").classList.remove("hidden");
 
346
  } else if (msg.type === "read_receipt") {
347
  handleReadReceipt(msg);
348
  } else if (msg.type === "call_offer") {
349
+ handleCallOffer(msg);
 
 
 
 
350
  } else if (msg.type === "call_answer") {
351
+ handleCallAnswer(msg);
 
 
 
 
352
  } else if (msg.type === "ice_candidate") {
353
  handleIceCandidate(msg);
354
  } else if (msg.type === "call_end") {
355
  handleCallEnd(msg);
 
 
 
 
 
 
 
 
 
 
 
 
356
  }
357
  }
358
 
 
498
  }
499
 
500
  async function startCall(type) {
501
+ if (!currentChat || currentChat.type !== "dm" || !currentChat.other_user) return;
502
  document.getElementById("call-dropdown").classList.add("hidden");
 
 
 
 
 
 
503
  callType = type;
504
  callTarget = currentChat.other_user;
505
  callState = "calling";
 
538
  }
539
 
540
  function handleCallOffer(msg) {
541
+ if (callState) {
 
542
  ws.send(JSON.stringify({ type: "call_end", target_id: msg.from }));
543
  return;
544
  }
 
558
 
559
  async function acceptCall() {
560
  hideIncomingCall();
 
 
 
 
561
  callState = "connected";
562
  try {
563
  const constraints = { audio: true, video: callType === "video" };
 
597
 
598
  function rejectCall() {
599
  hideIncomingCall();
 
 
 
 
600
  if (ws && ws.readyState === WebSocket.OPEN && callTarget) {
601
  ws.send(JSON.stringify({ type: "call_end", target_id: callTarget.id }));
602
  }
 
627
  }
628
  cleanupCall();
629
  }
 
 
 
630
  }
631
 
632
  function endCall() {
 
 
 
 
633
  if (ws && ws.readyState === WebSocket.OPEN && callTarget) {
634
  ws.send(JSON.stringify({ type: "call_end", target_id: callTarget.id }));
635
  }
 
657
  if (lv) lv.srcObject = localStream;
658
  const videoContent = document.getElementById("call-video-content");
659
  const audioContent = document.getElementById("call-audio-content");
660
+ const videoBtn = document.getElementById("call-video-btn");
661
  if (callType === "video") {
662
  if (videoContent) videoContent.classList.remove("hidden");
663
  if (audioContent) audioContent.classList.add("hidden");
664
+ if (videoBtn) videoBtn.classList.remove("hidden");
665
  } else {
666
  if (videoContent) videoContent.classList.add("hidden");
667
  if (audioContent) audioContent.classList.remove("hidden");
668
+ if (videoBtn) videoBtn.classList.add("hidden");
669
  const av = document.getElementById("call-audio-avatar");
670
  if (av) {
671
  av.textContent = callTarget.display_name[0].toUpperCase();
 
694
  }
695
  }
696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  async function loadChats() {
698
  [conversations, groups] = await Promise.all([
699
  apiFetch("/chat/conversations"),
 
799
  document.getElementById("chat-avatar").style.background = full.avatar_color;
800
  document.getElementById("chat-name").textContent = full.name;
801
  document.getElementById("chat-sub").textContent = `${full.members.length} members`;
802
+ document.getElementById("call-btn").classList.add("hidden");
803
  document.getElementById("call-dropdown").classList.add("hidden");
804
  document.getElementById("group-settings-btn").classList.remove("hidden");
805
 
src/static/index.html CHANGED
@@ -48,7 +48,7 @@
48
 
49
  <div class="glass rounded-2xl p-8 shadow-2xl">
50
  <!-- Tabs -->
51
- <div class="flex gap-1 mb-6 bg-white/5 p-1 rounded-xl">
52
  <button onclick="showTab('login')" id="tab-login" class="flex-1 py-2 rounded-lg text-sm font-medium transition-all glass-btn-active text-white">Sign In</button>
53
  <button onclick="showTab('register')" id="tab-register" class="flex-1 py-2 rounded-lg text-sm font-medium transition-all text-gray-400">Create Account</button>
54
  </div>
@@ -360,15 +360,6 @@
360
  <video id="local-video" autoplay playsinline muted class="w-full h-full object-cover"></video>
361
  </div>
362
  </div>
363
- <!-- Group video content (multiple participants) -->
364
- <div id="group-video-content" class="hidden flex-1 flex flex-col p-4">
365
- <div id="group-remote-videos" class="flex-1 grid grid-cols-2 gap-3 auto-rows-[40%]"></div>
366
- <div class="flex justify-center mt-3">
367
- <div class="w-32 h-24 rounded-xl overflow-hidden border border-white/10">
368
- <video id="group-local-video" autoplay playsinline muted class="w-full h-full object-cover"></video>
369
- </div>
370
- </div>
371
- </div>
372
  <!-- Audio content -->
373
  <div id="call-audio-content" class="hidden flex-1 flex flex-col items-center justify-center gap-4">
374
  <div id="call-audio-avatar" class="w-28 h-28 rounded-full flex items-center justify-center text-white text-5xl font-semibold"></div>
 
48
 
49
  <div class="glass rounded-2xl p-8 shadow-2xl">
50
  <!-- Tabs -->
51
+ <div id="auth-tabs" class="flex gap-1 mb-6 bg-white/5 p-1 rounded-xl">
52
  <button onclick="showTab('login')" id="tab-login" class="flex-1 py-2 rounded-lg text-sm font-medium transition-all glass-btn-active text-white">Sign In</button>
53
  <button onclick="showTab('register')" id="tab-register" class="flex-1 py-2 rounded-lg text-sm font-medium transition-all text-gray-400">Create Account</button>
54
  </div>
 
360
  <video id="local-video" autoplay playsinline muted class="w-full h-full object-cover"></video>
361
  </div>
362
  </div>
 
 
 
 
 
 
 
 
 
363
  <!-- Audio content -->
364
  <div id="call-audio-content" class="hidden flex-1 flex flex-col items-center justify-center gap-4">
365
  <div id="call-audio-avatar" class="w-28 h-28 rounded-full flex items-center justify-center text-white text-5xl font-semibold"></div>
src/ws_manager.py CHANGED
@@ -1,6 +1,6 @@
1
  from fastapi import WebSocket
2
  from sqlalchemy.orm import Session
3
- from typing import Dict, Optional, List
4
  import json
5
  from datetime import datetime
6
 
@@ -198,93 +198,4 @@ class ConnectionManager:
198
  "read_by": sender.id
199
  })
200
 
201
- # ── Group Calls ────────────────────────────────────────────────────────────
202
-
203
- active_group_calls: Dict[int, dict] = {}
204
-
205
- async def handle_group_call_start(self, msg: dict, sender: User, db: Session):
206
- group_id = msg.get("group_id")
207
- call_type = msg.get("call_type", "audio")
208
- member = db.query(GroupMember).filter(
209
- GroupMember.group_id == group_id, GroupMember.user_id == sender.id
210
- ).first()
211
- if not member or group_id in self.active_group_calls:
212
- return
213
-
214
- members = db.query(GroupMember).filter(GroupMember.group_id == group_id).all()
215
- member_users = []
216
- for m in members:
217
- u = db.query(User).filter(User.id == m.user_id).first()
218
- if u:
219
- member_users.append({"id": u.id, "display_name": u.display_name, "avatar_color": u.avatar_color})
220
-
221
- self.active_group_calls[group_id] = {
222
- "call_type": call_type,
223
- "participants": [sender.id],
224
- "member_ids": [m.user_id for m in members]
225
- }
226
-
227
- await self.send_to(sender.id, {
228
- "type": "group_call_started", "group_id": group_id,
229
- "call_type": call_type, "participants": member_users
230
- })
231
-
232
- for m in members:
233
- if m.user_id != sender.id and m.user_id in self.active:
234
- await self.send_to(m.user_id, {
235
- "type": "group_call_ringing", "group_id": group_id,
236
- "call_type": call_type, "initiator_id": sender.id,
237
- "initiator_name": sender.display_name,
238
- "initiator_avatar": sender.avatar_color
239
- })
240
-
241
- async def handle_group_call_accept(self, msg: dict, sender: User, db: Session):
242
- group_id = msg.get("group_id")
243
- call = self.active_group_calls.get(group_id)
244
- if not call or sender.id in call["participants"]:
245
- return
246
- call["participants"].append(sender.id)
247
-
248
- for pid in call["participants"]:
249
- if pid != sender.id:
250
- await self.send_to(pid, {
251
- "type": "group_participant_joined", "group_id": group_id,
252
- "user_id": sender.id, "display_name": sender.display_name,
253
- "avatar_color": sender.avatar_color
254
- })
255
-
256
- participant_list = []
257
- for pid in call["participants"]:
258
- if pid != sender.id:
259
- u = db.query(User).filter(User.id == pid).first()
260
- if u:
261
- participant_list.append({"id": u.id, "display_name": u.display_name, "avatar_color": u.avatar_color})
262
-
263
- await self.send_to(sender.id, {
264
- "type": "group_call_joined", "group_id": group_id, "participants": participant_list
265
- })
266
-
267
- async def handle_group_call_leave(self, msg: dict, sender: User):
268
- group_id = msg.get("group_id")
269
- call = self.active_group_calls.get(group_id)
270
- if not call:
271
- return
272
- call["participants"] = [p for p in call["participants"] if p != sender.id]
273
- for pid in call["participants"]:
274
- await self.send_to(pid, {
275
- "type": "group_participant_left", "group_id": group_id, "user_id": sender.id
276
- })
277
- if not call["participants"]:
278
- del self.active_group_calls[group_id]
279
-
280
- async def handle_group_call_end(self, msg: dict, sender: User):
281
- group_id = msg.get("group_id")
282
- call = self.active_group_calls.pop(group_id, None)
283
- if not call:
284
- return
285
- for pid in call["participants"]:
286
- if pid != sender.id:
287
- await self.send_to(pid, {"type": "group_call_ended", "group_id": group_id})
288
-
289
-
290
  manager = ConnectionManager()
 
1
  from fastapi import WebSocket
2
  from sqlalchemy.orm import Session
3
+ from typing import Dict, Optional
4
  import json
5
  from datetime import datetime
6
 
 
198
  "read_by": sender.id
199
  })
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  manager = ConnectionManager()