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


Руководство. Включение встроенной поддержки изображений в приложении Чата

Пакет SDK для чата предназначен для эффективной работы с Microsoft Teams. В частности, пакет SDK чата предоставляет решение для получения встроенных образов и отправки встроенных образов пользователям из Microsoft Teams.

В этом руководстве описано, как включить встроенную поддержку образов с помощью пакета SDK для чата Службы коммуникации Azure для JavaScript.

Встроенные изображения — это изображения, скопированные и вставляемые непосредственно в поле отправки клиента Teams. Для изображений, отправленных с помощью меню отправки из этого меню устройства или перетаскивания, таких как изображения, перетаскиваемые непосредственно в поле отправки в Teams, необходимо обратиться к этому руководству в качестве части функции общего доступа к файлам. (См. раздел "Обработка вложений изображений".)

Чтобы скопировать изображение, у пользователей Teams есть два варианта:

  • Используйте контекстное меню операционной системы, чтобы скопировать файл изображения, а затем вставить его в поле отправки клиента Teams.
  • Используйте сочетания клавиш.

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

Примечание.

Возможность отправки встроенных изображений в настоящее время доступна в общедоступной предварительной версии. Он доступен только для JavaScript. Для получения встроенных изображений он в настоящее время доступен в целом. Он доступен как для JavaScript, так и для C# в чате взаимодействия Teams.

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

Пример кода

Найдите завершенный код этого руководства на сайте GitHub.

Обработка полученных встроенных изображений в новом событии сообщения

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

В кратком руководстве вы создали обработчик событий для chatMessageReceived события, который активируется при получении нового сообщения от пользователя Teams. Вы также добавляете входящее содержимое messageContainer сообщения непосредственно после получения события из следующего chatMessageReceived chatClient:

chatClient.on("chatMessageReceived", (e) => {
   console.log("Notification chatMessageReceived!");

   // Check whether the notification is intended for the current thread
   if (threadIdInput.value != e.threadId) {
      return;
   }

   if (e.sender.communicationUserId != userId) {
      renderReceivedMessage(e.message);
   }
   else {
      renderSentMessage(e.message);
   }
});
   
async function renderReceivedMessage(message) {
   messages += '<div class="container lighter">' + message + '</div>';
   messagesContainer.innerHTML = messages;
}

Из входящего события типа ChatMessageReceivedEventсвойство с именем attachments содержит сведения о встроенном изображении. Все, что вам нужно отрисовки встроенных изображений в пользовательском интерфейсе:

export interface ChatMessageReceivedEvent extends BaseChatMessageEvent {
  /**
   * Content of the message.
   */
  message: string;

  /**
   * Metadata of the message.
   */
  metadata: Record<string, string>;

  /**
   * Chat message attachment.
   */
  attachments?: ChatAttachment[];
}

export interface ChatAttachment {
  /** Id of the attachment */
  id: string;
  /** The type of attachment. */
  attachmentType: ChatAttachmentType;
  /** The name of the attachment content. */
  name?: string;
  /** The URL where the attachment can be downloaded */
  url?: string;
  /** The URL where the preview of attachment can be downloaded */
  previewUrl?: string;
}

export type ChatAttachmentType = "image" | "unknown";

Теперь вернитесь к предыдущему коду, чтобы добавить дополнительную логику, например следующие фрагменты кода:

chatClient.on("chatMessageReceived", (e) => {
  console.log("Notification chatMessageReceived!");
  // Check whether the notification is intended for the current thread
  if (threadIdInput.value != e.threadId) {
     return;
  }
   
  const isMyMessage = e.sender.communicationUserId === userId;
  renderReceivedMessage(e, isMyMessage);
});

function renderReceivedMessage(e, isMyMessage) {
  const messageContent = e.message;

  const card = document.createElement('div');
  card.className = isMyMessage ? "container darker" : "container lighter";
  card.innerHTML = messageContent;
  
  messagesContainer.appendChild(card);
  
  // Filter out inline images from attachments
  const imageAttachments = e.attachments.filter((e) =>
    e.attachmentType.toLowerCase() === 'image');
  
  // Fetch and render preview images
  fetchPreviewImages(imageAttachments);
  
  // Set up onclick event handler to fetch full-scale image
  setImgHandler(card, imageAttachments);
}

function setImgHandler(element, imageAttachments) {
  // Do nothing if there are no image attachments
  if (!imageAttachments.length > 0) {
    return;
  }
  const imgs = element.getElementsByTagName('img');
  for (const img of imgs) {
    img.addEventListener('click', (e) => {
      // Fetch full-scale image upon click
      fetchFullScaleImage(e, imageAttachments);
    });
  }
}

async function fetchPreviewImages(attachments) {
  if (!attachments.length > 0) {
    return;
  }
  // Since each message could contain more than one inline image
  // we need to fetch them individually 
  const result = await Promise.all(
      attachments.map(async (attachment) => {
        // Fetch preview image from its 'previewURL'
        const response = await fetch(attachment.previewUrl, {
          method: 'GET',
          headers: {
            // The token here should be the same one from chat initialization
            'Authorization': 'Bearer ' + tokenString,
          },
        });
        // The response would be in an image blob, so we can render it directly
        return {
          id: attachment.id,
          content: await response.blob(),
        };
      }),
  );
  result.forEach((imageResult) => {
    const urlCreator = window.URL || window.webkitURL;
    const url = urlCreator.createObjectURL(imageResult.content);
    // Look up the image ID and replace its 'src' with object URL
    document.getElementById(imageResult.id).src = url;
  });
}

В этом примере вы создали две вспомогательные функции и fetchPreviewImages setImgHandler. Первый извлекает изображение предварительного просмотра непосредственно из предоставленного previewURL в каждом ChatAttachment объекте заголовком проверки подлинности. Аналогичным образом вы настраиваете onclick событие для каждого изображения в функции setImgHandler. В обработчике событий вы извлекаете полномасштабное изображение из свойства url из ChatAttachment объекта с заголовком проверки подлинности.

Теперь необходимо предоставить маркер на глобальном уровне, так как необходимо создать с ним заголовок проверки подлинности. Необходимо изменить следующий код:

// New variable for token string
var tokenString = '';

async function init() {

   ....
   
   let tokenResponse = await identityClient.getToken(identityResponse, [
      "voip",
      "chat"
	]);
	const { token, expiresOn } = tokenResponse;
   
   // Save to token string
   tokenString = token;
   
   ...
}

Чтобы отобразить полномасштабное изображение в наложении, также необходимо добавить новый компонент:


<div class="overlay" id="overlay-container">
   <div class="content">
      <img id="full-scale-image" src="" alt="" />
   </div>
</div>

С некоторыми CSS:


/* let's make chat popup scrollable */
.chat-popup {

   ...
   
   max-height: 650px;
   overflow-y: scroll;
}

 .overlay {
    position: fixed; 
    width: 100%; 
    height: 100%;
    background: rgba(0, 0, 0, .7);
    top: 0;
    left: 0;
    z-index: 100;
 }

.overlay .content {
   position: fixed; 
   width: 100%;
   height: 100%;
   text-align: center;
   overflow: hidden;
   z-index: 100;
   margin: auto;
   background-color: rgba(0, 0, 0, .7);
}

.overlay img {
   position: absolute;
   display: block;
   max-height: 90%;
   max-width: 90%;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
}

#overlay-container {
   display: none
}

Теперь, когда наложение настроено, пришло время работать над логикой для отрисовки полномасштабных изображений. Помните, что вы создали onClick обработчик событий для вызова функции fetchFullScaleImage:


const overlayContainer = document.getElementById('overlay-container');
const loadingImageOverlay = document.getElementById('full-scale-image');

function fetchFullScaleImage(e, imageAttachments) {
  // Get the image ID from the clicked image element
  const link = imageAttachments.filter((attachment) =>
    attachment.id === e.target.id)[0].url;
  loadingImageOverlay.src = '';
  
  // Fetch the image
  fetch(link, {
    method: 'GET',
    headers: {'Authorization': 'Bearer ' + tokenString},
  }).then(async (result) => {
   
    // Now we set image blob to our overlay element
    const content = await result.blob();
    const urlCreator = window.URL || window.webkitURL;
    const url = urlCreator.createObjectURL(content);
    loadingImageOverlay.src = url;
  });
  // Show overlay
  overlayContainer.style.display = 'block';
}

Последнее, что вы хотите добавить, — это возможность закрыть наложение при щелчке изображения:

loadingImageOverlay.addEventListener('click', () => {
  overlayContainer.style.display = 'none';
});

Теперь вы внесли все изменения, необходимые для отображения встроенных изображений для сообщений, поступающих из уведомлений в режиме реального времени.

Выполнение кода

Пользователи Webpack могут использовать webpack-dev-server для создания и запуска приложения. Выполните следующую команду, чтобы упаковать узел приложения на локальном веб-сервере:

npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map

Демонстрация

Откройте свой браузер и перейдите по адресу http://localhost:8080/. Введите URL-адрес собрания и идентификатор потока. Отправьте несколько встроенных образов из клиента Teams.

Снимок экрана: клиент Teams с отправленным сообщением: вот некоторые идеи, сообщите мне, что вы думаете! Сообщение также содержит два встроенных изображения макетов интерьера комнаты.

Затем вы увидите новое сообщение, отрисованное вместе с изображениями предварительного просмотра.

Снимок экрана: пример приложения с входящим сообщением с встроенными изображениями.

После того как пользователь Службы коммуникации Azure выбирает изображение предварительного просмотра, появится наложение с полным масштабируемым изображением, отправленным пользователем Teams.

Снимок экрана: пример приложения с наложением полномасштабного изображения.

Обработка отправки встроенных изображений в новом запросе на сообщение

Внимание

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

Предварительные версии API и пакеты SDK предоставляются без соглашения об уровне обслуживания. Рекомендуется не использовать их для рабочих нагрузок. Некоторые функции могут не поддерживаться или могут иметь ограниченные возможности.

Дополнительные сведения см . в дополнительных условиях использования для предварительных версий Microsoft Azure.

Помимо обработки сообщений с встроенными изображениями, пакет SDK чата для JavaScript также предоставляет решение, позволяющее пользователю связи отправлять встроенные образы пользователю Microsoft Teams в чате взаимодействия.

Ознакомьтесь с новым API из ChatThreadClient:

var imageAttachment = await chatThreadClient.uploadImage(blob, file.name, {
  "onUploadProgress": reportProgressCallback
});

API принимает большой двоичный объект изображения, строку имени файла и обратный вызов функции, который сообщает о ходе отправки.

Чтобы отправить изображение другому участнику чата, необходимо:

  1. Отправьте изображение через uploadImage API ChatThreadClientи сохраните возвращенный объект.
  2. Создайте содержимое сообщения и задайте вложение возвращаемого объекта, сохраненного на предыдущем шаге.
  3. Отправьте новое сообщение через sendMessage API из ChatThreadClient.

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

<label for="myfile">Attach images:</label>
<input id="upload" type="file" id="myfile" name="myfile" accept="image/*" multiple>
<input style="display: none;" id="upload-result"></input>

Теперь настройте прослушиватель событий при изменении состояния:

document.getElementById("upload").addEventListener("change", uploadImages);

При изменении состояния необходимо создать новую функцию:

var uploadedImageModels = [];

async function uploadImages(e) {
  const files = e.target.files;
  if (files.length === 0) {
    return;
  }
  for (let key in files) {
    if (files.hasOwnProperty(key)) {
        await uploadImage(files[key]);
    }
}
}

async function uploadImage(file) {
  const buffer = await file.arrayBuffer();
  const blob = new Blob([new Uint8Array(buffer)], {type: file.type });
  const url = window.URL.createObjectURL(blob);
  document.getElementById("upload-result").innerHTML += `<img src="${url}" height="auto" width="100" />`;
  let uploadedImageModel = await chatThreadClient.uploadImage(blob, file.name, {
    imageBytesLength: file.size
  });
  uploadedImageModels.push(uploadedImageModel);
}

В этом примере вы создали FileReader образ для чтения каждого изображения в виде base64закодированных изображений, а затем создайте перед Blob вызовом API ChatSDK для их отправки. Вы создали глобальную uploadedImageModels среду для сохранения моделей данных отправленных изображений из пакета SDK чата.

Наконец, необходимо изменить sendMessageButton прослушиватель событий, созданный ранее, чтобы подключить отправленные образы.

sendMessageButton.addEventListener("click", async () => {
  let message = messagebox.value;
  let attachments = uploadedImageModels;

    // Inject image tags for images we have selected
  // so they can be treated as inline images
  // Alternatively, we can use some third-party libraries 
  // to have a rich text editor with inline image support
  message += attachments.map((attachment) => `<img id="${attachment.id}" />`).join("");

  let sendMessageRequest = {
    content: message,
    attachments: attachments,
  };

  let sendMessageOptions = {
    senderDisplayName: "Jack",
    type: "html"
  };

  let sendChatMessageResult = await chatThreadClient.sendMessage(
    sendMessageRequest,
    sendMessageOptions
  );
  let messageId = sendChatMessageResult.id;
  uploadedImageModels = [];

  messagebox.value = "";
  document.getElementById("upload").value = "";
  console.log(`Message sent!, message id:${messageId}`);
});

Вот и все. Теперь запустите код, чтобы увидеть его в действии.

Выполнение кода

Пользователи Webpack могут использовать webpack-dev-server для создания и запуска приложения. Выполните следующую команду, чтобы упаковать узел приложения на локальном веб-сервере:

npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map

Демонстрация

Откройте свой браузер и перейдите по адресу http://localhost:8080/. У вас есть новый раздел в поле отправки для присоединения изображений.

Снимок экрана: пример приложения с недавно добавленным разделом для присоединения изображений.

Затем можно выбрать изображения, которые вы хотите присоединить.

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

Снимок экрана: пример приложения с двумя изображениями, подключенными.

Теперь пользователь Teams должен получать изображение, отправленное при нажатии кнопки "Отправить".

Снимок экрана: пример приложения с отправленным сообщением с двумя внедренными изображениями.

Снимок экрана: клиент Teams с полученным сообщением с двумя внедренными изображениями.

В этом руководстве показано, как включить встроенную поддержку образов с помощью пакета SDK для чата Службы коммуникации Azure для C#.

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

  • Обработка встроенных изображений для новых сообщений.

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

Goal

  • previewUri Захватите свойство для встроенных вложений изображений.

Обработка встроенных изображений для новых сообщений

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

  CommunicationUserIdentifier currentUser = new(user_Id_);
  AsyncPageable<ChatMessage> allMessages = chatThreadClient.GetMessagesAsync();
  SortedDictionary<long, string> messageList = [];
  int textMessages = 0;
  await foreach (ChatMessage message in allMessages)
  {
      if (message.Type == ChatMessageType.Html || message.Type == ChatMessageType.Text)
      {
          textMessages++;
          var userPrefix = message.Sender.Equals(currentUser) ? "[you]:" : "";
          var strippedMessage = StripHtml(message.Content.Message);
          messageList.Add(long.Parse(message.SequenceId), $"{userPrefix}{strippedMessage}");
      }
  }

Из входящего события типа ChatMessageReceivedEventсвойство с именем attachments содержит сведения о встроенном изображении. Все, что вам нужно отрисовки встроенных изображений в пользовательском интерфейсе.

public class ChatAttachment
{
    public ChatAttachment(string id, ChatAttachmentType attachmentType)
    public ChatAttachmentType AttachmentType { get }
    public string Id { get }
    public string Name { get }
    public System.Uri PreviewUrl { get }
    public System.Uri Url { get }
}

public struct ChatAttachmentType : System.IEquatable<AttachmentType>
{
    public ChatAttachmentType(string value)
    public static File { get }
    public static Image { get }
}

Следующий код JSON — это пример того, что ChatAttachment может выглядеть для вложения изображения:

"attachments": [
    {
        "id": "9d89acb2-c4e4-4cab-b94a-7c12a61afe30",
        "attachmentType": "image",
        "name": "Screenshot.png",
        "url": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/original?api-version=2023-11-03",
        "previewUrl": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/small?api-version=2023-11-03"
      }
]

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

  CommunicationUserIdentifier currentUser = new(user_Id_);
  AsyncPageable<ChatMessage> allMessages = chatThreadClient.GetMessagesAsync();
  SortedDictionary<long, string> messageList = [];
  int textMessages = 0;
  await foreach (ChatMessage message in allMessages)
  {
      // Get message attachments that are of type 'image'
      IEnumerable<ChatAttachment> imageAttachments = message.Content.Attachments.Where(x => x.AttachmentType == ChatAttachmentType.Image);

      // Fetch image and render
      var chatAttachmentImageUris = new List<Uri>();
      foreach (ChatAttachment imageAttachment in imageAttachments)
      {
          client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", communicationTokenCredential.GetToken().Token);
          var response = await client.GetAsync(imageAttachment.PreviewUri);
          var randomAccessStream = await response.Content.ReadAsStreamAsync();
          await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
          {
              var bitmapImage = new BitmapImage();
              await bitmapImage.SetSourceAsync(randomAccessStream.AsRandomAccessStream());
              InlineImage.Source = bitmapImage;
          });

          chatAttachmentImageUris.Add(imageAttachment.PreviewUri);
      }

      // Build message list
      if (message.Type == ChatMessageType.Html || message.Type == ChatMessageType.Text)
      {
          textMessages++;
          var userPrefix = message.Sender.Equals(currentUser) ? "[you]:" : "";
          var strippedMessage = StripHtml(message.Content.Message);
          var chatAttachments = chatAttachmentImageUris.Count > 0 ? "[Attachments]:\n" + string.Join(",\n", chatAttachmentImageUris) : "";
          messageList.Add(long.Parse(message.SequenceId), $"{userPrefix}{strippedMessage}\n{chatAttachments}");
      }

В этом примере вы захватываете все вложения из сообщения типа Image , а затем извлекаете каждый из изображений. Для авторизации необходимо использовать часть Token Bearer заголовка запроса. После скачивания изображения его можно назначить InlineImage элементу представления.

Вы также включаете список URI вложений, отображаемых вместе с сообщением в списке текстовых сообщений.

Демонстрация

  • Запустите приложение из интегрированной среды разработки (IDE).
  • Введите ссылку на собрание Teams.
  • Присоединитесь к собранию.
  • Допустите пользователя на стороне Teams.
  • Отправьте сообщение с стороны Teams с изображением.

URL-адрес, включенный в сообщение, отображается в списке сообщений. Последнее полученное изображение отображается в нижней части окна.

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