Udostępnij za pośrednictwem


Wprowadzenie do biblioteki interfejsu użytkownika usług Azure Communication Services wywołującej aplikacje Teams Voice Apps

Ten projekt ma na celu poprowadzenie deweloperów w celu zainicjowania wywołania z usług Azure Communication Services wywołujących internetowy zestaw SDK do kolejki wywołań usługi Teams i automatycznego uczestnictwa przy użyciu biblioteki interfejsu użytkownika usługi Azure Communication.

Zgodnie z wymaganiami może być konieczne zaoferowanie klientom łatwego sposobu dotarcia do Ciebie bez żadnej złożonej konfiguracji.

Wywoływanie kolejki połączeń w usłudze Teams i automatycznej obecności to prosta, ale skuteczna koncepcja, która ułatwia natychmiastową interakcję z pomocą techniczną klienta, doradcą finansowym i innymi zespołami dostępnymi dla klientów. Celem tego samouczka jest pomoc w inicjowaniu interakcji z klientami po kliknięciu przycisku w Internecie.

Jeśli chcesz go wypróbować, możesz pobrać kod z usługi GitHub.

W tym samouczku zostaną wykonane następujące czynności:

  • Umożliwia kontrolowanie środowiska audio i wideo klientów w zależności od scenariusza klienta
  • Dowiedz się, jak utworzyć widżet do uruchamiania wywołań w aplikacji internetowej przy użyciu biblioteki interfejsu użytkownika.

Strona główna przykładowej aplikacji wywołującej widżet

Wymagania wstępne

Te kroki są wymagane , aby wykonać czynności opisane w tym samouczku. Skontaktuj się z administratorem usługi Teams, aby uzyskać informacje o dwóch ostatnich elementach, aby upewnić się, że zostały skonfigurowane odpowiednio.

Sprawdzanie węzłów i programu Visual Studio Code

Możesz sprawdzić, czy węzeł został poprawnie zainstalowany za pomocą tego polecenia.

node -v

Dane wyjściowe informują o posiadanej wersji, ale kończy się niepowodzeniem, jeśli węzeł nie został zainstalowany i dodany do elementu PATH. Podobnie jak w przypadku środowiska Node możesz sprawdzić, czy program VS Code został zainstalowany za pomocą tego polecenia.

code --version

Podobnie jak w przypadku środowiska Node to polecenie kończy się niepowodzeniem, jeśli na maszynie wystąpił problem z instalacją programu VS Code.

Wprowadzenie

Ten samouczek zawiera 7 kroków, a na końcu aplikacja będzie mogła wywoływać aplikację głosową usługi Teams. Kroki to:

  1. Konfigurowanie projektu
  2. Pobieranie zależności
  3. Początkowa konfiguracja aplikacji
  4. Tworzenie widżetu
  5. Styl widżetu
  6. Konfigurowanie wartości tożsamości
  7. Uruchamianie aplikacji

1. Konfigurowanie projektu

Użyj tego kroku tylko wtedy, gdy tworzysz nową aplikację.

Aby skonfigurować aplikację react, użyjemy create-react-app narzędzia wiersza polecenia. To narzędzie tworzy łatwą do uruchomienia aplikację TypeScript obsługiwaną przez platformę React.

Aby upewnić się, że na maszynie zainstalowano środowisko Node, uruchom to polecenie w programie PowerShell lub terminalu, aby wyświetlić wersję środowiska Node:

node -v

Jeśli na maszynie nie zainstalowano create-react-app programu , uruchom następujące polecenie, aby zainstalować je jako polecenie globalne:

npm install -g create-react-app

Po zainstalowaniu tego polecenia uruchom następujące następne polecenie, aby utworzyć nową aplikację react w celu skompilowania przykładu w programie :

# 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

Po zakończeniu tych poleceń chcesz otworzyć utworzony projekt w programie VS Code. Projekt można otworzyć za pomocą następującego polecenia.

code .

2. Pobieranie zależności

Następnie należy zaktualizować tablicę zależności w pliku , package.json aby uwzględnić niektóre pakiety z usług Azure Communication Services na potrzeby środowiska widżetu, które będziemy kompilować, aby działać:

"@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",

Aby zainstalować wymagane pakiety, uruchom następujące polecenie Node Menedżer pakietów.

npm install

Po zainstalowaniu tych pakietów wszystko jest gotowe do rozpoczęcia pisania kodu, który kompiluje aplikację. W tym samouczku modyfikujemy pliki w src katalogu.

3. Początkowa konfiguracja aplikacji

Aby rozpocząć, zastąp podaną App.tsx zawartość stroną główną, która będzie:

  • Przechowuj wszystkie informacje o komunikacji platformy Azure, które musimy utworzyć obiekt CallAdapter, aby zapewnić nasze środowisko wywoływania
  • Wyświetl nasz widżet, który jest udostępniany użytkownikowi końcowemu.

Plik App.tsx powinien wyglądać następująco:

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;

W tym fragmencie kodu rejestrujemy dwie nowe ikony <Dismiss20Regular/> i <CallAdd20Regular>. Te nowe ikony są używane wewnątrz składnika widżetu, który tworzymy w następnej sekcji.

4. Tworzenie widżetu

Teraz musimy utworzyć widżet, który może być wyświetlany w trzech różnych trybach:

  • Oczekiwanie: ten stan widżetu to sposób, w jaki składnik będzie znajdować się przed wywołaniem i po nim
  • Konfiguracja: ten stan jest taki, gdy widżet prosi o informacje od użytkownika, takie jak jego nazwa.
  • W wywołaniu: widżet został zastąpiony tutaj biblioteką interfejsu użytkownika Call Composite. Ten tryb widżetu jest wtedy, gdy użytkownik wywołuje aplikację Voice lub rozmawia z agentem.

Umożliwia utworzenie folderu o nazwie src/components. W tym folderze utwórz nowy plik o nazwie CallingWidgetComponent.tsx. Ten plik powinien wyglądać podobnie do następującego fragmentu kodu:

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>
  );
};

W pliku CallAdapterOptionszobaczymy niektóre pliki dźwiękowe, do których odwołuje się plik , te pliki służą do używania funkcji Wywoływanie dźwięków w systemie CallComposite. Jeśli interesuje Cię używanie dźwięków, zobacz ukończony kod, aby pobrać pliki dźwiękowe.

5. Styl widżetu

Musimy napisać kilka stylów, aby upewnić się, że widżet wygląda odpowiednio i może przechowywać nasze wywołania złożone. Te style powinny być już używane w widżecie, jeśli skopiowaliśmy fragment kodu dodany do pliku CallingWidgetComponent.tsx.

Utwórzmy nowy folder o nazwie src/styles w tym folderze, utwórz plik o nazwie CallingWidgetComponent.styles.ts. Plik powinien wyglądać podobnie do następującego fragmentu kodu:

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. Konfigurowanie wartości tożsamości

Zanim uruchomimy aplikację, przejdź do App.tsx pozycji i zastąp wartości symboli zastępczych tożsamościami usług Azure Communication Services i identyfikatorem konta zasobu dla aplikacji Teams Voice. Poniżej przedstawiono wartości wejściowe dla elementów tokeni 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. Uruchamianie aplikacji

Na koniec możemy uruchomić aplikację, aby wykonać nasze wywołania. Uruchom następujące polecenia, aby zainstalować nasze zależności i uruchomić naszą aplikację.

# Install the new dependencies
npm install

# run the React app
npm run start

Po uruchomieniu aplikacji możesz ją http://localhost:3000 zobaczyć w przeglądarce. Powinien zostać wyświetlony następujący ekran powitalny:

Zrzut ekranu przedstawiający zamknięty widżet przykładowy widżet przykładowej aplikacji.

Następnie po akcji przycisku widżetu powinno zostać wyświetlone małe menu:

Zrzut ekranu przedstawiający otwieranie widżetu przykładowego widżetu przykładowej aplikacji.

Po wypełnieniu nazwy kliknij przycisk Rozpocznij wywołanie, a wywołanie powinno się rozpocząć. Widżet powinien wyglądać następująco po uruchomieniu wywołania:

Zrzut ekranu przedstawiający kliknięcie, aby wywołać przykładową stronę główną aplikacji z funkcją wywoływania osadzoną w widżecie.

Następne kroki

Aby uzyskać więcej informacji na temat aplikacji głosowych usługi Teams, zapoznaj się z naszą dokumentacją dotyczącą automatycznych uczestników usługi Teams i kolejek połączeń usługi Teams. Możesz też zapoznać się z naszym samouczkiem dotyczącym tworzenia podobnego środowiska z pakietami języka JavaScript.

Szybki start: dołączanie aplikacji wywołującej do kolejki wywołań usługi Teams

Szybki start: dołączanie aplikacji wywołującej do aplikacji do automatycznego udziału w aplikacji Teams

Szybki start: wprowadzenie do biblioteki interfejsu użytkownika usług Azure Communication Services w języku JavaScript wywołujących kolejkę wywołań usługi Teams i automatyczną obsługę