Поделиться через


Начало работы с Службы коммуникации Azure библиотекой пользовательского интерфейса, вызываемой в Приложения Голосовой связи Teams

Этот проект направлен на запуск вызова из Службы коммуникации Azure вызова веб-пакета SDK для Teams в очередь вызовов Teams и автосекретаря с помощью библиотеки пользовательского интерфейса коммуникации Azure.

В соответствии с вашими требованиями вам может потребоваться предложить клиентам простой способ связаться с вами без сложной настройки.

Вызов очереди звонков Teams и автосекретарь — это простая и эффективная концепция, которая упрощает мгновенное взаимодействие с поддержкой клиентов, финансовым консультантом и другими командами, которые сталкиваются с клиентами. Цель этого руководства — помочь вам в инициировании взаимодействия с клиентами при нажатии кнопки в Интернете.

Если вы хотите попробовать его, вы можете скачать код с GitHub.

В этом руководстве будет приведено следующее:

  • Позволяет управлять аудио- и видеосообращением клиентов в зависимости от вашего сценария клиента.
  • Узнайте, как создать мини-приложение для запуска вызовов в веб-приложении с помощью библиотеки пользовательского интерфейса.

Домашняя страница примера приложения для мини-приложения для вызова мини-приложения

Необходимые компоненты

Эти действия необходимы для выполнения этого руководства. Обратитесь к администратору Teams за последними двумя элементами, чтобы убедиться, что вы правильно настроены.

Проверка узла и Visual Studio Code

Вы можете проверить правильность установки узла с помощью этой команды.

node -v

Выходные данные сообщают о том, что у вас есть версия, она завершается ошибкой, если узел не установлен и добавлен в ваш PATH. Как и в случае с узлом, можно проверить, установлен ли VS Code с помощью этой команды.

code --version

Как и в случае с node, эта команда завершается ошибкой, если на компьютере возникла проблема с установкой VS Code.

Начало работы

В этом руководстве содержится 7 шагов, и в конце приложения можно будет вызвать голосовое приложение Teams. Вот что нужно сделать:

  1. Настройка проекта
  2. Получение зависимостей
  3. Начальная настройка приложения
  4. Создание мини-приложения
  5. Стиль мини-приложения
  6. Настройка значений удостоверений
  7. Запуск приложения

1. Настройка проекта

Используйте этот шаг, только если вы создаете новое приложение.

Чтобы настроить приложение React, мы используем средство командной create-react-app строки. Это средство позволяет легко запускать приложение TypeScript на базе React.

Чтобы убедиться, что на компьютере установлен узел, выполните следующую команду в PowerShell или терминале, чтобы просмотреть версию узла:

node -v

Если на компьютере не create-react-app установлено, выполните следующую команду, чтобы установить ее в качестве глобальной команды:

npm install -g create-react-app

После установки этой команды выполните следующую команду, чтобы создать новое приложение React, чтобы создать пример в:

# 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

После завершения этих команд необходимо открыть созданный проект в VS Code. Проект можно открыть с помощью следующей команды.

code .

2. Получение зависимостей

Затем необходимо обновить массив зависимостей в пакетеpackage.json, чтобы включить некоторые пакеты из Службы коммуникации Azure для работы с мини-приложением:

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

Чтобы установить необходимые пакеты, выполните следующую команду Node диспетчер пакетов.

npm install

После установки этих пакетов все настроено, чтобы начать написание кода, который создает приложение. В этом руководстве мы изменим файлы в каталоге src .

3. Начальная настройка приложения

Чтобы приступить к работе, замените предоставленное App.tsx содержимое главной страницей, которая:

  • Сохраните все сведения о коммуникации Azure, необходимые для создания CallAdapter для работы с вызовами.
  • Отображение нашего мини-приложения, которое предоставляется конечному пользователю.

Файл App.tsx должен выглядеть следующим образом:

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;

В этом фрагменте кода мы регистрируем два новых значка <Dismiss20Regular/> и <CallAdd20Regular>. Эти новые значки используются внутри компонента мини-приложения, который мы создадим в следующем разделе.

4. Создание мини-приложения

Теперь нам нужно сделать мини-приложение, которое может отображаться в трех разных режимах:

  • Ожидание. Это состояние мини-приложения заключается в том, как компонент будет находиться до и после вызова
  • Настройка: это состояние, когда мини-приложение запрашивает информацию от пользователя, как его имя.
  • В вызове: мини-приложение заменено здесь на call call Composite библиотеки пользовательского интерфейса. Этот режим мини-приложения — это когда пользователь вызывает голосовое приложение или разговаривает с агентом.

Позволяет создать папку с именем src/components. В этой папке создайте новый файл с именем CallingWidgetComponent.tsx. Этот файл должен выглядеть следующим фрагментом кода:

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

В этой CallAdapterOptionsстатье мы видим некоторые звуковые файлы, на которые ссылается ссылка, эти файлы предназначены для использования функции "Вызывающие звуки" в этой CallCompositeстатье. Если вы хотите использовать звуки, просмотрите полный код для скачивания звуковых файлов.

5. Стиль мини-приложения

Необходимо написать некоторые стили, чтобы убедиться, что мини-приложение выглядит соответствующим образом и может содержать составный вызов. Эти стили уже должны использоваться в мини-приложении при копировании фрагмента кода, добавленного в файл CallingWidgetComponent.tsx.

Позволяет создать новую папку src/styles , вызываемую в этой папке, создать файл с именем CallingWidgetComponent.styles.ts. Файл должен выглядеть следующим фрагментом кода:

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. Настройка значений удостоверений

Перед запуском приложения перейдите App.tsx к значениям заполнителей и замените их идентификаторами Службы коммуникации Azure удостоверениями и идентификатором учетной записи ресурса для приложения Голосовой связи Teams. Ниже приведены входные значения для tokenuserId и 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. Запуск приложения

Наконец, мы можем запустить приложение, чтобы сделать наши звонки! Выполните следующие команды, чтобы установить наши зависимости и запустить наше приложение.

# Install the newe dependencies
npm install

# run the React app
npm run start

После запуска приложения его http://localhost:3000 можно увидеть в браузере. Появится следующий экран-заставка:

Снимок экрана: закрытие мини-приложения примера домашней страницы приложения.

Затем при действии кнопки мини-приложения вы увидите небольшое меню:

Снимок экрана: открытие мини-приложения примера домашней страницы приложения.

После заполнения имени нажмите кнопку "Начать звонок" и вызов должен начинаться. Мини-приложение должно выглядеть так после запуска вызова:

Снимок экрана: выборка домашней страницы приложения с интерфейсом вызова, внедренным в мини-приложение.

Следующие шаги

Дополнительные сведения о голосовых приложениях Teams см. в нашей документации по автосекретарям Teams и очередям вызовов Teams. Кроме того, ознакомьтесь с нашим руководством по созданию аналогичного интерфейса с пакетами JavaScript.

Краткое руководство. Присоединение вызывающего приложения к очереди вызовов Teams

Краткое руководство. Присоединение вызывающего приложения к автосекретарю Teams

Краткое руководство. Начало работы с пакетом JavaScript библиотеки пользовательского интерфейса Службы коммуникации Azure, вызываемой очередью вызовов Teams и автосекретарем