Introducción a la llamada de la biblioteca de interfaz de usuario de Azure Communication Services a las aplicaciones de voz de Teams
Este proyecto tiene como objetivo guiar a los desarrolladores a iniciar una llamada desde el SDK web de llamadas de Azure Communication Services a la cola de llamadas y el operador automático de Teams mediante la biblioteca de UI de Azure Communication.
En función de los requisitos, es posible que tenga que ofrecer a los clientes una forma sencilla de ponerse en contacto con usted sin ninguna configuración compleja.
La llamada a la cola de llamadas y el operador automático de Teams es un concepto sencillo pero eficaz que facilita la interacción instantánea con el servicio de atención al cliente, el asesor financiero y otros equipos de atención al cliente. El objetivo de este tutorial es ayudarle a iniciar interacciones con los clientes cuando hagan clic en un botón de la web.
Si desea probarlo, puede descargar el código de GitHub.
Después de este tutorial:
- Le permite controlar la experiencia de audio y vídeo de los clientes en función del escenario del cliente
- Enseñarle a crear un widget para iniciar llamadas en la aplicación web mediante la biblioteca de interfaz de usuario.
Requisitos previos
Estos pasos son necesarios para seguir este tutorial. Póngase en contacto con el administrador de Teams con relación a los dos últimos elementos para asegurarse de estar configurado correctamente.
- Visual Studio Code en una de las plataformas admitidas.
- Node.js, se recomienda Active LTS (compatibilidad a largo plazo) y versiones Nodo 20. Use el comando
node --version
para comprobar la versión. - Un recurso de Azure Communication Services. Creación de un recurso de Communications
- Complete la configuración del inquilino de Teams para la interoperabilidad con el recurso de Azure Communication Services
- Uso de colas de llamadas de Teams y Azure Communication Services.
- Uso de operadores automáticos de Teams y Azure Communication Services.
Comprobación de Node y Visual Studio Code
Puede comprobar que Node se instaló correctamente con este comando.
node -v
La salida indica la versión que tiene, se produce un error si Node no se instaló y se agregó a la PATH
. Al igual que con Node, puede comprobar si VS Code se instaló con este comando.
code --version
Al igual que con Node, este comando produce un error si se produjo un problema al instalar VS Code en el equipo.
Introducción
Este tutorial tiene 7 pasos y, al final, la aplicación podrá llamar a una aplicación de voz de Teams. Los pasos son:
- Configurar el proyecto
- Obtener las dependencias
- Configuración inicial de la aplicación
- Creación del widget
- Estilo del widget
- Configuración de valores de identidad
- Ejecución de la aplicación
1. Configuración del proyecto
Utilice este paso solo si está creando una nueva aplicación.
Para configurar la aplicación React, usamos la herramienta de línea de comandos create-react-app
. Esta herramienta crea una aplicación TypeScript fácil de ejecutar con tecnología de React.
Para asegurarse de que tiene Node instalado en la máquina, ejecute este comando en PowerShell o el terminal para ver la versión del nodo:
node -v
Si no tiene create-react-app
instalado en la máquina, ejecute el siguiente comando para instalarlo como un comando global:
npm install -g create-react-app
Una vez instalado ese comando, ejecute el siguiente comando para crear una nueva aplicación react para compilar el ejemplo en:
# Create an Azure Communication Services App powered by React.
npx create-react-app ui-library-calling-widget-app --template typescript
# Change to the directory of the newly created App.
cd ui-library-calling-widget-app
Una vez completados estos comandos, quiere abrir el proyecto creado en VS Code. Puede abrir el proyecto con el siguiente comando.
code .
2. Obtención de las dependencias
Después, debe actualizar la matriz de dependencias en package.json
para incluir algunos paquetes de Azure Communication Services a fin de que funcione la experiencia de widget que se va a crear:
"@azure/communication-calling": "^1.23.1",
"@azure/communication-chat": "^1.4.0",
"@azure/communication-react": "^1.15.0",
"@azure/communication-calling-effects": "1.0.1",
"@azure/communication-common": "2.3.0",
"@fluentui/react-icons": "~2.0.203",
"@fluentui/react": "~8.98.3",
Para instalar los paquetes necesarios, ejecute el siguiente comando del Administrador de paquetes de Node.
npm install
Después de instalar estos paquetes, todos están configurados para empezar a escribir el código que compila la aplicación. En este tutorial, vamos a modificar los archivos en el directorio de src
.
3. Configuración inicial de la aplicación
Para empezar, reemplazamos el contenido proporcionado App.tsx
por una página principal que:
- Almacene toda la información de Azure Communication que necesitamos para crear un CallAdapter para impulsar nuestra experiencia de llamada
- Muestra del widget que se expone al usuario final.
El archivo App.tsx
debería tener este aspecto:
src/App.tsx
import "./App.css";
import {
CommunicationIdentifier,
MicrosoftTeamsAppIdentifier,
} from "@azure/communication-common";
import {
Spinner,
Stack,
initializeIcons,
registerIcons,
Text,
} from "@fluentui/react";
import { CallAdd20Regular, Dismiss20Regular } from "@fluentui/react-icons";
import logo from "./logo.svg";
import { CallingWidgetComponent } from "./components/CallingWidgetComponent";
registerIcons({
icons: { dismiss: <Dismiss20Regular />, callAdd: <CallAdd20Regular /> },
});
initializeIcons();
function App() {
/**
* Token for local user.
*/
const token = "<Enter your ACS Token here>";
/**
* User identifier for local user.
*/
const userId: CommunicationIdentifier = {
communicationUserId: "Enter your ACS Id here",
};
/**
* Enter your Teams voice app identifier from the Teams admin center here
*/
const teamsAppIdentifier: MicrosoftTeamsAppIdentifier = {
teamsAppId: "<Enter your Teams Voice app id here>",
cloud: "public",
};
const widgetParams = {
userId,
token,
teamsAppIdentifier,
};
if (!token || !userId || !teamsAppIdentifier) {
return (
<Stack verticalAlign="center" style={{ height: "100%", width: "100%" }}>
<Spinner
label={"Getting user credentials from server"}
ariaLive="assertive"
labelPosition="top"
/>
</Stack>
);
}
return (
<Stack
style={{ height: "100%", width: "100%", padding: "3rem" }}
tokens={{ childrenGap: "1.5rem" }}
>
<Stack tokens={{ childrenGap: "1rem" }} style={{ margin: "auto" }}>
<Stack
style={{ padding: "3rem" }}
horizontal
tokens={{ childrenGap: "2rem" }}
>
<Text style={{ marginTop: "auto" }} variant="xLarge">
Welcome to a Calling Widget sample
</Text>
<img
style={{ width: "7rem", height: "auto" }}
src={logo}
alt="logo"
/>
</Stack>
<Text>
Welcome to a Calling Widget sample for the Azure Communication
Services UI Library. Sample has the ability to connect you through
Teams voice apps to a agent to help you.
</Text>
<Text>
As a user all you need to do is click the widget below, enter your
display name for the call - this will act as your caller id, and
action the <b>start call</b> button.
</Text>
</Stack>
<Stack
horizontal
tokens={{ childrenGap: "1.5rem" }}
style={{ overflow: "hidden", margin: "auto" }}
>
<CallingWidgetComponent
widgetAdapterArgs={widgetParams}
onRenderLogo={() => {
return (
<img
style={{ height: "4rem", width: "4rem", margin: "auto" }}
src={logo}
alt="logo"
/>
);
}}
/>
</Stack>
</Stack>
);
}
export default App;
En este fragmento de código se registran dos nuevos iconos, <Dismiss20Regular/>
y <CallAdd20Regular>
. Estos iconos se usan dentro del componente de widget que se creará en la sección siguiente.
4. Creación del widget
Ahora es necesario crear un widget que pueda mostrarse en tres modos diferentes:
- En espera: este estado del widget es el que tendrá el componente antes y después de realizar una llamada
- Configuración: este estado es cuando el widget solicita información del usuario, como su nombre.
- En una llamada: aquí el widget se reemplaza por la biblioteca de interfaz de usuario Call Composite. Este modo de widget se da cuando el usuario llama a la aplicación de voz o habla con un agente.
Ahora se creará una carpeta llamada src/components
. En esta carpeta, cree un nuevo archivo denominado CallingWidgetComponent.tsx
. El archivo debería parecerse al siguiente fragmento de código:
CallingWidgetComponent.tsx
import {
IconButton,
PrimaryButton,
Stack,
TextField,
useTheme,
Checkbox,
Icon,
Spinner,
} from "@fluentui/react";
import React, { useEffect, useRef, useState } from "react";
import {
callingWidgetSetupContainerStyles,
checkboxStyles,
startCallButtonStyles,
callingWidgetContainerStyles,
callIconStyles,
logoContainerStyles,
collapseButtonStyles,
} from "../styles/CallingWidgetComponent.styles";
import {
AzureCommunicationTokenCredential,
CommunicationUserIdentifier,
MicrosoftTeamsAppIdentifier,
} from "@azure/communication-common";
import {
CallAdapter,
CallAdapterState,
CallComposite,
CommonCallAdapterOptions,
StartCallIdentifier,
createAzureCommunicationCallAdapter,
} from "@azure/communication-react";
// lets add to our react imports as well
import { useMemo } from "react";
import { callingWidgetInCallContainerStyles } from "../styles/CallingWidgetComponent.styles";
/**
* Properties needed for our widget to start a call.
*/
export type WidgetAdapterArgs = {
token: string;
userId: CommunicationUserIdentifier;
teamsAppIdentifier: MicrosoftTeamsAppIdentifier;
};
export interface CallingWidgetComponentProps {
/**
* arguments for creating an AzureCommunicationCallAdapter for your Calling experience
*/
widgetAdapterArgs: WidgetAdapterArgs;
/**
* Custom render function for displaying logo.
* @returns
*/
onRenderLogo?: () => JSX.Element;
}
/**
* Widget for Calling Widget
* @param props
*/
export const CallingWidgetComponent = (
props: CallingWidgetComponentProps
): JSX.Element => {
const { onRenderLogo, widgetAdapterArgs } = props;
const [widgetState, setWidgetState] = useState<"new" | "setup" | "inCall">(
"new"
);
const [displayName, setDisplayName] = useState<string>();
const [consentToData, setConsentToData] = useState<boolean>(false);
const [useLocalVideo, setUseLocalVideo] = useState<boolean>(false);
const [adapter, setAdapter] = useState<CallAdapter>();
const callIdRef = useRef<string>();
const theme = useTheme();
// add this before the React template
const credential = useMemo(() => {
try {
return new AzureCommunicationTokenCredential(widgetAdapterArgs.token);
} catch {
console.error("Failed to construct token credential");
return undefined;
}
}, [widgetAdapterArgs.token]);
const adapterOptions: CommonCallAdapterOptions = useMemo(
() => ({
callingSounds: {
callEnded: { url: "/sounds/callEnded.mp3" },
callRinging: { url: "/sounds/callRinging.mp3" },
callBusy: { url: "/sounds/callBusy.mp3" },
},
}),
[]
);
const callAdapterArgs = useMemo(() => {
return {
userId: widgetAdapterArgs.userId,
credential: credential,
targetCallees: [
widgetAdapterArgs.teamsAppIdentifier,
] as StartCallIdentifier[],
displayName: displayName,
options: adapterOptions,
};
}, [
widgetAdapterArgs.userId,
widgetAdapterArgs.teamsAppIdentifier.teamsAppId,
credential,
displayName,
]);
useEffect(() => {
if (adapter) {
adapter.on("callEnded", () => {
/**
* We only want to reset the widget state if the call that ended is the same as the current call.
*/
if (
adapter.getState().acceptedTransferCallState &&
adapter.getState().acceptedTransferCallState?.id !== callIdRef.current
) {
return;
}
setDisplayName(undefined);
setWidgetState("new");
setConsentToData(false);
setAdapter(undefined);
adapter.dispose();
});
adapter.on("transferAccepted", (e) => {
console.log("transferAccepted", e);
});
adapter.onStateChange((state: CallAdapterState) => {
if (state?.call?.id && callIdRef.current !== state?.call?.id) {
callIdRef.current = state?.call?.id;
console.log(`Call Id: ${callIdRef.current}`);
}
});
}
}, [adapter]);
/** widget template for when widget is open, put any fields here for user information desired */
if (widgetState === "setup") {
return (
<Stack
styles={callingWidgetSetupContainerStyles(theme)}
tokens={{ childrenGap: "1rem" }}
>
<IconButton
styles={collapseButtonStyles}
iconProps={{ iconName: "Dismiss" }}
onClick={() => {
setDisplayName(undefined);
setConsentToData(false);
setUseLocalVideo(false);
setWidgetState("new");
}}
/>
<Stack tokens={{ childrenGap: "1rem" }} styles={logoContainerStyles}>
<Stack style={{ transform: "scale(1.8)" }}>
{onRenderLogo && onRenderLogo()}
</Stack>
</Stack>
<TextField
label={"Name"}
required={true}
placeholder={"Enter your name"}
onChange={(_, newValue) => {
setDisplayName(newValue);
}}
/>
<Checkbox
styles={checkboxStyles(theme)}
label={
"Use video - Checking this box will enable camera controls and screen sharing"
}
onChange={(_, checked?: boolean | undefined) => {
setUseLocalVideo(!!checked);
setUseLocalVideo(true);
}}
></Checkbox>
<Checkbox
required={true}
styles={checkboxStyles(theme)}
disabled={displayName === undefined}
label={
"By checking this box, you are consenting that we will collect data from the call for customer support reasons"
}
onChange={async (_, checked?: boolean | undefined) => {
setConsentToData(!!checked);
if (callAdapterArgs && callAdapterArgs.credential) {
setAdapter(
await createAzureCommunicationCallAdapter({
displayName: displayName ?? "",
userId: callAdapterArgs.userId,
credential: callAdapterArgs.credential,
targetCallees: callAdapterArgs.targetCallees,
options: callAdapterArgs.options,
})
);
}
}}
></Checkbox>
<PrimaryButton
styles={startCallButtonStyles(theme)}
onClick={() => {
if (displayName && consentToData && adapter) {
setWidgetState("inCall");
adapter?.startCall(callAdapterArgs.targetCallees, {
audioOptions: { muted: false },
});
}
}}
>
{!consentToData && `Enter your name`}
{consentToData && !adapter && (
<Spinner ariaLive="assertive" labelPosition="top" />
)}
{consentToData && adapter && `StartCall`}
</PrimaryButton>
</Stack>
);
}
if (widgetState === "inCall" && adapter) {
return (
<Stack styles={callingWidgetInCallContainerStyles(theme)}>
<CallComposite
adapter={adapter}
options={{
callControls: {
cameraButton: useLocalVideo,
screenShareButton: useLocalVideo,
moreButton: false,
peopleButton: false,
displayType: "compact",
},
localVideoTile: !useLocalVideo ? false : { position: "floating" },
}}
/>
</Stack>
);
}
return (
<Stack
horizontalAlign="center"
verticalAlign="center"
styles={callingWidgetContainerStyles(theme)}
onClick={() => {
setWidgetState("setup");
}}
>
<Stack
horizontalAlign="center"
verticalAlign="center"
style={{
height: "4rem",
width: "4rem",
borderRadius: "50%",
background: theme.palette.themePrimary,
}}
>
<Icon iconName="callAdd" styles={callIconStyles(theme)} />
</Stack>
</Stack>
);
};
En el CallAdapterOptions
, vemos algunos archivos de sonido a los que se hace referencia, estos archivos son para usar la característica Sonidos de llamada en la CallComposite
. Si está interesado en usar los sonidos, vea el código completado para descargar los archivos de sonido.
5. Estilo del widget
Será necesario escribir algunos estilos para asegurarse de que el widget tiene el aspecto adecuado y puede contener la composición de la llamada. Estos estilos ya deben usarse en el widget si se copia el fragmento de código que hemos agregado al archivo CallingWidgetComponent.tsx
.
Ahora, cree una carpeta denominada src/styles
y, en ella, cree un archivo denominado CallingWidgetComponent.styles.ts
. El archivo debería parecerse al siguiente fragmento de código:
import {
IButtonStyles,
ICheckboxStyles,
IIconStyles,
IStackStyles,
Theme,
} from "@fluentui/react";
export const checkboxStyles = (theme: Theme): ICheckboxStyles => {
return {
label: {
color: theme.palette.neutralPrimary,
},
};
};
export const callingWidgetContainerStyles = (theme: Theme): IStackStyles => {
return {
root: {
width: "5rem",
height: "5rem",
padding: "0.5rem",
boxShadow: theme.effects.elevation16,
borderRadius: "50%",
bottom: "1rem",
right: "1rem",
position: "absolute",
overflow: "hidden",
cursor: "pointer",
":hover": {
boxShadow: theme.effects.elevation64,
},
},
};
};
export const callingWidgetSetupContainerStyles = (
theme: Theme
): IStackStyles => {
return {
root: {
width: "18rem",
minHeight: "20rem",
maxHeight: "25rem",
padding: "0.5rem",
boxShadow: theme.effects.elevation16,
borderRadius: theme.effects.roundedCorner6,
bottom: 0,
right: "1rem",
position: "absolute",
overflow: "hidden",
cursor: "pointer",
background: theme.palette.white,
},
};
};
export const callIconStyles = (theme: Theme): IIconStyles => {
return {
root: {
paddingTop: "0.2rem",
color: theme.palette.white,
transform: "scale(1.6)",
},
};
};
export const startCallButtonStyles = (theme: Theme): IButtonStyles => {
return {
root: {
background: theme.palette.themePrimary,
borderRadius: theme.effects.roundedCorner6,
borderColor: theme.palette.themePrimary,
},
textContainer: {
color: theme.palette.white,
},
};
};
export const logoContainerStyles: IStackStyles = {
root: {
margin: "auto",
padding: "0.2rem",
height: "5rem",
width: "10rem",
zIndex: 0,
},
};
export const collapseButtonStyles: IButtonStyles = {
root: {
position: "absolute",
top: "0.2rem",
right: "0.2rem",
zIndex: 1,
},
};
export const callingWidgetInCallContainerStyles = (
theme: Theme
): IStackStyles => {
return {
root: {
width: "35rem",
height: "25rem",
padding: "0.5rem",
boxShadow: theme.effects.elevation16,
borderRadius: theme.effects.roundedCorner6,
bottom: 0,
right: "1rem",
position: "absolute",
overflow: "hidden",
cursor: "pointer",
background: theme.semanticColors.bodyBackground,
},
};
};
6. Configuración de valores de identidad
Antes de ejecutar la aplicación, vaya a App.tsx
y reemplace los valores de marcador de posición allí por sus Identidades de Azure Communication Services y el Identificador de cuenta de recursos para la aplicación de Voz de Teams. Estos son los valores de entrada para token
, userId
y teamsAppIdentifier
.
./src/App.tsx
/**
* Token for local user.
*/
const token = "<Enter your ACS Token here>";
/**
* User identifier for local user.
*/
const userId: CommunicationIdentifier = {
communicationUserId: "Enter your ACS Id here",
};
/**
* Enter your Teams voice app identifier from the Teams admin center here
*/
const teamsAppIdentifier: MicrosoftTeamsAppIdentifier = {
teamsAppId: "<Enter your Teams Voice app id here>",
cloud: "public",
};
7. Ejecución de la aplicación
Por último, se puede ejecutar la aplicación para realizar las llamadas. Ejecute los comandos siguientes para instalar las dependencias y ejecutar la aplicación.
# Install the new dependencies
npm install
# run the React app
npm run start
Una vez ejecutada la aplicación, podrá verla en http://localhost:3000
en su navegador. Debería ver la siguiente pantalla de presentación:
Después, al accionar el botón del widget, debería ver un pequeño menú:
Después de rellenar el nombre, haga clic en Iniciar llamada y la llamada debería comenzar. El widget debe tener un aspecto similar al siguiente después de iniciar una llamada:
Pasos siguientes
Para obtener más información sobre las aplicaciones de voz de Teams, consulte nuestra documentación sobre operadores automáticos de Teams y colas de llamadas de Teams. También vea nuestro tutorial sobre cómo crear una experiencia similar con paquetes de JavaScript.
Inicio rápido: Incorporación de una aplicación de llamadas a una cola de llamadas de Teams
Inicio rápido: Unión de la aplicación de llamadas a un operador automático de Teams