Partilhar via


Tutorial: Ativar o suporte a imagens embutidas no seu aplicativo de bate-papo

O SDK do Chat foi projetado para funcionar com o Microsoft Teams perfeitamente. Especificamente, o SDK de bate-papo fornece uma solução para receber imagens embutidas e enviar imagens embutidas para usuários do Microsoft Teams.

Neste tutorial, você aprenderá a habilitar o suporte a imagens embutidas usando o SDK de Chat dos Serviços de Comunicação do Azure para JavaScript.

Imagens embutidas são imagens que são copiadas e coladas diretamente na caixa de envio do cliente do Teams. Para imagens carregadas através do menu Carregar a partir deste dispositivo ou através do recurso de arrastar e largar, como imagens arrastadas diretamente para a caixa de envio no Teams, tem de consultar este tutorial como parte da funcionalidade de partilha de ficheiros. (Consulte a seção "Manipular anexos de imagem.")

Para copiar uma imagem, os usuários do Teams têm duas opções:

  • Use o menu de contexto do sistema operacional para copiar o arquivo de imagem e colá-lo na caixa de envio do cliente do Teams.
  • Use atalhos de teclado.

Neste tutorial, você aprende o que precisa fazer quando:

Nota

A capacidade de enviar imagens em linha está atualmente disponível em pré-visualização pública. Está disponível apenas para JavaScript. Para receber imagens em linha, está atualmente disponível em geral. Ele está disponível para JavaScript e C# em um bate-papo de interoperabilidade do Teams.

Pré-requisitos

Código de exemplo

Encontre o código finalizado deste tutorial no GitHub.

Manipular imagens embutidas recebidas em um novo evento de mensagem

Nesta seção, você aprenderá como renderizar imagens embutidas incorporadas no conteúdo da mensagem de um novo evento de mensagem recebida.

No início rápido, você criou um manipulador de eventos para o chatMessageReceived evento, que é acionado quando você recebe uma nova mensagem do usuário do Teams. Você também acrescenta o conteúdo da mensagem de entrada diretamente ao messageContainer receber o chatMessageReceived evento do , da chatClientseguinte forma:

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

A partir do evento de entrada do tipo ChatMessageReceivedEvent, uma propriedade nomeada attachments contém informações sobre a imagem embutida. É tudo o que você precisa para renderizar imagens embutidas em sua interface do usuário:

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";

Agora volte para o código anterior para adicionar alguma lógica extra, como os seguintes trechos de código:

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

Neste exemplo, você criou duas funções auxiliares fetchPreviewImages e setImgHandler. O primeiro busca a imagem de visualização diretamente do previewURL fornecido em cada ChatAttachment objeto com um cabeçalho de autenticação. Da mesma forma, você configura um onclick evento para cada imagem na função setImgHandler. No manipulador de eventos, você busca uma imagem em escala completa da propriedade url do ChatAttachment objeto com um cabeçalho de autenticação.

Agora você precisa expor o token para o nível global porque você precisa construir um cabeçalho de autenticação com ele. Você precisa modificar o seguinte código:

// 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;
   
   ...
}

Para mostrar a imagem em escala real em uma sobreposição, você também precisa adicionar um novo componente:


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

Com alguns 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
}

Agora que você tem uma sobreposição configurada, é hora de trabalhar na lógica para renderizar imagens em escala real. Lembre-se de que você criou um onClick manipulador de eventos para chamar uma função 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';
}

Uma última coisa que você deseja adicionar é a capacidade de descartar a sobreposição quando a imagem é clicada:

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

Agora você fez todas as alterações necessárias para renderizar imagens embutidas para mensagens provenientes de notificações em tempo real.

Executar o código

Os usuários do Webpack podem usar webpack-dev-server para criar e executar seu aplicativo. Execute o seguinte comando para agrupar o host do aplicativo em um servidor Web local:

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

Demonstração

Abra o seu browser e vá para http://localhost:8080/. Insira o URL da reunião e o ID do thread. Envie algumas imagens embutidas do cliente do Teams.

Captura de tela que mostra um cliente do Teams com uma mensagem enviada que diz: Aqui estão algumas ideias, deixe-me saber o que você pensa! A mensagem também contém duas imagens em linha de maquetes interiores da sala.

Em seguida, você verá a nova mensagem renderizada junto com as imagens de visualização.

Captura de tela que mostra um aplicativo de exemplo com uma mensagem de entrada com imagens embutidas.

Depois que o usuário dos Serviços de Comunicação do Azure seleciona a imagem de visualização, uma sobreposição aparece com a imagem em escala real enviada pelo usuário do Teams.

Captura de tela que mostra um aplicativo de exemplo com uma sobreposição de uma imagem em escala real.

Manipular o envio de imagens embutidas em uma nova solicitação de mensagem

Importante

Esta funcionalidade dos Serviços de Comunicação do Azure está atualmente em pré-visualização.

As APIs e SDKs de visualização são fornecidos sem um contrato de nível de serviço. Recomendamos que você não os use para cargas de trabalho de produção. Alguns recursos podem não ser suportados ou podem ter recursos restritos.

Para obter mais informações, consulte Termos de Utilização Suplementares para Pré-visualizações do Microsoft Azure.

Além de lidar com mensagens com imagens embutidas, o SDK de bate-papo para JavaScript também fornece uma solução para permitir que o usuário de comunicação envie imagens embutidas para o usuário do Microsoft Teams em um bate-papo de interoperabilidade.

Dê uma olhada na nova API de ChatThreadClient:

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

A API recebe um blob de imagem, uma cadeia de caracteres de nome de arquivo e um retorno de chamada de função que relata o progresso do carregamento.

Para enviar uma imagem para outro participante do chat, você precisa:

  1. Carregue a imagem através da uploadImage API de e salve o objeto retornado ChatThreadClient.
  2. Componha o conteúdo da mensagem e defina um anexo para o objeto retornado que você salvou na etapa anterior.
  3. Envie a nova mensagem através da sendMessage API de ChatThreadClient.

Crie um novo seletor de arquivos que aceite imagens:

<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>

Agora configure um ouvinte de eventos para quando houver uma alteração de estado:

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

Você precisa criar uma nova função para quando o estado for alterado:

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

Neste exemplo, você criou um FileReader para ler cada imagem como base64imagens codificadas e, em seguida, criar um Blob antes de chamar a API do ChatSDK para carregá-las. Você criou um global uploadedImageModels para salvar os modelos de dados das imagens carregadas do SDK de bate-papo.

Por fim, você precisa modificar o ouvinte de sendMessageButton eventos criado anteriormente para anexar as imagens carregadas.

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

Já está. Agora execute o código para vê-lo em ação.

Executar o código

Os usuários do Webpack podem usar webpack-dev-server para criar e executar seu aplicativo. Execute o seguinte comando para agrupar o host do aplicativo em um servidor Web local:

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

Demonstração

Abra o seu browser e vá para http://localhost:8080/. Você tem uma nova seção na caixa de envio para anexar imagens.

Captura de tela que mostra um aplicativo de exemplo com uma seção recém-adicionada para anexar imagens.

Em seguida, você pode selecionar as imagens que deseja anexar.

Captura de tela que mostra um seletor de arquivos com uma lista de imagens que os usuários podem anexar às suas mensagens.

Captura de tela que mostra o aplicativo de exemplo com duas imagens anexadas.

O usuário do Teams agora deve receber a imagem que você acabou de enviar quando selecionar Enviar.

Captura de tela que mostra o aplicativo de exemplo com uma mensagem enviada com duas imagens incorporadas.

Captura de tela que mostra o cliente do Teams com uma mensagem recebida com duas imagens incorporadas.

Este tutorial mostra como habilitar o suporte a imagens embutidas usando o SDK de Chat dos Serviços de Comunicação do Azure para C#.

Neste tutorial, irá aprender a:

  • Manipule imagens embutidas para novas mensagens.

Pré-requisitos

Goal

  • Pegue a previewUri propriedade para anexos de imagem embutidos.

Manipular imagens embutidas para novas mensagens

No início rápido, você pesquisa mensagens e acrescenta novas mensagens à messageList propriedade. Você aproveita essa funcionalidade posteriormente para incluir a análise e a busca das imagens embutidas.

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

A partir do evento de entrada do tipo ChatMessageReceivedEvent, a propriedade nomeada attachments contém informações sobre a imagem embutida. É tudo o que você precisa para renderizar imagens embutidas em sua interface do usuário.

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

O JSON a seguir é um exemplo do que ChatAttachment pode parecer para um anexo de imagem:

"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"
      }
]

Agora volte e substitua o código para adicionar lógica extra para analisar e buscar os anexos de imagem:

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

Neste exemplo, você pega todos os anexos da mensagem do tipo Image e, em seguida, busca cada uma das imagens. Você deve usar o Bearer seu Token na parte do cabeçalho da solicitação para fins de autorização. Depois que a imagem for baixada, você poderá atribuí-la ao InlineImage elemento da exibição.

Você também inclui uma lista dos URIs de anexo a serem mostrados junto com a mensagem na lista de mensagens de texto.

Demonstração

  • Execute o aplicativo a partir do ambiente de desenvolvimento integrado (IDE).
  • Insira um link de reunião do Teams.
  • Participe da reunião.
  • Admita o usuário do lado do Teams.
  • Envie uma mensagem do lado do Teams com uma imagem.

O URL incluído com a mensagem aparece na lista de mensagens. A última imagem recebida é renderizada na parte inferior da janela.

Próximos passos

  • Saiba mais sobre outros recursos de interoperabilidade suportados.
  • Confira nossa amostra de herói do bate-papo.
  • Saiba mais sobre como funciona o Chat.