Introdução às chamadas da biblioteca de IU dos Serviços de Comunicação do Azure para as Aplicações de Voz do Teams
Este projeto tem como objetivo orientar os desenvolvedores a iniciar uma chamada do SDK da Web de Chamada dos Serviços de Comunicação do Azure para a Fila de Chamadas do Teams e o Atendedor Automático usando a Biblioteca da Interface do Usuário de Comunicação do Azure.
De acordo com suas necessidades, você pode precisar oferecer aos seus clientes uma maneira fácil de entrar em contato com você sem qualquer configuração complexa.
Ligar para o Teams Call Queue e Auto Attendant é um conceito simples, mas eficaz, que facilita a interação instantânea com o suporte ao cliente, consultor financeiro e outras equipes voltadas para o cliente. O objetivo deste tutorial é ajudá-lo a iniciar interações com seus clientes quando eles clicam em um botão na web.
Se você deseja experimentá-lo, você pode baixar o código do GitHub.
Seguir este tutorial irá:
- Permitir que você controle a experiência de áudio e vídeo de seus clientes, dependendo do cenário do cliente
- Ensine como criar um widget para iniciar chamadas em seu aplicativo Web usando a biblioteca da interface do usuário.
Pré-requisitos
Estas etapas são necessárias para seguir este tutorial. Entre em contato com o administrador do Teams para obter os dois últimos itens para se certificar de que você está configurado adequadamente.
- Visual Studio Code numa das plataformas suportadas.
- Node.js, LTS Ativo (Suporte de Longo Prazo) e versões Node 20 é recomendado. Use o
node --version
comando para verificar sua versão. - Um recurso dos Serviços de Comunicação do Azure. Criar um recurso de comunicações
- Conclua a configuração do locatário do Teams para interoperabilidade com seu recurso dos Serviços de Comunicação do Azure
- Trabalhando com Teams Call Queues e Serviços de Comunicação do Azure.
- Trabalhando com Atendedores Automáticos do Teams e Serviços de Comunicação do Azure.
Verificando o nó e o código do Visual Studio
Você pode verificar se o Node foi instalado corretamente com este comando.
node -v
A saída informa a versão que você tem, ela falha se o Node não foi instalado e adicionado ao seu PATH
. Assim como com o Node você pode verificar se o VS Code foi instalado com este comando.
code --version
Tal como acontece com o Node, este comando falha se houver um problema ao instalar o VS Code na sua máquina.
Introdução
Este tutorial tem 7 passos e no final o aplicativo será capaz de chamar um aplicativo de voz do Teams. Os passos são:
- Configurar o projeto
- Obtenha as suas dependências
- Configuração inicial do aplicativo
- Criar o widget
- Estilizar o widget
- Configurar valores de identidade
- Executar o aplicativo
1. Configurar o projeto
Use esta etapa somente se estiver criando um novo aplicativo.
Para configurar o aplicativo react, usamos a ferramenta de linha de create-react-app
comando. Esta ferramenta cria um aplicativo TypeScript fácil de executar alimentado por React.
Para certificar-se de que você tem o Node instalado em sua máquina, execute este comando no PowerShell ou no terminal para ver sua versão do Node:
node -v
Se você não tiver create-react-app
instalado em sua máquina, execute o seguinte comando para instalá-lo como um comando global:
npm install -g create-react-app
Depois que o comando for instalado, execute este próximo comando para criar um novo aplicativo react para compilar o exemplo em:
# 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
Após a conclusão desses comandos, você deseja abrir o projeto criado no VS Code. Você pode abrir o projeto com o seguinte comando.
code .
2. Obtenha as suas dependências
Em seguida, você precisa atualizar a matriz de dependência no package.json
para incluir alguns pacotes dos Serviços de Comunicação do Azure para a experiência de widget que vamos criar para funcionar:
"@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 os pacotes necessários, execute o seguinte comando do Gerenciador de Pacotes de Nó.
npm install
Depois de instalar esses pacotes, você está pronto para começar a escrever o código que cria o aplicativo. Neste tutorial, estamos modificando os arquivos no src
diretório.
3. Configuração inicial do aplicativo
Para começar, substituímos o conteúdo fornecido App.tsx
por uma página principal que:
- Armazene todas as informações de Comunicação do Azure de que precisamos para criar um CallAdapter para potencializar nossa experiência de Chamada
- Exiba nosso widget que está exposto ao usuário final.
O seu App.tsx
ficheiro deve ter o seguinte aspeto:
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;
Neste trecho, registramos dois novos ícones <Dismiss20Regular/>
e <CallAdd20Regular>
. Esses novos ícones são usados dentro do componente de widget que estamos criando na próxima seção.
4. Crie o widget
Agora precisamos fazer um widget que pode mostrar em três modos diferentes:
- Esperando: Este estado do widget é como o componente estará antes e depois de uma chamada ser feita
- Configuração: Este estado é quando o widget pede informações do usuário, como seu nome.
- Em uma chamada: o widget é substituído aqui pela biblioteca da interface do usuário Call Composite. Este modo de widget é quando o usuário está chamando o aplicativo de voz ou falando com um agente.
Vamos criar uma pasta chamada src/components
. Nesta pasta, crie um novo arquivo chamado CallingWidgetComponent.tsx
. Esse arquivo deve se parecer com o seguinte trecho:
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>
);
};
CallAdapterOptions
No , vemos alguns arquivos de som referenciados, esses arquivos devem usar o recurso Chamando sons no CallComposite
. Se você estiver interessado em usar os sons, consulte o código concluído para baixar os arquivos de som.
5. Estilize o widget
Precisamos escrever alguns estilos para garantir que o widget pareça apropriado e possa conter nosso composto de chamadas. Esses estilos já devem ser usados no widget se copiar o trecho que adicionamos ao arquivo CallingWidgetComponent.tsx
.
Vamos fazer uma nova pasta chamada src/styles
nesta pasta, criar um arquivo chamado CallingWidgetComponent.styles.ts
. O arquivo deve se parecer com o seguinte trecho:
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. Configurar valores de identidade
Antes de executarmos o aplicativo, vá e App.tsx
substitua os valores de espaço reservado por suas Identidades dos Serviços de Comunicação do Azure e o identificador de conta de recurso para seu aplicativo Teams Voice. Aqui estão os valores de entrada para o token
, userId
e 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. Execute o aplicativo
Finalmente podemos executar o aplicativo para fazer nossas chamadas! Execute os seguintes comandos para instalar nossas dependências e executar nosso aplicativo.
# Install the new dependencies
npm install
# run the React app
npm run start
Quando o aplicativo estiver em execução, você poderá vê-lo em http://localhost:3000
seu navegador. Você verá a seguinte tela inicial:
Em seguida, quando você acionar o botão do widget, você verá um pequeno menu:
Depois de preencher o seu nome, clique em iniciar chamada e a chamada deve começar. O widget deve ter essa aparência depois de iniciar uma chamada:
Próximos passos
Para obter mais informações sobre os aplicativos de voz do Teams, confira nossa documentação sobre atendedores automáticos do Teams e filas de chamadas do Teams. Ou também veja nosso tutorial sobre como criar uma experiência semelhante com pacotes JavaScript.
Guia de início rápido: associe seu aplicativo de chamadas a uma fila de chamadas do Teams
Guia de início rápido: associe seu aplicativo de chamadas a um Atendedor Automático do Teams