Compartir vía


Tutorial: Habilitar la compatibilidad con imágenes insertadas en la aplicación de chat

El SDK de chat está diseñado para trabajar con Microsoft Teams sin problemas. En concreto, el SDK de chat proporciona una solución para recibir imágenes insertadas y enviar imágenes insertadas a los usuarios de Microsoft Teams.

En este tutorial, aprenderá a habilitar la compatibilidad con imágenes insertadas mediante el SDK de Azure Communication Services Chat para JavaScript.

Las imágenes insertadas son imágenes que se copian y pegan directamente en el cuadro de envío del cliente de Teams. Para las imágenes cargadas a través del menú Cargar desde este dispositivo o mediante arrastrar y colocar, como imágenes arrastradas directamente al cuadro de envío en Teams, debe hacer referencia a este tutorial como parte de la característica de uso compartido de archivos. (Vea la sección "Controlar datos adjuntos de imágenes").

Para copiar una imagen, los usuarios de Teams tienen dos opciones:

  • Use el menú contextual del sistema operativo para copiar el archivo de imagen y péguelo en el cuadro de envío de su cliente de Teams.
  • Usar métodos abreviados de teclado.

En este tutorial, aprenderá lo que necesita hacer cuando:

Nota:

La capacidad de enviar imágenes insertadas está disponible actualmente en versión preliminar pública. Solo está disponible para JavaScript. Para recibir imágenes insertadas, actualmente está disponible con carácter general. Está disponible para JavaScript y C# en un chat de interoperabilidad de Teams.

Requisitos previos

Código de ejemplo

Encuentre el código finalizado de este tutorial en GitHub.

Control de imágenes insertadas recibidas en un nuevo evento de mensaje

En esta sección, aprenderá a representar imágenes insertadas en el contenido del mensaje insertado de un nuevo evento recibido.

En el inicio rápido, creó un controlador de eventos para el evento chatMessageReceived, que se desencadena cuando recibe un nuevo mensaje del usuario de Teams. También anexa contenido de mensaje entrante a messageContainer directamente después de recibir el chatMessageReceived evento del chatClient, de la siguiente manera:

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

Desde el evento entrante de tipo ChatMessageReceivedEvent, una propiedad denominada attachments contiene información sobre la imagen insertada. Es todo lo que necesita para representar imágenes insertadas en la interfaz de usuario:

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

Ahora vuelva al código anterior para agregar cierta lógica adicional, como los fragmentos de código siguientes:

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

En este ejemplo, ha creado dos funciones auxiliares, fetchPreviewImages y setImgHandler. La primera captura la imagen de vista previa directamente de la previewURL proporcionada en cada objeto ChatAttachment con un encabezado de autenticación. Del mismo modo, configurará un evento de onclick para cada imagen de la función setImgHandler. En el controlador de eventos, se captura una imagen a escala completa de la propiedad url del objeto ChatAttachment con un encabezado de autenticación.

Ahora debe exponer el token en el nivel global porque necesita construir un encabezado de autenticación con él. Debe modificar el código siguiente:

// 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 la imagen a escala completa en una superposición, también debe agregar un nuevo componente:


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

Con algunas hojas de estilo 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
}

Ahora que tiene configurada una superposición, es el momento de trabajar en la lógica para representar imágenes a escala completa. Recuerde que ha creado un controlador de eventos onClick para llamar a una función 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';
}

Una última cosa que desea agregar es la capacidad de descartar la superposición cuando se hace clic en la imagen:

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

Ahora ha realizado todos los cambios que necesita para representar imágenes insertadas para los mensajes que proceden de notificaciones en tiempo real.

Ejecución del código

Los usuarios de Webpack pueden usar webpack-dev-server para compilar y ejecutar la aplicación. Ejecute el siguiente comando para agrupar el host de la aplicación en un servidor web local:

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

Demostración

Abra su explorador y diríjase a http://localhost:8080/. Escriba la dirección URL de la reunión y el identificador de la conversación. Envíe algunas imágenes insertadas desde el cliente de Teams.

Captura de pantalla que muestra un cliente de Teams con un mensaje enviado que lee: Estas son algunas ideas, hágame saber lo que piensa. El mensaje también contiene dos imágenes insertadas de bocetos interiores de la sala.

A continuación, debería ver el nuevo mensaje representado junto con las imágenes de vista previa.

Captura de pantalla que muestra una aplicación de ejemplo con un mensaje entrante con imágenes insertadas.

Una vez que el usuario de Azure Communication Services selecciona la imagen de vista previa, aparece una superposición con la imagen de escala completa enviada por el usuario de Teams.

Captura de pantalla que muestra una aplicación de ejemplo con una superposición de una imagen a escala completa.

Controlar el envío de imágenes insertadas en una nueva solicitud de mensaje

Importante

Esta característica de Azure Communication Services se encuentra actualmente en versión preliminar.

Las API y los SDK en versión preliminar se proporcionan sin contrato de nivel de servicio. Se recomienda no usarlos para las cargas de trabajo de producción. Es posible que algunas características no sean compatibles o que sus funcionalidades estén limitadas.

Para obtener más información, consulte Términos de uso complementarios para las Versiones preliminares de Microsoft Azure.

Además de controlar mensajes con imágenes insertadas, el SDK de Chat para JavaScript también proporciona una solución para permitir que el usuario de comunicación envíe imágenes insertadas al usuario de Microsoft Teams en un chat de interoperabilidad.

Eche un vistazo a la nueva API de ChatThreadClient:

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

La API toma un blob de imagen, una cadena de nombre de archivo y una devolución de llamada de función que informa del progreso de la carga.

Para enviar una imagen a otro participante de chat, debe:

  1. Cargue la imagen a través de la uploadImageAPI desde ChatThreadClient, y guarde el objeto devuelto.
  2. Redacte el contenido del mensaje y establezca un archivo adjunto en el objeto devuelto que guardó en el paso anterior.
  3. Envíe el nuevo mensaje a través de la sendMessageAPI desde ChatThreadClient.

Cree un nuevo selector de archivos que acepte imágenes:

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

Ahora, configure un agente de escucha de eventos para cuando se produzca un cambio de estado:

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

Debe crear una nueva función para cuando cambie el estado:

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

En este ejemplo, ha creado un FileReader para leer cada imagen como imágenes codificadas base64y, a continuación, crear un Blob antes de llamar a la API ChatSDK para cargarlas. Ha creado un global uploadedImageModels para guardar los modelos de datos de imágenes cargadas desde el SDK de Chat.

Por último, debe modificar el agente de escucha de eventos sendMessageButton que creó anteriormente para adjuntar las imágenes que cargó.

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

Eso es. Ahora ejecute el código para verlo en acción.

Ejecución del código

Los usuarios de Webpack pueden usar webpack-dev-server para compilar y ejecutar la aplicación. Ejecute el siguiente comando para agrupar el host de la aplicación en un servidor web local:

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

Demostración

Abra su explorador y diríjase a http://localhost:8080/. Tiene una nueva sección en el cuadro de envío para adjuntar imágenes.

Captura de pantalla que muestra una aplicación de ejemplo con una sección recién agregada para adjuntar imágenes.

A continuación, puede seleccionar las imágenes que desea adjuntar.

Captura de pantalla que muestra un selector de archivos con una lista de imágenes que los usuarios pueden adjuntar a sus mensajes.

Captura de pantalla que muestra la aplicación de ejemplo con dos imágenes adjuntas.

El usuario de Teams ahora debería recibir la imagen que acaba de enviar al seleccionar Enviar.

Captura de pantalla que muestra la aplicación de ejemplo con un mensaje enviado con dos imágenes insertadas.

Captura de pantalla que muestra el cliente de Teams con un mensaje recibido con dos imágenes insertadas.

En este tutorial se muestra cómo habilitar la compatibilidad con imágenes insertadas mediante el SDK de Azure Communication Services Chat para C#.

En este tutorial, aprenderá a:

  • Controle las imágenes insertadas para los nuevos mensajes.

Requisitos previos

Objetivo

  • Tome la propiedad previewUri para los datos adjuntos de imagen insertados.

Control de imágenes insertadas para mensajes nuevos

En el inicio rápido, sondea mensajes y anexa nuevos mensajes a la propiedad messageList. Más adelante, se basa en esta funcionalidad para incluir el análisis y la captura de las imágenes insertadas.

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

Desde el evento entrante de tipo ChatMessageReceivedEvent, la propiedad denominada attachments contiene información sobre la imagen insertada. Es todo lo que necesita para representar imágenes insertadas en la interfaz de usuario.

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

El siguiente JSON es un ejemplo de lo que ChatAttachment podría tener un aspecto para los datos adjuntos de una imagen:

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

Ahora vuelva y reemplace el código para agregar lógica adicional para analizar y capturar los datos adjuntos de la imagen:

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

En este ejemplo, obtendrá todos los datos adjuntos del mensaje de tipo Image y, a continuación, capturará cada una de las imágenes. Debe usar el Token en la parte Bearer del encabezado de solicitud con fines de autorización. Una vez descargada la imagen, puede asignarla al elemento InlineImage de la vista.

También se incluye una lista de los URI de datos adjuntos que se mostrarán junto con el mensaje de la lista de mensajes de texto.

Demostración

  • Ejecute la aplicación desde el entorno de desarrollo integrado (IDE).
  • Siga el vínculo a una reunión de Teams.
  • Unirse a la reunión.
  • Admita el usuario en el lado de Teams.
  • Envíe un mensaje desde Teams con una imagen.

La dirección URL incluida con el mensaje aparece en la lista de mensajes. La última imagen recibida se representa en la parte inferior de la ventana.

Pasos siguientes