Udostępnij za pośrednictwem


Samouczek: włączanie obsługi obrazów wbudowanych w aplikacji Czat

Zestaw SDK czatu został zaprojektowany do bezproblemowej pracy z usługą Microsoft Teams. Zestaw SDK czatu udostępnia rozwiązanie do odbierania obrazów wbudowanych i wysyłania obrazów wbudowanych do użytkowników z usługi Microsoft Teams.

Z tego samouczka dowiesz się, jak włączyć obsługę obrazów wbudowanych przy użyciu zestawu SDK czatu usług Azure Communication Services dla języka JavaScript.

Obrazy wbudowane to obrazy, które są kopiowane i wklejane bezpośrednio w polu wysyłania klienta usługi Teams. W przypadku obrazów przekazanych za pośrednictwem menu Przekaż z tego urządzenia lub przeciągania i upuszczania, takich jak obrazy przeciągane bezpośrednio do pola wysyłania w usłudze Teams, należy odwołać się do tego samouczka w ramach funkcji udostępniania plików. (Zobacz sekcję "Obsługa załączników obrazów").

Aby skopiować obraz, użytkownicy usługi Teams mają dwie opcje:

  • Użyj menu kontekstowego systemu operacyjnego, aby skopiować plik obrazu, a następnie wkleić go do pola wysyłania klienta usługi Teams.
  • Użyj skrótów klawiaturowych.

Z tego samouczka dowiesz się, co należy zrobić, gdy:

Uwaga

Możliwość wysyłania obrazów wbudowanych jest obecnie dostępna w publicznej wersji zapoznawczej. Jest ona dostępna tylko dla języka JavaScript. W przypadku odbierania obrazów wbudowanych jest ona obecnie ogólnie dostępna. Jest ona dostępna zarówno dla języków JavaScript, jak i C# w czacie współdziałania usługi Teams.

Wymagania wstępne

Przykładowy kod

Znajdź sfinalizowany kod tego samouczka w witrynie GitHub.

Obsługa odebranych obrazów wbudowanych w nowym zdarzeniu wiadomości

W tej sekcji dowiesz się, jak renderować obrazy wbudowane osadzone w zawartości komunikatu nowego zdarzenia odebranego komunikatu.

W przewodniku Szybki start utworzono procedurę obsługi zdarzeń dla chatMessageReceived zdarzenia, która jest wyzwalana po otrzymaniu nowego komunikatu od użytkownika usługi Teams. Dołączasz również zawartość wiadomości przychodzących bezpośrednio messageContainer po odebraniu chatMessageReceived zdarzenia z obiektu chatClient, w następujący sposób:

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

Z przychodzącego zdarzenia typu ChatMessageReceivedEventwłaściwość o nazwie attachments zawiera informacje o obrazie wbudowanym. Wystarczy renderować obrazy wbudowane w interfejsie użytkownika:

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

Teraz wróć do poprzedniego kodu, aby dodać dodatkową logikę, na przykład następujące fragmenty kodu:

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

W tym przykładzie utworzono dwie funkcje pomocnicze i fetchPreviewImages setImgHandler. Pierwszy pobiera obraz podglądu bezpośrednio z podanego previewURL w każdym ChatAttachment obiekcie z nagłówkiem uwierzytelniania. Podobnie skonfigurujesz onclick zdarzenie dla każdego obrazu w funkcji setImgHandler. W procedurze obsługi zdarzeń pobierasz obraz o pełnej skali z właściwości url z ChatAttachment obiektu z nagłówkiem uwierzytelniania.

Teraz musisz uwidocznić token na poziomie globalnym, ponieważ musisz utworzyć z nim nagłówek uwierzytelniania. Należy zmodyfikować następujący kod:

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

Aby wyświetlić obraz o pełnej skali w nakładce, należy również dodać nowy składnik:


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

Za pomocą niektórych arkuszy 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
}

Teraz, gdy masz już skonfigurowaną nakładkę, nadszedł czas, aby pracować nad logiką w celu renderowania obrazów w pełnej skali. Pamiętaj, że utworzono procedurę obsługi zdarzeń onClick w celu wywołania funkcji 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';
}

Ostatnią rzeczą, którą chcesz dodać, jest możliwość odrzucenia nakładki po kliknięciu obrazu:

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

Teraz wprowadzono wszystkie zmiany, które należy renderować w tekście obrazów dla komunikatów pochodzących z powiadomień w czasie rzeczywistym.

Uruchamianie kodu

Użytkownicy pakietu webpack mogą używać webpack-dev-server do kompilowania i uruchamiania aplikacji. Uruchom następujące polecenie, aby utworzyć pakiet hosta aplikacji na lokalnym serwerze internetowym:

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

Demonstracja

Otwórz przeglądarkę i przejdź do http://localhost:8080/. Wprowadź adres URL spotkania i identyfikator wątku. Wyślij kilka wbudowanych obrazów z klienta usługi Teams.

Zrzut ekranu przedstawiający klienta usługi Teams z wysłaną wiadomością: Oto kilka pomysłów, daj mi znać to, co myślisz! Wiadomość zawiera również dwa wbudowane obrazy makiet wnętrz pomieszczeń.

Następnie powinien zostać wyświetlony nowy komunikat renderowany wraz z obrazami podglądu.

Zrzut ekranu przedstawiający przykładową aplikację z komunikatem przychodzącym z obrazami wbudowanymi.

Po wybraniu obrazu w wersji zapoznawczej przez użytkownika usług Azure Communication Services zostanie wyświetlona nakładka z obrazem pełnoskalowym wysłanym przez użytkownika usługi Teams.

Zrzut ekranu przedstawiający przykładową aplikację z nakładką obrazu o pełnej skali.

Obsługa wysyłania obrazów wbudowanych w nowym żądaniu wiadomości

Ważne

Ta funkcja usług Azure Communication Services jest obecnie dostępna w wersji zapoznawczej.

Interfejsy API i zestawy SDK w wersji zapoznawczej są udostępniane bez umowy dotyczącej poziomu usług. Zalecamy, aby nie używać ich w przypadku obciążeń produkcyjnych. Niektóre funkcje mogą nie być obsługiwane lub mogą mieć ograniczone możliwości.

Aby uzyskać więcej informacji, zapoznaj się z dodatkowymi warunkami użytkowania dla wersji zapoznawczych platformy Microsoft Azure.

Oprócz obsługi komunikatów przy użyciu obrazów wbudowanych zestaw Chat SDK dla języka JavaScript udostępnia również rozwiązanie umożliwiające użytkownikowi komunikacji wysyłanie obrazów wbudowanych do użytkownika aplikacji Microsoft Teams w czacie współdziałania.

Zapoznaj się z nowym interfejsem API z witryny ChatThreadClient:

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

Interfejs API przyjmuje obiekt blob obrazu, ciąg nazwy pliku i wywołanie zwrotne funkcji, które zgłasza postęp przekazywania.

Aby wysłać obraz do innego uczestnika czatu, musisz:

  1. Przekaż obraz za pośrednictwem interfejsu uploadImage API z ChatThreadClientpliku i zapisz zwrócony obiekt.
  2. Utwórz zawartość wiadomości i ustaw załącznik na zwrócony obiekt zapisany w poprzednim kroku.
  3. Wyślij nową wiadomość za pośrednictwem interfejsu sendMessage API z adresu ChatThreadClient.

Utwórz nowy selektor plików, który akceptuje obrazy:

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

Teraz skonfiguruj odbiornik zdarzeń pod kątem zmiany stanu:

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

Podczas zmiany stanu należy utworzyć nową funkcję:

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

W tym przykładzie utworzono element , FileReader aby odczytać każdy obraz jako obrazy zakodowane w base64formacie , a następnie utworzyć Blob element przed wywołaniem interfejsu API ChatSDK w celu ich przekazania. Utworzono globalny model uploadedImageModels zapisywania modeli danych przekazanych obrazów z zestawu Chat SDK.

Na koniec należy zmodyfikować sendMessageButton utworzony wcześniej odbiornik zdarzeń, aby dołączyć przekazane obrazy.

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

To wszystko. Teraz uruchom kod, aby zobaczyć go w działaniu.

Uruchamianie kodu

Użytkownicy pakietu webpack mogą używać webpack-dev-server do kompilowania i uruchamiania aplikacji. Uruchom następujące polecenie, aby utworzyć pakiet hosta aplikacji na lokalnym serwerze internetowym:

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

Demonstracja

Otwórz przeglądarkę i przejdź do http://localhost:8080/. W polu wysyłania masz nową sekcję w celu dołączenia obrazów.

Zrzut ekranu przedstawiający przykładową aplikację z nowo dodaną sekcją w celu dołączenia obrazów.

Następnie możesz wybrać obrazy, które chcesz dołączyć.

Zrzut ekranu przedstawiający selektor plików z listą obrazów, które użytkownicy mogą dołączać do swoich wiadomości.

Zrzut ekranu przedstawiający przykładową aplikację z dołączonymi dwoma obrazami.

Użytkownik usługi Teams powinien teraz otrzymać właśnie wysłany obraz po wybraniu pozycji Wyślij.

Zrzut ekranu przedstawiający przykładową aplikację z wysłaną wiadomością z dwoma obrazami osadzonymi.

Zrzut ekranu przedstawiający klienta usługi Teams z odebranym komunikatem z dwoma obrazami osadzonymi.

W tym samouczku pokazano, jak włączyć obsługę obrazów wbudowanych przy użyciu zestawu SDK czatu usług Azure Communication Services dla języka C#.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Obsługa obrazów wbudowanych dla nowych komunikatów.

Wymagania wstępne

Goal

  • previewUri Pobierz właściwość dla wbudowanych załączników obrazów.

Obsługa obrazów wbudowanych dla nowych komunikatów

W przewodniku Szybki start sondujesz komunikaty i dołączasz nowe komunikaty do messageList właściwości . Ta funkcja zostanie utworzona później w celu uwzględnienia analizowania i pobierania obrazów wbudowanych.

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

Z przychodzącego zdarzenia typu ChatMessageReceivedEventwłaściwość o nazwie attachments zawiera informacje o obrazie wbudowanym. Wystarczy renderować obrazy wbudowane w interfejsie użytkownika.

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

Poniższy kod JSON jest przykładem tego, jak ChatAttachment może wyglądać załącznik obrazu:

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

Teraz wróć i zastąp kod, aby dodać dodatkową logikę do analizowania i pobierania załączników obrazów:

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

W tym przykładzie pobierasz wszystkie załączniki z wiadomości typu Image , a następnie pobierasz każdy z obrazów. Musisz użyć elementu Token w Bearer części nagłówka żądania do celów autoryzacji. Po pobraniu obrazu można przypisać go do InlineImage elementu widoku.

Możesz również dołączyć listę identyfikatorów URI załącznika, które mają być wyświetlane wraz z wiadomością na liście wiadomości SMS.

Demonstracja

  • Uruchom aplikację ze zintegrowanego środowiska projektowego (IDE).
  • Wprowadź link do spotkania usługi Teams.
  • Dołącz do spotkania.
  • Przyznaj użytkownika po stronie aplikacji Teams.
  • Wyślij wiadomość ze strony aplikacji Teams przy użyciu obrazu.

Adres URL dołączony do wiadomości zostanie wyświetlony na liście wiadomości. Ostatni odebrany obraz jest renderowany w dolnej części okna.

Następne kroki