openreplay/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx
Delirium a71381da40
getting rid of redux for good (#2556)
* start moving ui to redux tlk

* remove unused reducer

* changes for gdpr and site types

* ui: migrating duck/roles to mobx

* ui: drop unreferenced types

* ui: drop unreferenced types

* ui: move player slice reducer to mobx family

* ui: move assignments to issueReportingStore.ts

* remove issues store

* some fixes after issues store

* remove errors reducer, drop old components

* finish removing errors reducer

* start moving integrations state to mobx

* change(ui): funnel duck cleanup

* change(ui): custom fields

* change(ui): customMetrics cleanup

* change(ui): customMetrics cleanup

* change(ui): duck/filters minor cleanup

* change(ui): duck/filters cleanup

* change(ui): duck/customMetrics cleanup and upgrades

* fix integrations service, fix babel config to >.25 + not ie

* refactoring integrations reducers etc WIP

* finish removing integrations state

* some fixes for integrated check

* start of projects refactoring

* move api and "few" files to new project store

* new batch for site -> projects

* fix setid context

* move all critical components, drop site duck

* remove all duck/site refs, remove old components

* fixup for SessionTags.tsx, remove duck/sources (?)

* move session store

* init sessionstore outside of context

* fix userfilter

* replace simple actions for session store

* sessions sotre

* Rtm temp (#2597)

* change(ui): duck/search wip

* change(ui): duck/search wip

* change(ui): duck/search wip

* change(ui): duck/searchLive wip

* change(ui): duck/searchLive wip

* change(ui): duck/searchLive wip

* change(ui): duck/searchLive wip

* change(ui): search states

* change(ui): search states

* change(ui): search states

* change(ui): fix savedSearch store

* change(ui): fix savedSearch store

* some fixes for session connector

* change(ui): fix savedSearch store

* change(ui): fix searchLive

* change(ui): fix searchLive

* fixes for session replay

* change(ui): bookmark fetch

* last components for sessions

* add fetchautoplaylist

* finish session reducer, remove deleted reducers

* change(ui): fix the search fetch

* change(ui): fix the search fetch

* fix integrations call ctx

* ensure ctx for sessionstore

* fix(ui): checking for latest sessions path

* start removing user reducer

* removing user reducer pt2...

* finish user store

* remove rand log

* fix crashes

* tinkering workflow file for tracker test

* making sure prefetched sessions work properly

* fix conflict

* fix router redirects during loading

---------

Co-authored-by: Shekar Siri <sshekarsiri@gmail.com>
2024-10-03 11:38:36 +02:00

291 lines
8.1 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Button, Tooltip } from 'UI';
import cn from 'classnames';
import ChatWindow from '../../ChatWindow';
import { CallingState, ConnectionStatus, RemoteControlStatus, RequestLocalStream } from 'Player';
import type { LocalStream } from 'Player';
import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
import { toast } from 'react-toastify';
import { confirm } from 'UI';
import stl from './AassistActions.module.css';
import ScreenRecorder from 'App/components/Session_/ScreenRecorder/ScreenRecorder';
import { audioContextManager } from 'App/utils/screenRecorder';
import { useStore } from "App/mstore";
function onReject() {
toast.info(`Call was rejected.`);
}
function onControlReject() {
toast.info('Remote control request was rejected by user');
}
function onControlBusy() {
toast.info('Remote control busy');
}
function onError(e: any) {
console.log(e);
toast.error(typeof e === 'string' ? e : e.message);
}
interface Props {
userId: string;
isCallActive: boolean;
agentIds: string[];
userDisplayName: string;
}
const AssistActionsPing = {
control: {
start: 's_control_started',
end: 's_control_ended'
},
call: {
start: 's_call_started',
end: 's_call_ended'
},
} as const
function AssistActions({
userId,
isCallActive,
agentIds,
}: Props) {
// @ts-ignore ???
const { player, store } = React.useContext<ILivePlayerContext>(PlayerContext);
const { sessionStore, userStore } = useStore();
const permissions = userStore.account.permissions || [];
const hasPermission = permissions.includes('ASSIST_CALL') || permissions.includes('SERVICE_ASSIST_CALL');
const isEnterprise = userStore.isEnterprise;
const agentId = userStore.account.id;
const userDisplayName = sessionStore.current.userDisplayName;
const {
assistManager: {
call: callPeer,
setCallArgs,
requestReleaseRemoteControl,
toggleAnnotation,
setRemoteControlCallbacks,
},
toggleUserName,
} = player;
const {
calling,
annotating,
peerConnectionStatus,
remoteControl: remoteControlStatus,
livePlay,
} = store.get();
const [isPrestart, setPrestart] = useState(false);
const [incomeStream, setIncomeStream] = useState<MediaStream[] | null>([]);
const [localStream, setLocalStream] = useState<LocalStream | null>(null);
const [callObject, setCallObject] = useState<{ end: () => void } | null>(null);
const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting;
const callRequesting = calling === CallingState.Connecting;
const cannotCall =
peerConnectionStatus !== ConnectionStatus.Connected || (isEnterprise && !hasPermission);
const remoteRequesting = remoteControlStatus === RemoteControlStatus.Requesting;
const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled;
useEffect(() => {
if (!onCall && isCallActive && agentIds) {
setPrestart(true);
// call(agentIds); do not autocall on prestart, can change later
}
}, [agentIds, isCallActive]);
useEffect(() => {
if (!livePlay) {
if (annotating) {
toggleAnnotation(false);
}
if (remoteActive) {
requestReleaseRemoteControl();
}
}
}, [livePlay]);
useEffect(() => {
if (remoteActive) {
toggleUserName(userDisplayName);
} else {
// higher than waiting for messages
if (peerConnectionStatus > 1) {
toggleUserName();
}
}
}, [remoteActive]);
useEffect(() => {
return callObject?.end();
}, []);
useEffect(() => {
if (peerConnectionStatus == ConnectionStatus.Disconnected) {
toast.info(`Live session was closed.`);
}
}, [peerConnectionStatus]);
const addIncomeStream = (stream: MediaStream) => {
setIncomeStream((oldState) => {
if (oldState === null) return [stream];
if (!oldState.find((existingStream) => existingStream.id === stream.id)) {
audioContextManager.mergeAudioStreams(stream);
return [...oldState, stream];
}
return oldState;
});
};
function call(additionalAgentIds?: string[]) {
RequestLocalStream()
.then((lStream) => {
setLocalStream(lStream);
audioContextManager.mergeAudioStreams(lStream.stream);
setCallArgs(
lStream,
addIncomeStream,
() => {
player.assistManager.ping(AssistActionsPing.call.end, agentId)
lStream.stop.bind(lStream);
},
onReject,
onError
);
setCallObject(callPeer());
if (additionalAgentIds) {
callPeer(additionalAgentIds);
}
})
.catch(onError);
}
const confirmCall = async () => {
if (callRequesting || remoteRequesting) return;
if (
await confirm({
header: 'Start Call',
confirmButton: 'Call',
confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`,
})
) {
call(agentIds);
}
};
const requestControl = () => {
const onStart = () => {
player.assistManager.ping(AssistActionsPing.control.start, agentId)
}
const onEnd = () => {
player.assistManager.ping(AssistActionsPing.control.end, agentId)
}
setRemoteControlCallbacks({
onReject: onControlReject,
onStart: onStart,
onEnd: onEnd,
onBusy: onControlBusy,
});
requestReleaseRemoteControl();
};
React.useEffect(() => {
if (onCall) {
player.assistManager.ping(AssistActionsPing.call.start, agentId)
}
}, [onCall])
return (
<div className="flex items-center">
{(onCall || remoteActive) && (
<>
<div
className={cn('cursor-pointer p-2 flex items-center', {
[stl.disabled]: cannotCall || !livePlay,
})}
onClick={() => toggleAnnotation(!annotating)}
role="button"
>
<Button
icon={annotating ? 'pencil-stop' : 'pencil'}
variant={annotating ? 'text-red' : 'text-primary'}
style={{ height: '28px' }}
>
Annotate
</Button>
</div>
<div className={stl.divider} />
</>
)}
{/* @ts-ignore wtf? */}
<ScreenRecorder />
<div className={stl.divider} />
{/* @ts-ignore */}
<Tooltip title="Go live to initiate remote control" disabled={livePlay}>
<div
className={cn('cursor-pointer p-2 flex items-center', {
[stl.disabled]: cannotCall || !livePlay || callRequesting || remoteRequesting,
})}
onClick={requestControl}
role="button"
>
<Button
icon={remoteActive ? 'window-x' : 'remote-control'}
variant={remoteActive ? 'text-red' : 'text-primary'}
style={{ height: '28px' }}
>
Remote Control
</Button>
</div>
</Tooltip>
<div className={stl.divider} />
<Tooltip
title={
cannotCall
? `You don't have the permissions to perform this action.`
: `Call ${userId ? userId : 'User'}`
}
disabled={onCall}
>
<div
className={cn('cursor-pointer p-2 flex items-center', {
[stl.disabled]: cannotCall || callRequesting || remoteRequesting,
})}
onClick={onCall ? callObject?.end : confirmCall}
role="button"
>
<Button
icon="headset"
variant={onCall ? 'text-red' : isPrestart ? 'green' : 'primary'}
style={{ height: '28px' }}
>
{onCall ? 'End' : isPrestart ? 'Join Call' : 'Call'}
</Button>
</div>
</Tooltip>
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
{onCall && callObject && (
<ChatWindow
endCall={callObject.end}
userId={userId}
incomeStream={incomeStream}
localStream={localStream}
isPrestart={isPrestart}
/>
)}
</div>
</div>
);
}
export default observer(AssistActions);