Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
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 --versioncomando 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 criar uma aplicação React, usamos vite. É uma ferramenta de construção que visa fornecer uma experiência de desenvolvimento mais rápida e enxuta para projetos web modernos.
Você pode ler mais sobre vite aqui Criar um aplicativo React a partir do zero e Introdução ao Vite
Observe que a biblioteca de interface do usuário do Serviço de Comunicação do Azure só oferece suporte a até o React 18. Verifique se você está na versão suportada depois de criar o aplicativo react verificando package.json
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
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>
);
};
CallAdapterOptionsNo , 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 dev
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