| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { useState, useCallback, useRef, useEffect } from 'react'; |
| function headers(apiKey) { |
| const h = { 'Content-Type': 'application/json' }; |
| if (apiKey) |
| h['x-api-key'] = apiKey; |
| return h; |
| } |
| export function useBridge({ backendUrl, apiKey, roomId, statusPollInterval = 0 }) { |
| const [status, setStatus] = useState(null); |
| const [loading, setLoading] = useState(false); |
| const [error, setError] = useState(null); |
| const pollRef = useRef(null); |
| |
| const fetchStatus = useCallback(async (rid) => { |
| const id = rid || roomId; |
| if (!id) |
| return null; |
| try { |
| const res = await fetch(`${backendUrl}/v1/teams/bridge/status/${id}`, { |
| headers: headers(apiKey), |
| }); |
| if (!res.ok) |
| throw new Error(`HTTP ${res.status}`); |
| const data = await res.json(); |
| setStatus(data); |
| return data; |
| } |
| catch (e) { |
| |
| setStatus({ room_id: id, connected: false, status: 'not_connected' }); |
| return null; |
| } |
| }, [backendUrl, apiKey, roomId]); |
| |
| useEffect(() => { |
| if (!roomId || !statusPollInterval) |
| return; |
| fetchStatus(); |
| pollRef.current = setInterval(() => fetchStatus(), statusPollInterval); |
| return () => { |
| if (pollRef.current) |
| clearInterval(pollRef.current); |
| }; |
| }, [roomId, statusPollInterval, fetchStatus]); |
| |
| const connect = useCallback(async (params) => { |
| setLoading(true); |
| setError(null); |
| try { |
| const res = await fetch(`${backendUrl}/v1/teams/bridge/connect`, { |
| method: 'POST', |
| headers: headers(apiKey), |
| body: JSON.stringify(params), |
| }); |
| if (!res.ok) { |
| const err = await res.json().catch(() => ({ detail: `HTTP ${res.status}` })); |
| throw new Error(err.detail || 'Failed to connect'); |
| } |
| const data = await res.json(); |
| setStatus(data); |
| return data; |
| } |
| catch (e) { |
| setError(e.message); |
| throw e; |
| } |
| finally { |
| setLoading(false); |
| } |
| }, [backendUrl, apiKey]); |
| |
| const disconnect = useCallback(async (rid) => { |
| const id = rid || roomId; |
| if (!id) |
| return; |
| setLoading(true); |
| try { |
| await fetch(`${backendUrl}/v1/teams/bridge/disconnect/${id}`, { |
| method: 'POST', |
| headers: headers(apiKey), |
| }); |
| setStatus({ room_id: id, connected: false, status: 'disconnected' }); |
| } |
| catch (e) { |
| setError(e.message); |
| } |
| finally { |
| setLoading(false); |
| } |
| }, [backendUrl, apiKey, roomId]); |
| |
| const toggleVoice = useCallback(async (enabled, rid) => { |
| const id = rid || roomId; |
| if (!id) |
| return; |
| try { |
| const res = await fetch(`${backendUrl}/v1/teams/bridge/voice/${id}`, { |
| method: 'POST', |
| headers: headers(apiKey), |
| body: JSON.stringify({ enabled }), |
| }); |
| if (!res.ok) |
| throw new Error('Failed to toggle voice'); |
| |
| await fetchStatus(id); |
| } |
| catch (e) { |
| setError(e.message); |
| } |
| }, [backendUrl, apiKey, roomId, fetchStatus]); |
| |
| const sendToMeeting = useCallback(async (senderName, content, rid) => { |
| const id = rid || roomId; |
| if (!id) |
| return; |
| try { |
| await fetch(`${backendUrl}/v1/teams/bridge/send/${id}`, { |
| method: 'POST', |
| headers: headers(apiKey), |
| body: JSON.stringify({ sender_name: senderName, content }), |
| }); |
| } |
| catch (e) { |
| setError(e.message); |
| } |
| }, [backendUrl, apiKey, roomId]); |
| |
| const checkMcpAvailable = useCallback(async (mcpBaseUrl = 'http://localhost:9106') => { |
| try { |
| const res = await fetch(`${backendUrl}/v1/teams/bridge/health?mcp_base_url=${encodeURIComponent(mcpBaseUrl)}`, { headers: headers(apiKey) }); |
| if (!res.ok) |
| return false; |
| const data = await res.json(); |
| return !!data.available; |
| } |
| catch { |
| return false; |
| } |
| }, [backendUrl, apiKey]); |
| return { |
| status, |
| loading, |
| error, |
| connect, |
| disconnect, |
| toggleVoice, |
| sendToMeeting, |
| fetchStatus, |
| checkMcpAvailable, |
| }; |
| } |
| |
| |
| |
| |
| |
| |
| |
| export const MCP_SERVERS_CHANGED_EVENT = 'homepilot:mcp-servers-changed'; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function useTeamsMcpAvailable(backendUrl, apiKey, active = false) { |
| const [available, setAvailable] = useState(false); |
| const [checking, setChecking] = useState(true); |
| useEffect(() => { |
| let cancelled = false; |
| const check = async () => { |
| try { |
| const h = {}; |
| if (apiKey) |
| h['x-api-key'] = apiKey; |
| const res = await fetch(`${backendUrl}/v1/teams/bridge/health`, { headers: h }); |
| if (!res.ok) { |
| if (!cancelled) |
| setAvailable(false); |
| return; |
| } |
| const data = await res.json(); |
| if (!cancelled) |
| setAvailable(!!data.available); |
| } |
| catch { |
| if (!cancelled) |
| setAvailable(false); |
| } |
| finally { |
| if (!cancelled) |
| setChecking(false); |
| } |
| }; |
| |
| check(); |
| |
| let interval; |
| if (active) { |
| interval = setInterval(check, 15_000); |
| } |
| |
| const onServersChanged = () => { check(); }; |
| window.addEventListener(MCP_SERVERS_CHANGED_EVENT, onServersChanged); |
| return () => { |
| cancelled = true; |
| if (interval) |
| clearInterval(interval); |
| window.removeEventListener(MCP_SERVERS_CHANGED_EVENT, onServersChanged); |
| }; |
| }, [backendUrl, apiKey, active]); |
| return { available, checking }; |
| } |
|
|