共用方式為


教學課程:在聊天應用程式中啟用內嵌影像支援

聊天 SDK 的設計旨在順暢地與 Microsoft Teams 搭配使用。 具體來說,Chat SDK 提供一個解決方案來接收內嵌影像,並將內嵌影像傳送給 Microsoft Teams 中的使用者。

在本教學課程中,您將瞭解如何使用適用於 JavaScript 的 Azure 通訊服務 Chat SDK 來啟用內嵌影像支援。

內嵌影像是直接複製到 Teams 用戶端傳送方塊並貼上的影像。 對於透過 [從此裝置上傳] 功能表或透過拖放上傳的影像,例如直接拖曳至 Teams 中的傳送方塊的影像,您必須將本教學課程稱為檔案共用功能的一部分。 (請參閱一節。

若要複製映像,Teams 使用者有兩個選項:

  • 使用操作系統的操作功能表複製映像檔,然後將它貼到Teams用戶端的傳送方塊中。
  • 使用鍵盤快捷方式。

在本教學課程中,您會了解當您:

注意

傳送內嵌影像的功能目前可在公開預覽中取得。 它僅適用於 JavaScript。 若要接收內嵌影像,目前已正式推出。 它適用於 Teams 互操作性聊天中的 JavaScript 和 C#。

必要條件

  • 檢閱將聊天應用程式加入Teams會議的快速入門
  • 建立 Azure 通訊服務資源。 如需詳細資訊,請參閱建立 Azure 通訊服務資源。 您必須記錄此教學課程的連接字串
  • 使用您的商務帳戶設定 Teams 會議,並備妥會議 URL。
  • 使用 Chat SDK for JavaScript (@azure/communication-chat) 1.4.0 或最新版。 如需詳細資訊,請參閱 適用於 JavaScript 的 Azure 通訊聊天用戶端連結庫。

範例指令碼

GitHub 上尋找本教學課程的完成程式碼。

處理新訊息事件中收到的內嵌影像

在本節中,您將瞭解如何轉譯內嵌在新訊息接收事件訊息內容中的內嵌影像。

在快速入門,您已建立chatMessageReceived事件的事件處理程式,當您收到來自Teams使用者的新訊息時,就會觸發此事件處理程式。 您也會在收到 來自 chatClient的事件時chatMessageReceived,直接將傳入訊息內容附加至 messageContainer ,如下所示:

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 的屬性包含內嵌影像的相關信息。 您只需要在 UI 中轉譯內嵌影像:

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 物件中提供的 擷取預覽影像,其中包含驗證標頭。 同樣地,您會為 函式 中的每個setImgHandler影像設定onclick事件。 在事件處理程式中,您會從ChatAttachment具有驗證標頭之 對象的 屬性url擷取完整縮放影像。

現在您必須將令牌公開至全域層級,因為您需要使用它建構驗證標頭。 您必須修改下列程式代碼:

// 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 建置和執行您的應用程式。 執行下列命令,將應用程式主機組合在本機 Web 伺服器上:

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 預覽版增補使用規定

除了處理內嵌影像的訊息之外,Chat SDK for JavaScript 也提供解決方案,可讓通訊使用者在互操作性聊天中將內嵌影像傳送給 Microsoft Teams 使用者。

請從 檢視新的 API ChatThreadClient

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

API 會採用影像 Blob、檔名字符串,以及報告上傳進度的函式回呼。

若要將影像傳送給其他聊天參與者,您需要:

  1. 透過 uploadImage API 上傳影像, ChatThreadClient然後儲存傳回的物件。
  2. 撰寫郵件內容,並將附件設定為您在上一個步驟中儲存的傳回物件。
  3. 透過 API 從 ChatThreadClient傳送新訊息sendMessage

建立接受影像的新檔案選擇器:

<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編碼的影像,然後在呼叫 ChatSDK API 以上傳影像之前先建立 Blob 。 您已建立全域 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 建置和執行您的應用程式。 執行下列命令,將應用程式主機組合在本機 Web 伺服器上:

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

示範

開啟瀏覽器,並前往 http://localhost:8080/。 您在傳送方塊中有新的區段來附加影像。

此螢幕快照顯示已新增區段以附加影像的範例應用程式。

接下來,您可以選取您要附加的影像。

此螢幕快照顯示檔案選擇器,其中包含使用者可以附加至其訊息的影像清單。

顯示連結兩個影像之範例應用程式的螢幕快照。

Teams 使用者現在應該會在選取 [ 傳送] 時收到您剛傳送的影像。

顯示範例應用程式的螢幕快照,其中含有兩個內嵌影像的已傳送訊息。

顯示 Teams 用戶端的螢幕快照,其中包含兩個內嵌影像的已接收訊息。

本教學課程說明如何使用適用於 C# 的 Azure 通訊服務 Chat SDK 來啟用內嵌影像支援。

在本教學課程中,您會了解如何:

  • 處理新訊息的內嵌影像。

必要條件

  • 檢閱將聊天應用程式加入Teams會議的快速入門
  • 建立 Azure 通訊服務資源。 如需詳細資訊,請參閱建立 Azure 通訊服務資源。 您必須記錄此教學課程的連接字串
  • 使用您的商務帳戶設定 Teams 會議,並備妥會議 URL。
  • 使用適用於 C# 的聊天 SDK (Azure.Communication.Chat) 1.3.0 版或最新版本。 如需詳細資訊,請參閱 適用於 .NET 的 Azure 通訊聊天用戶端連結庫。

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 的屬性包含內嵌影像的相關信息。 您只需要在UI中轉譯內嵌影像。

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 訊息擷取所有附件,然後擷取每個影像。 您必須在要求標頭的部分中使用 ,TokenBearer才能進行授權。 下載影像之後,您可以將它指派給 InlineImage 檢視的 元素。

您也會包含要與文字訊息清單中訊息一起顯示的附件 URI 清單。

示範

  • 從整合開發環境執行應用程式(IDE)。
  • 輸入 Teams 會議連結。
  • 加入會議。
  • 承認 Teams 端的使用者。
  • 使用影像從 Teams 端傳送訊息。

訊息隨附的 URL 會出現在訊息清單中。 最後一個接收的影像會在視窗底部呈現。

下一步