diff --git a/api/chalicelib/core/metadata.py b/api/chalicelib/core/metadata.py index fafe52c51..e761ab4f4 100644 --- a/api/chalicelib/core/metadata.py +++ b/api/chalicelib/core/metadata.py @@ -98,17 +98,23 @@ def __edit(project_id, col_index, colname, new_name): if col_index not in list(old_metas.keys()): return {"errors": ["custom field not found"]} - with pg_client.PostgresClient() as cur: - if old_metas[col_index]["key"] != new_name: + if old_metas[col_index]["key"] != new_name: + with pg_client.PostgresClient() as cur: query = cur.mogrify(f"""UPDATE public.projects SET {colname} = %(value)s WHERE project_id = %(project_id)s AND deleted_at ISNULL - RETURNING {colname};""", + RETURNING {colname}, + (SELECT {colname} FROM projects WHERE project_id = %(project_id)s) AS old_{colname};""", {"project_id": project_id, "value": new_name}) cur.execute(query=query) - new_name = cur.fetchone()[colname] + row = cur.fetchone() + new_name = row[colname] + old_name = row['old_' + colname] old_metas[col_index]["key"] = new_name + projects.rename_metadata_condition(project_id=project_id, + old_metadata_key=old_name, + new_metadata_key=new_name) return {"data": old_metas[col_index]} @@ -121,8 +127,8 @@ def edit(tenant_id, project_id, index: int, new_name: str): def delete(tenant_id, project_id, index: int): index = int(index) old_segments = get(project_id) - old_segments = [k["index"] for k in old_segments] - if index not in old_segments: + old_indexes = [k["index"] for k in old_segments] + if index not in old_indexes: return {"errors": ["custom field not found"]} with pg_client.PostgresClient() as cur: @@ -132,7 +138,8 @@ def delete(tenant_id, project_id, index: int): WHERE project_id = %(project_id)s AND deleted_at ISNULL;""", {"project_id": project_id}) cur.execute(query=query) - + projects.delete_metadata_condition(project_id=project_id, + metadata_key=old_segments[old_indexes.index(index)]["key"]) return {"data": get(project_id)} diff --git a/api/chalicelib/core/projects.py b/api/chalicelib/core/projects.py index fac4c0dbc..5d33028f8 100644 --- a/api/chalicelib/core/projects.py +++ b/api/chalicelib/core/projects.py @@ -413,7 +413,6 @@ def update_project_conditions(project_id, conditions): create_project_conditions(project_id, to_be_created) if to_be_updated: - logger.debug(to_be_updated) update_project_condition(project_id, to_be_updated) return get_conditions(project_id) @@ -428,3 +427,45 @@ def get_projects_ids(tenant_id): cur.execute(query=query) rows = cur.fetchall() return [r["project_id"] for r in rows] + + +def delete_metadata_condition(project_id, metadata_key): + sql = """\ + UPDATE public.projects_conditions + SET filters=(SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb) + FROM jsonb_array_elements(filters) AS elem + WHERE NOT (elem ->> 'type' = 'metadata' + AND elem ->> 'source' = %(metadata_key)s)) + WHERE project_id = %(project_id)s + AND jsonb_typeof(filters) = 'array' + AND EXISTS (SELECT 1 + FROM jsonb_array_elements(filters) AS elem + WHERE elem ->> 'type' = 'metadata' + AND elem ->> 'source' = %(metadata_key)s);""" + + with pg_client.PostgresClient() as cur: + query = cur.mogrify(sql, {"project_id": project_id, "metadata_key": metadata_key}) + cur.execute(query) + + +def rename_metadata_condition(project_id, old_metadata_key, new_metadata_key): + sql = """\ + UPDATE public.projects_conditions + SET filters = (SELECT jsonb_agg(CASE + WHEN elem ->> 'type' = 'metadata' AND elem ->> 'source' = %(old_metadata_key)s + THEN elem || ('{"source": "'||%(new_metadata_key)s||'"}')::jsonb + ELSE elem END) + FROM jsonb_array_elements(filters) AS elem) + WHERE project_id = %(project_id)s + AND jsonb_typeof(filters) = 'array' + AND EXISTS (SELECT 1 + FROM jsonb_array_elements(filters) AS elem + WHERE elem ->> 'type' = 'metadata' + AND elem ->> 'source' = %(old_metadata_key)s);""" + + with pg_client.PostgresClient() as cur: + query = cur.mogrify(sql, {"project_id": project_id, "old_metadata_key": old_metadata_key, + "new_metadata_key": new_metadata_key}) + cur.execute(query) + +# TODO: make project conditions use metadata-column-name instead of metadata-key diff --git a/assist/utils/assistHelper.js b/assist/utils/assistHelper.js index c27c80963..ce90aa2a8 100644 --- a/assist/utils/assistHelper.js +++ b/assist/utils/assistHelper.js @@ -10,7 +10,8 @@ const EVENTS_DEFINITION = { UPDATE_EVENT: "UPDATE_SESSION", // tab become active/inactive, page title change, changed session object (rare case), call start/end CONNECT_ERROR: "connect_error", CONNECT_FAILED: "connect_failed", - ERROR: "error" + ERROR: "error", + WEBRTC_AGENT_CALL: "WEBRTC_AGENT_CALL", }, //The following list of events will be only emitted by the server server: { diff --git a/assist/utils/socketHandlers.js b/assist/utils/socketHandlers.js index a9a3e8a14..503969c08 100644 --- a/assist/utils/socketHandlers.js +++ b/assist/utils/socketHandlers.js @@ -127,6 +127,9 @@ async function onConnect(socket) { // Handle update event socket.on(EVENTS_DEFINITION.listen.UPDATE_EVENT, (...args) => onUpdateEvent(socket, ...args)); + // Handle webrtc events + socket.on(EVENTS_DEFINITION.listen.WEBRTC_AGENT_CALL, (...args) => onWebrtcAgentHandler(socket, ...args)); + // Handle errors socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err)); socket.on(EVENTS_DEFINITION.listen.CONNECT_ERROR, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_ERROR, err)); @@ -186,6 +189,16 @@ async function onUpdateEvent(socket, ...args) { } } +async function onWebrtcAgentHandler(socket, ...args) { + if (socket.handshake.query.identity === IDENTITIES.agent) { + const agentIdToConnect = args[0]?.data?.toAgentId; + logger.debug(`${socket.id} sent webrtc event to agent:${agentIdToConnect}`); + if (agentIdToConnect && socket.handshake.sessionData.AGENTS_CONNECTED.includes(agentIdToConnect)) { + socket.to(agentIdToConnect).emit(EVENTS_DEFINITION.listen.WEBRTC_AGENT_CALL, args[0]); + } + } +} + async function onAny(socket, eventName, ...args) { if (Object.values(EVENTS_DEFINITION.listen).indexOf(eventName) >= 0) { logger.debug(`received event:${eventName}, should be handled by another listener, stopping onAny.`); diff --git a/ee/api/chalicelib/core/projects.py b/ee/api/chalicelib/core/projects.py index 499a2bc8a..aeac3fa97 100644 --- a/ee/api/chalicelib/core/projects.py +++ b/ee/api/chalicelib/core/projects.py @@ -427,7 +427,6 @@ def update_project_conditions(project_id, conditions): create_project_conditions(project_id, to_be_created) if to_be_updated: - print(to_be_updated) update_project_condition(project_id, to_be_updated) return get_conditions(project_id) @@ -486,3 +485,45 @@ def is_authorized_batch(project_ids, tenant_id): cur.execute(query=query) rows = cur.fetchall() return [r["project_id"] for r in rows] + + +def delete_metadata_condition(project_id, metadata_key): + sql = """\ + UPDATE public.projects_conditions + SET filters=(SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb) + FROM jsonb_array_elements(filters) AS elem + WHERE NOT (elem ->> 'type' = 'metadata' + AND elem ->> 'source' = %(metadata_key)s)) + WHERE project_id = %(project_id)s + AND jsonb_typeof(filters) = 'array' + AND EXISTS (SELECT 1 + FROM jsonb_array_elements(filters) AS elem + WHERE elem ->> 'type' = 'metadata' + AND elem ->> 'source' = %(metadata_key)s);""" + + with pg_client.PostgresClient() as cur: + query = cur.mogrify(sql, {"project_id": project_id, "metadata_key": metadata_key}) + cur.execute(query) + + +def rename_metadata_condition(project_id, old_metadata_key, new_metadata_key): + sql = """\ + UPDATE public.projects_conditions + SET filters = (SELECT jsonb_agg(CASE + WHEN elem ->> 'type' = 'metadata' AND elem ->> 'source' = %(old_metadata_key)s + THEN elem || ('{"source": "'||%(new_metadata_key)s||'"}')::jsonb + ELSE elem END) + FROM jsonb_array_elements(filters) AS elem) + WHERE project_id = %(project_id)s + AND jsonb_typeof(filters) = 'array' + AND EXISTS (SELECT 1 + FROM jsonb_array_elements(filters) AS elem + WHERE elem ->> 'type' = 'metadata' + AND elem ->> 'source' = %(old_metadata_key)s);""" + + with pg_client.PostgresClient() as cur: + query = cur.mogrify(sql, {"project_id": project_id, "old_metadata_key": old_metadata_key, + "new_metadata_key": new_metadata_key}) + cur.execute(query) + +# TODO: make project conditions use metadata-column-name instead of metadata-key diff --git a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx index 01a397f96..5597a6c59 100644 --- a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx +++ b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx @@ -9,7 +9,7 @@ import stl from './chatWindow.module.css'; import VideoContainer from '../components/VideoContainer'; export interface Props { - incomeStream: MediaStream[] | null; + incomeStream: { stream: MediaStream, isAgent: boolean }[] | null; localStream: LocalStream | null; userId: string; isPrestart?: boolean; @@ -54,8 +54,8 @@ function ChatWindow({ > {incomeStream ? ( incomeStream.map((stream) => ( - - + + )) ) : ( @@ -66,6 +66,7 @@ function ChatWindow({ stream={localStream ? localStream.stream : null} muted height={anyRemoteEnabled ? 50 : 'unset'} + local /> diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index 7ca030472..d58ca274c 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -82,7 +82,7 @@ function AssistActions({ } = store.get(); const [isPrestart, setPrestart] = useState(false); - const [incomeStream, setIncomeStream] = useState([]); + const [incomeStream, setIncomeStream] = useState<{ stream: MediaStream; isAgent: boolean }[] | null>([]); const [localStream, setLocalStream] = useState(null); const [callObject, setCallObject] = useState<{ end:() => void } | null>(null); @@ -130,18 +130,25 @@ function AssistActions({ } }, [peerConnectionStatus]); - const addIncomeStream = (stream: MediaStream) => { + const addIncomeStream = (stream: MediaStream, isAgent: boolean) => { setIncomeStream((oldState) => { - if (oldState === null) return [stream]; - if (!oldState.find((existingStream) => existingStream.id === stream.id)) { + if (oldState === null) return [{ stream, isAgent }]; + if (!oldState.find((existingStream) => existingStream.stream.id === stream.id)) { audioContextManager.mergeAudioStreams(stream); - return [...oldState, stream]; + return [...oldState, { stream, isAgent }]; } return oldState; }); }; - function call(additionalAgentIds?: string[]) { + const removeIncomeStream = (stream: MediaStream) => { + setIncomeStream((prevState) => { + if (!prevState) return []; + return prevState.filter((existingStream) => existingStream.stream.id !== stream.id); + }); + }; + + function call() { RequestLocalStream() .then((lStream) => { setLocalStream(lStream); @@ -150,16 +157,17 @@ function AssistActions({ lStream, addIncomeStream, () => { - player.assistManager.ping(AssistActionsPing.call.end, agentId); - lStream.stop.bind(lStream); + player.assistManager.ping(AssistActionsPing.call.end, agentId) + lStream.stop.apply(lStream); + removeIncomeStream(lStream.stream); }, onReject, onError, ); setCallObject(callPeer()); - if (additionalAgentIds) { - callPeer(additionalAgentIds); - } + // if (additionalAgentIds) { + // callPeer(additionalAgentIds); + // } }) .catch(onError); } diff --git a/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx b/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx index 6d2030af8..a0b7ca8d9 100644 --- a/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx +++ b/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx @@ -5,10 +5,17 @@ interface Props { muted?: boolean; height?: number | string; setRemoteEnabled?: (isEnabled: boolean) => void; + local?: boolean; + isAgent?: boolean; } function VideoContainer({ - stream, muted = false, height = 280, setRemoteEnabled, + stream, + muted = false, + height = 280, + setRemoteEnabled, + local, + isAgent, }: Props) { const ref = useRef(null); const [isEnabled, setEnabled] = React.useState(false); @@ -17,7 +24,7 @@ function VideoContainer({ if (ref.current) { ref.current.srcObject = stream; } - }, [ref.current, stream, stream.getVideoTracks()[0]?.getSettings().width]); + }, [ref.current, stream, stream?.getVideoTracks()[0]?.getSettings().width]); useEffect(() => { if (!stream) { @@ -49,9 +56,19 @@ function VideoContainer({ width: isEnabled ? undefined : '0px!important', height: isEnabled ? undefined : '0px!important', border: '1px solid grey', + transform: local ? 'scaleX(-1)' : undefined, }} > -