File size: 6,444 Bytes
921d377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
 * BridgeStatusPanel — Shows the connection status of a Teams meeting bridge.
 *
 * Displays:
 *   - Connected/disconnected state with visual indicator
 *   - Meeting chat ID
 *   - Messages ingested count
 *   - Voice detection toggle (chat-only vs chat+voice)
 *   - Disconnect button
 *
 * This is a compact panel meant to sit inside the MeetingRoom right rail
 * or as an overlay badge.
 */
import React, { useState } from 'react';
import { Wifi, Mic, MessageSquare, Unplug, RefreshCw, ChevronDown, ChevronUp, } from 'lucide-react';
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
export function BridgeStatusPanel({ status, onDisconnect, onToggleVoice, onRefresh, loading, }) {
    const [expanded, setExpanded] = useState(false);
    if (!status || !status.connected) {
        return null; // Don't render if not connected
    }
    const bridge = status.bridge;
    const voiceEnabled = bridge?.voice_enabled ?? false;
    const messagesSeen = bridge?.messages_seen ?? 0;
    const chatId = bridge?.chat_id || '(resolving...)';
    return (<div className="bg-gradient-to-r from-blue-500/10 to-purple-500/10 border border-blue-500/20 rounded-xl overflow-hidden">
      {/* Header — always visible */}
      <button onClick={() => setExpanded(!expanded)} className="w-full flex items-center justify-between px-4 py-3 hover:bg-white/5 transition-colors">
        <div className="flex items-center gap-2.5">
          <div className="relative">
            <Wifi size={16} className="text-blue-400"/>
            <div className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-green-400 rounded-full animate-pulse"/>
          </div>
          <div className="text-left">
            <p className="text-xs font-medium text-white/80">Teams Bridge Active</p>
            <p className="text-[10px] text-white/40">
              {messagesSeen} message{messagesSeen !== 1 ? 's' : ''} ingested
            </p>
          </div>
        </div>
        <div className="flex items-center gap-2">
          {/* Voice indicator */}
          <div className={`flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium ${voiceEnabled
            ? 'bg-purple-500/20 text-purple-300'
            : 'bg-white/10 text-white/40'}`}>
            {voiceEnabled ? <Mic size={10}/> : <MessageSquare size={10}/>}
            {voiceEnabled ? 'Voice' : 'Chat'}
          </div>
          {expanded ? <ChevronUp size={14} className="text-white/40"/> : <ChevronDown size={14} className="text-white/40"/>}
        </div>
      </button>

      {/* Expanded details */}
      {expanded && (<div className="px-4 pb-3 space-y-3 border-t border-white/5">
          {/* Chat ID */}
          <div className="pt-3">
            <p className="text-[10px] text-white/30 uppercase tracking-wider mb-1">Meeting Chat</p>
            <p className="text-xs text-white/60 font-mono truncate">{chatId}</p>
          </div>

          {/* Stats */}
          <div className="grid grid-cols-2 gap-2">
            <div className="p-2 bg-white/5 rounded-lg">
              <p className="text-[10px] text-white/30">Messages</p>
              <p className="text-sm font-semibold text-white">{messagesSeen}</p>
            </div>
            <div className="p-2 bg-white/5 rounded-lg">
              <p className="text-[10px] text-white/30">Poll Rate</p>
              <p className="text-sm font-semibold text-white">{bridge?.poll_interval || 5}s</p>
            </div>
          </div>

          {/* Voice toggle */}
          <div>
            <p className="text-[10px] text-white/30 uppercase tracking-wider mb-2">Input Mode</p>
            <div className="flex gap-2">
              <button onClick={() => onToggleVoice(false)} className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg text-xs font-medium transition-all ${!voiceEnabled
                ? 'bg-blue-500/20 text-blue-300 border border-blue-500/30'
                : 'bg-white/5 text-white/40 border border-white/10 hover:bg-white/10'}`}>
                <MessageSquare size={12}/>
                Chat
              </button>
              <button onClick={() => onToggleVoice(true)} className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg text-xs font-medium transition-all ${voiceEnabled
                ? 'bg-purple-500/20 text-purple-300 border border-purple-500/30'
                : 'bg-white/5 text-white/40 border border-white/10 hover:bg-white/10'}`}>
                <Mic size={12}/>
                Voice
              </button>
            </div>
          </div>

          {/* Actions */}
          <div className="flex gap-2 pt-1">
            <button onClick={onRefresh} disabled={loading} className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-xs text-white/60 hover:bg-white/10 transition-colors disabled:opacity-40">
              <RefreshCw size={12} className={loading ? 'animate-spin' : ''}/>
              Refresh
            </button>
            <button onClick={onDisconnect} className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 bg-red-500/10 border border-red-500/20 rounded-lg text-xs text-red-400 hover:bg-red-500/20 transition-colors">
              <Unplug size={12}/>
              Disconnect
            </button>
          </div>
        </div>)}
    </div>);
}
// ---------------------------------------------------------------------------
// Compact badge variant (for inline use in headers)
// ---------------------------------------------------------------------------
export function BridgeBadge({ status, onClick, }) {
    if (!status?.connected)
        return null;
    const voiceEnabled = status.bridge?.voice_enabled ?? false;
    const messagesSeen = status.bridge?.messages_seen ?? 0;
    return (<button onClick={onClick} className="flex items-center gap-1.5 px-2.5 py-1 bg-blue-500/15 border border-blue-500/20 rounded-full text-xs text-blue-300 hover:bg-blue-500/25 transition-colors" title="Teams Bridge connected">
      <div className="relative">
        <Wifi size={11}/>
        <div className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 bg-green-400 rounded-full"/>
      </div>
      <span>{messagesSeen}</span>
      {voiceEnabled && <Mic size={10} className="text-purple-300"/>}
    </button>);
}