Aan de slag met de UI-bibliotheek van Azure Communication Services die belt naar Teams Voice Apps
Dit project is bedoeld om ontwikkelaars te begeleiden bij het initiëren van een oproep vanuit de Web-SDK voor oproepen van Azure Communication Services naar Teams-oproepwachtrij en Auto Attendant met behulp van de Azure Communication UI Library.
Op basis van uw vereisten moet u uw klanten mogelijk een eenvoudige manier bieden om u te bereiken zonder complexe instellingen.
Bellen naar Teams-oproepwachtrij en Auto Attendant is een eenvoudig maar effectief concept dat directe interactie mogelijk maakt met klantondersteuning, financiële adviseur en andere klantgerichte teams. Het doel van deze zelfstudie is om u te helpen bij het initiëren van interacties met uw klanten wanneer ze op een knop op het web klikken.
Als u het wilt uitproberen, kunt u de code downloaden van GitHub.
Volg deze zelfstudie:
- Hiermee kunt u de audio- en video-ervaring van uw klanten beheren, afhankelijk van uw klantscenario
- Leer hoe u een widget maakt voor het starten van aanroepen in uw web-app met behulp van de UI-bibliotheek.
Vereisten
Deze stappen zijn nodig om deze zelfstudie te volgen. Neem contact op met uw Teams-beheerder voor de laatste twee items om ervoor te zorgen dat u op de juiste manier bent ingesteld.
- Visual Studio Code op een van de ondersteunde platforms.
- Node.js, Active LTS (Long Term Support) en versie Node 20 wordt aanbevolen. Gebruik de opdracht
node --version
om uw versie te controleren. - Een Azure Communication Services-resource. Een communicatieresource maken
- Voltooi de installatie van de Teams-tenant voor interoping met uw Azure Communication Services-resource
- Werken met Teams-oproepwachtrijen en Azure Communication Services.
- Werken met Teams Auto Attendants en Azure Communication Services.
Controleren op Node en Visual Studio Code
U kunt controleren of Node correct is geïnstalleerd met deze opdracht.
node -v
De uitvoer geeft aan welke versie u hebt. Het mislukt als Node niet is geïnstalleerd en toegevoegd aan uw PATH
. Net als bij Node kunt u controleren of VS Code is geïnstalleerd met deze opdracht.
code --version
Net als bij Node mislukt deze opdracht als er een probleem is opgetreden bij het installeren van VS Code op uw computer.
Aan de slag
Deze zelfstudie heeft 7 stappen en aan het einde kan de app een Teams-spraaktoepassing aanroepen. Dit zijn de stappen:
- Het project instellen
- Uw afhankelijkheden ophalen
- Eerste app-installatie
- De widget maken
- Stijl van de widget
- Identiteitswaarden instellen
- De app uitvoeren
1. Het project instellen
Gebruik deze stap alleen als u een nieuwe toepassing maakt.
Om de React App in te stellen, gebruiken we het create-react-app
opdrachtregelprogramma. Met dit hulpprogramma kunt u eenvoudig een TypeScript-toepassing uitvoeren die wordt aangedreven door React.
Als u ervoor wilt zorgen dat Node op uw computer is geïnstalleerd, voert u deze opdracht uit in PowerShell of de terminal om uw Node-versie te zien:
node -v
Als u niet op uw computer hebt create-react-app
geïnstalleerd, voert u de volgende opdracht uit om deze te installeren als een globale opdracht:
npm install -g create-react-app
Nadat deze opdracht is geïnstalleerd, voert u deze volgende opdracht uit om een nieuwe react-toepassing te maken om het voorbeeld te bouwen in:
# 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
Nadat deze opdrachten zijn voltooid, wilt u het gemaakte project openen in VS Code. U kunt het project openen met de volgende opdracht.
code .
2. Haal uw afhankelijkheden op
Vervolgens moet u de afhankelijkheidsmatrix bijwerken om package.json
enkele pakketten van Azure Communication Services op te nemen voor de widgetervaring die we gaan bouwen om te werken:
"@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",
Voer de volgende knooppunt-Pakketbeheer opdracht uit om de benodigde pakketten te installeren.
npm install
Nadat u deze pakketten hebt geïnstalleerd, bent u klaar om te beginnen met het schrijven van de code waarmee de toepassing wordt gebouwd. In deze zelfstudie wijzigen we de bestanden in de src
map.
3. Eerste installatie van de app
Om aan de slag te gaan, vervangen we de opgegeven App.tsx
inhoud door een hoofdpagina die:
- Sla alle Azure Communication-informatie op die we nodig hebben om een CallAdapter te maken om onze belervaring mogelijk te maken
- Geef onze widget weer die beschikbaar is voor de eindgebruiker.
Het App.tsx
bestand moet er als volgt uitzien:
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;
In dit fragment registreren we twee nieuwe pictogrammen <Dismiss20Regular/>
en <CallAdd20Regular>
. Deze nieuwe pictogrammen worden gebruikt in het widgetonderdeel dat we in de volgende sectie maken.
4. Maak de widget
Nu moeten we een widget maken die in drie verschillende modi kan worden weergegeven:
- Wachten: deze widgetstatus is hoe het onderdeel binnenkomt voor en nadat een aanroep is gedaan
- Setup: Deze status is wanneer de widget vraagt om informatie van de gebruiker, zoals hun naam.
- In een oproep: De widget wordt hier vervangen door de UI-bibliotheek Call Composite. Deze widgetmodus is wanneer de gebruiker de Voice-app aanroept of met een agent praat.
Hiermee kunt u een map maken met de naam src/components
. Maak in deze map een nieuw bestand met de naam CallingWidgetComponent.tsx
. Dit bestand moet eruitzien als het volgende fragment:
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>
);
};
In de CallAdapterOptions
, we zien enkele geluidsbestanden waarnaar wordt verwezen, deze bestanden zijn om de functie Belgeluiden in de CallComposite
. Als u geïnteresseerd bent in het gebruik van de geluiden, raadpleegt u de voltooide code om de geluidsbestanden te downloaden.
5. Stijl van de widget
We moeten enkele stijlen schrijven om ervoor te zorgen dat de widget er geschikt uitziet en onze oproep samengesteld kan bevatten. Deze stijlen moeten al worden gebruikt in de widget als u het fragment kopieert dat we aan het bestand CallingWidgetComponent.tsx
hebben toegevoegd.
Hiermee kunt u een nieuwe map maken die in deze map wordt aangeroepen src/styles
, een bestand maken met de naam CallingWidgetComponent.styles.ts
. Het bestand moet eruitzien als het volgende fragment:
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. Identiteitswaarden instellen
Voordat we de app uitvoeren, gaat u naar App.tsx
en vervangt u de tijdelijke aanduidingen daar door uw Azure Communication Services-identiteiten en de resourceaccount-id voor uw Teams Voice-toepassing. Hier volgen invoerwaarden voor de token
en userId
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. De app uitvoeren
Ten slotte kunnen we de toepassing uitvoeren om onze aanroepen uit te voeren. Voer de volgende opdrachten uit om onze afhankelijkheden te installeren en onze app uit te voeren.
# Install the new dependencies
npm install
# run the React app
npm run start
Zodra de app wordt uitgevoerd, kunt u deze http://localhost:3000
zien in uw browser. U ziet nu het volgende welkomstscherm:
Wanneer u vervolgens de widgetknop actie uitvoert, ziet u een klein menu:
Nadat u uw naam hebt ingevuld, klikt u op Gesprek starten en moet de oproep beginnen. De widget moet er als volgt uitzien na het starten van een aanroep:
Volgende stappen
Raadpleeg onze documentatie over Teams Auto Attendants en Teams-oproepwachtrijen voor meer informatie over Teams-spraaktoepassingen. Of zie onze zelfstudie over het bouwen van een vergelijkbare ervaring met JavaScript-bundels.
Quickstart: Uw bel-app toevoegen aan een Teams-oproepwachtrij
Quickstart: Uw bel-app toevoegen aan een Teams Auto Attendant