Comece a usar a biblioteca de interface do usuário dos Serviços de Comunicação do Azure fazendo uma chamada para os Aplicativos de Voz do Teams
Esse projeto tem como objetivo orientar os desenvolvedores a iniciar uma chamada do SDK da Web de Chamadas dos Serviços de Comunicação do Azure para a Fila de Chamadas do Teams e o Atendente Automático usando a Biblioteca de Interface do Usuário de Comunicação do Azure.
De acordo com seus requisitos, você talvez precise oferecer aos seus clientes uma maneira fácil de entrar em contato com você sem nenhuma configuração complexa.
Fazer uma chamada para a Fila de Chamadas e Atendente Automático do Teams é um conceito simples, mas eficaz, que facilita a interação instantânea com o atendimento ao cliente, um consultor financeiro e outras equipes voltadas para o cliente. O objetivo desse tutorial é ajudar você a iniciar interações com seus clientes quando estes clicarem em um botão na web.
Se você quiser experimentar, baixe 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
- Ensina a criar um widget para iniciar chamadas em seu aplicativo Web usando a biblioteca de interface do usuário.
Pré-requisitos
Essas etapas são necessárias para seguir este tutorial. Entre em contato com o administrador do Teams para obter os dois últimos itens para verificar se você está configurado adequadamente.
- Visual Studio Code em uma das plataformas compatíveis.
- Recomenda-se o uso do Node.js, com Suporte de Longo Prazo (LTS) ativo e versões Node 20. Use o comando
node --version
para verificar sua versão. - Um recurso dos Serviços de Comunicação do Azure. Criar um Recurso de Comunicação
- Conclua a configuração do locatário do Teams para interagir com o recurso dos Serviços de Comunicação do Azure
- Como trabalhar com as Filas de Chamadas do Teams e os Serviços de Comunicação do Azure.
- Como trabalhar com os Atendentes Automáticos do Teams e os Serviços de Comunicação do Azure.
Verificação do código do Node e 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 e falha se o Node não tiver sido instalado e adicionado ao seu PATH
. Assim como com o Node, você pode verificar se o VS Code foi instalado com esse comando.
code --version
Como no caso do Node, esse comando falhará se houver algum problema na instalação do VS Code em seu computador.
Introdução
Este tutorial tem 7 etapas e, no final, o aplicativo poderá chamar um aplicativo de voz do Teams. As etapas são:
- Configurar o projeto
- Obtenha suas dependências
- Configuração inicial do aplicativo
- Cria o widget
- Estilizar o widget
- Configurar os valores de identidade
- Executar o aplicativo
1. Configurar o projeto
Use esta etapa somente se você estiver criando um novo aplicativo.
Para configurar o aplicativo react, usamos a ferramenta de linha de comando create-react-app
. Esta ferramenta cria um aplicativo TypeScript fácil de executar desenvolvido com React.
Para garantir que você tenha o Nó instalado em seu computador, execute este comando no PowerShell ou no terminal para ver sua versão do Nó:
node -v
Se você não tiver o create-react-app
instalado em seu computador, execute o seguinte comando para instalá-lo como um comando global:
npm install -g create-react-app
Depois que esse comando for instalado, execute este próximo comando para criar um novo aplicativo react para criar 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ê deverá abrir o projeto criado no VS Code. Você pode abrir o projeto com o seguinte comando.
code .
2. Obter suas dependências
Em seguida, você precisa atualizar a matriz de dependência em package.json
para incluir alguns pacotes dos Serviços de Comunicação do Azure para que a experiência de widget que vamos criar funcione:
"@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 do Node.
npm install
Depois de instalar esses pacotes, você estará pronto para começar a escrever o código que constrói o aplicativo. Neste tutorial, estamos modificando os arquivos no diretório src
.
3. Configuração inicial do aplicativo
Para começar, substituímos o conteúdo fornecido App.tsx
por uma página principal que:
- Armazenar todas as informações de Comunicação do Azure necessárias para criar um CallAdapter para alimentar nossa experiência de chamada
- Exiba o nosso widget que é exposto ao usuário final.
O arquivo App.tsx
deve ter esta aparência:
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 snippet, registramos dois novos ícones <Dismiss20Regular/>
e <CallAdd20Regular>
. Estes novos ícones são usados dentro do componente do widget que estamos criando na próxima seção.
4. Criar o widget
Agora, precisamos criar um widget que possa ser exibido em três modos diferentes:
- Aguardando: esse estado do widget é a forma como o componente irá aparecer antes e depois de uma chamada ser efetuada
- Configuração: esse estado aparece quando o widget solicita informações do usuário, como o nome.
- Em uma chamada: aqui o widget é substituído pelos Elementos de Chamadas da biblioteca da interface do usuário. Este modo de widget é quando o usuário está chamando o aplicativo do Serviço de Voz ou conversando com um agente.
Vamos criar uma pasta chamada src/components
. Nesta pasta, faça um novo arquivo chamado CallingWidgetComponent.tsx
. Esse arquivo deve se parecer com o seguinte snippet:
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>
);
};
No CallAdapterOptions
, vemos alguns arquivos de som referenciados, esses arquivos são para usar o recurso Sons de Chamada no CallComposite
. Se você estiver interessado em usar os sons, veja o código completo para baixar os arquivos de som.
5. Criar o estilo do widget
Precisamos escrever alguns estilos para garantir que o widget tenha a aparência apropriada e possa reter nossos elementos de chamadas. Esses estilos já devem ser usados no widget se copiarmos o snippet que adicionamos ao arquivo CallingWidgetComponent.tsx
.
Vamos criar uma nova pasta chamada src/styles
nesta pasta, crie um arquivo chamado CallingWidgetComponent.styles.ts
. O arquivo deverá se parecer com o seguinte snippet:
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á para App.tsx
e substitua os valores de espaço reservado com suas Identidades dos Serviços de Comunicação do Azure e o identificador da conta do recurso para seu aplicativo Teams Voice. Aqui estão os valores de entrada para 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. Executar o aplicativo
Para terminar, podemos executar o aplicativo para fazer nossas chamadas! Execute os comandos a seguir para instalar nossas dependências e executar nosso aplicativo.
# Install the newe dependencies
npm install
# run the React app
npm run start
Depois que o aplicativo estiver em execução, você poderá vê-lo em http://localhost:3000
no seu navegador. Você deverá ver a tela inicial a seguir:
Em seguida, ao acionar do botão do widget, você deverá ver um pequeno menu:
Depois de preencher seu nome, clique em iniciar a chamada e a chamada deverá começar. O widget deve ter a seguinte aparência após iniciar uma chamada:
Próximas etapas
Para obter mais informações sobre aplicativos de voz do Teams, confira nossa documentação sobre atendedores automáticos do Teams e filas de chamadas do Teams. Ou consulte também nosso tutorial sobre como criar uma experiência semelhante com pacotes de JavaScript.
Início Rápido: ingressar seu aplicativo de chamadas a uma fila de chamadas do Teams
Guia de início rápido: ingresse seu aplicativo de chamadas a um Atendente Automático do Teams