Delen via


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.

Startpagina van voorbeeld-app Widget aanroepen

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.

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:

  1. Het project instellen
  2. Uw afhankelijkheden ophalen
  3. Eerste app-installatie
  4. De widget maken
  5. Stijl van de widget
  6. Identiteitswaarden instellen
  7. 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.tsxhebben 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 tokenen 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:

Schermopname van het sluiten van de widgetvoorbeeld-app-widget voor de startpagina.

Wanneer u vervolgens de widgetknop actie uitvoert, ziet u een klein menu:

Schermopname van het openen van de widgetvoorbeeld-app- startpaginawidget.

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:

Schermopname van klik om de startpagina van de voorbeeld-app aan te roepen met de belervaring die is ingesloten in de widget.

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

Quickstart: Aan de slag met de Ui-bibliotheek van Azure Communication Services JavaScript bundelt oproepen naar teams-oproepwachtrij en Auto Attendant