Blog Post

Educator Developer Blog
19 MIN READ

Step-by-Step Contact Center Chat Analysis with Azure OpenAI & Communication Services

PascalBurume08's avatar
PascalBurume08
Copper Contributor
Apr 17, 2025

 

1. Introduction

Contact centers are the front lines of customer interaction, generating vast amounts of valuable data through chat logs, call transcripts, and emails. However, manually sifting through this data to find actionable insights is often a monumental task. Imagine the scenario of a thriving online service, like a food delivery app: as usage climbs, so does the number of customer support chats, making it incredibly difficult to pinpoint recurring problems or gauge overall satisfaction from the sea of text. How can businesses effectively tap into this wealth of information?

This post explores a powerful solution: building an automated analytics platform using Azure Communication Services (ACS) combined with the intelligence of Azure OpenAI Service. We'll outline how this integration allows businesses to process, understand, and derive actionable insights from their contact center conversations at scale, transforming raw data into a strategic asset.

(Why This Azure Stack for Analytics?)

Combining ACS and Azure OpenAI offers a compelling approach for contact center analytics:

  • Azure Communication Services (ACS): Provides a comprehensive cloud-based platform. In terms of analytics, it serves as the central hub for chat interactions or where transcripts are ingested and stored within organized threads, ready for programmatic access via its SDKs.
  • Azure OpenAI Service: Offers secure access to advanced large language models (such as GPT-4). These models excel in understanding context, summarizing extensive texts, extracting key information, and analyzing sentiment, making them ideal for processing unstructured conversation data automatically.

What We'll Cover:

  • Setting up Azure OpenAI and Azure Communication Services.
  • Finding and configuring necessary credentials (Endpoints, Keys, Connection Strings).
  • Setting up your streamlit environment.
  • Writing Python code to retrieve chat messages from ACS.
  • Crafting prompts to instruct Azure OpenAI for analysis (summary, topic, sentiment).
  • Calling the Azure OpenAI API.
  • Processing the AI-generated insights.

2. Architecture

2.1. Data Flow:

  1. The user interacts with the Streamlit application.
  2. When instructed (e.g., by a button click or automatically), the Streamlit application uses the ACS SDK to fetch chat messages from relevant threads within the Azure Communication Services.
  3. The Streamlit application prepares the chat data and formulates prompts based on the desired analysis (e.g., summarize, extract topics, analyze sentiment).
  4. These prompts and the chat data are sent to the Azure OpenAI Service.
  5. Azure OpenAI processes the information using its LLMs and returns the analysis results (summary, topics, sentiment) to the Streamlit application.
  6. The Streamlit application receives the analysis results and formats them for display in the user interface, alongside the original chat messages.

2.2. Prerequisites

Before you begin, ensure you have the following:

  • Python: Version 3.9 or later installed.
  • Azure Account: An active Azure subscription. If you're new, you can create a free account with credits.
  • Azure OpenAI Access: Your Azure subscription must have access enabled for Azure OpenAI Service. You can request access here: https://aka.ms/oaiapply
  • Code Editor/Environment: An IDE like VS Code

3. Setting Up Azure Resources

We need two core Azure services: Azure OpenAI (for the AI model) and Azure Communication Services (to access chat data).

3.1 Azure OpenAI Service

  • Requirement: You need an existing Azure OpenAI resource and a deployed model (like gpt-35-turbo, gpt-4, gpt-4o, gpt-4o).
  • Action: If you don't have one, follow the Microsoft documentation to Create and deploy an Azure OpenAI Service resource.1
  • Collect Credentials: Once deployed, navigate to your Azure OpenAI resource in the Azure Portal. Go to the "Keys and Endpoint" section. You will need to copy:
    • One of the API Keys (AZURE_OPENAI_API_KEY)
    • The Endpoint URL (AZURE_OPENAI_ENDPOINT)
    • You also need the Deployment Name you gave your model (AZURE_OPENAI_DEPLOYMENT_NAME).
    • Note the API Version you intend to use (e.g., 2024-05-01-preview).

3.2 Azure Communication Services (ACS)

This service will store or provide access to the chat threads we want to analyze.

  • Action: Create the ACS Resource
    • Log in to the Azure Portal.
    • In the top search bar, type Communication Services and select it from the results.

On the Communication Services page, click the "+ Create" button. (Alternatively: Click "+ Create a resource", search for "Communication Services" in the Marketplace, select it, and click "Create").

    • Once you click on Create, you get the option to select from the list of services. Select Communication Services from the dropdown, as shown here:

 

    • Once you land on the Communication Services page, you need to click on the Create button, as shown here:
    •  Once you’ve successfully created the Communication Service from the Marketplace, move on to the next step. On the Create resource form, choose the subscription and resource group that you set up back
    • Tags Tab (Optional): You can add tags for organization or billing purposes, but it's not required for this tutorial. Click "Next: Review + create".
  •  
    • Review + create Tab: Azure will validate your settings. Once validation passes, review the details and click the "Create" button.
    • Wait for the deployment to complete. You'll see a notification when it's done.
  • Action: Find ACS Credentials
    • Once deployed, click "Go to resource" or find your newly created Communication Services resource via the search bar or resource groups.
    • In the resource menu (left-hand side), under "Settings", click on "Keys".
    • On the Keys page, you will find:
      • The Endpoint URL.
      • Two Connection Strings (Primary and Secondary). You only need one.
    • Copy the Endpoint URL and one of the Connection String values. Keep these secure alongside your Azure OpenAI credentials.

4. Implementing the Streamlit Application Code

Now, we'll build the core of our application. Create a Python file named app.py (or your preferred name) in your project folder. We will add the code in logical sections.

4.1 Imports and Configuration Loading

First, we import all necessary libraries and load the configuration securely from our .env file. We also define some constants for sender names.

# --- Start of Streamlit App Code (app.py) ---
import streamlit as st
from azure.communication.chat import (
    ChatClient, CommunicationTokenCredential, ChatMessageType,
    ChatParticipant,
    ChatThreadClient
)
from azure.communication.identity import CommunicationIdentityClient, CommunicationUserIdentifier
from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
from datetime import datetime, timedelta
import openai
import re
import os
import time
from dotenv import load_dotenv # Import dotenv

# --- Load Environment Variables ---
load_dotenv() # Load variables from .env file at the start

# --- Configuration ---
# Load from environment with error
checking
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
AZURE_API_VERSION = os.getenv("AZURE_API_VERSION",'2024-05-01-preview') # Provide
a default API version if needed
AZURE_OPENAI_DEPLOYMENT_NAME = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME") # Your
model deployment name

COMMUNICATION_CONNECTION_STRING = os.getenv("COMMUNICATION_CONNECTION_STRING")
COMMUNICATION_ENDPOINT = os.getenv("COMMUNICATION_ENDPOINT")

# --- Early check for critical
environment variables ---
# Stop execution if essential configs
are missing to prevent errors later
if not all([AZURE_OPENAI_API_KEY,
            AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT_NAME,
            COMMUNICATION_CONNECTION_STRING, COMMUNICATION_ENDPOINT]):
    # Use st.error which is visible
    in the Streamlit app
    st.error("❌ Critical environment variables are missing! Please ensure AZURE_OPENAI_API_KEY,
             AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT_NAME,
             COMMUNICATION_CONNECTION_STRING, and COMMUNICATION_ENDPOINT are set in your
             .env file or system environment.")
    st.stop() # Halt script execution if
configuration is incomplete

# --- Application Specific Settings ---
TARGET_THREAD_TOPIC = "Streamlit ACS Interactive Demo V3" # Name for
the ACS chat thread

# --- Constants for Sender Display Names
---
SENDER_USER = "User
(via Streamlit)" # Display name for messages sent by the human user
SENDER_AI_AGENT = "AI Agent"        #
Display name for messages generated by OpenAI
SENDER_SYSTEM = "System"            # Display name for automated system messages

# --- Configure OpenAI library (using
v0.28.1 syntax) ---
# This configures the openai library
globally for this script run
openai.api_type = "azure"
openai.api_key = AZURE_OPENAI_API_KEY
openai.api_base = AZURE_OPENAI_ENDPOINT
openai.api_version = AZURE_API_VERSION

Explanation: This section imports necessary modules from Streamlit, Azure SDKs, OpenAI, and standard Python libraries. It uses dotenv to load credentials stored in the.env file into environment variables. A crucial check is added using st.error and st.stop() to halt the app gracefully if essential configuration values are missing. Finally, it defines constants for sender display names and configures the openai library to point to your Azure OpenAI resource using the loaded credentials (note this uses syntax for openai library version 0.x).

4.2 Helper Function: Parsing the AI Summary

This function takes the raw text output from the OpenAI summarization call and extracts structured information using regular expressions.

# --- Helper Function for Parsing Summary ---
def parse_summary(text):
    """ Parses the
    AI response for summary sections using regex. """
    parsed = {
        "Topic": "Not
found", "Summary": "Not
found",
        "Highlights": "Not
found", "Sentiment": "Not
found"
    }
    if not text:
        parsed["Error"] = "AI
returned no text to parse."
        return
parsed
    try:
        # Regex patterns look for
        Markdown headings (## Heading)
        # re.DOTALL makes '.' match
        newline characters within sections
        topic_match = re.search(r"##\s*Topic\s*\n(.*?)(?=\n##\s*(Summary|Highlights|Sentiment)|$)", text, re.IGNORECASE | re.DOTALL)
        if
topic_match: parsed["Topic"] = topic_match.group(1).strip()

        summary_match = re.search(r"##\s*Summary\s*\n(.*?)(?=\n##\s*(Highlights|Sentiment)|$)", text, re.IGNORECASE | re.DOTALL)
        if
summary_match: parsed["Summary"] = summary_match.group(1).strip()

        highlights_match = re.search(r"##\s*Highlights\s*\n(.*?)(?=\n##\s*Sentiment|$)", text, re.IGNORECASE | re.DOTALL)
        if
highlights_match: parsed["Highlights"] = highlights_match.group(1).strip()

        sentiment_match = re.search(r"##\s*Sentiment\s*\n(.*)", text, re.IGNORECASE | re.DOTALL)
        if
sentiment_match: parsed["Sentiment"] = sentiment_match.group(1).strip()

        # Check if parsing failed
        completely or partially
        if all(v == "Not found" for k, v in
parsed.items() if
k not in ["RawOutput","Error", "Warning"]):
            parsed["Error"] = "Could
not parse specific sections. Displaying raw output."
            parsed["RawOutput"] = text # Include
raw output if parsing fails
        elif any(v == "Not found" for k, v in
parsed.items() if
k not in ["RawOutput","Error", "Warning"]):
            parsed["Warning"] = "AI
response formatting inconsistent; some sections might be missing."

    except
Exception as
e:
        st.exception(f"Error during summary
parsing: {e}") # Log full
error
        parsed["Error"] = f"An
error occurred during parsing. Raw response:\n{text}"
        parsed["RawOutput"] = text
        parsed["Topic"], parsed["Summary"], parsed["Highlights"], parsed["Sentiment"] = "Error", "Error", "Error", "Error"
    return
parsed

Explanation: The parse_summary function takes the text generated by the AI summarization prompt. It uses regular expressions (re.search) to find sections starting with markdown headings like ## Topic. It extracts the content under each heading and stores it in a dictionary. It includes error handling for cases where the AI output doesn't match the expected format, returning an Error or Warning key in the dictionary along with the raw output if possible.

4.3 Helper Function: Initializing Azure Clients and ACS Thread

This is a critical function that sets up connections to Azure services and creates the specific chat thread for the application session. It's decorated with st.cache_resource to ensure it only runs once per session, improving performance and preventing accidental resource recreation.

# --- Initialize Clients and Thread (Cached Resource)
---
@st.cache_resource # IMPORTANT: Caches the returned objects for the
session
def initialize_azure_clients_and_thread():
    """
    Initializes Azure OpenAI & ACS clients, creates the chat thread for
    this session.
    Returns a dictionary containing clients, thread_id, and error status.
    Cached by Streamlit to run only once per session unless cache is
    cleared.
    """
    initialization_details = {
        "chat_client": None, "chat_thread_client": None,
        "thread_id": None, "error": None
    }
    try:
        # --- (Re)Configure OpenAI
        library within function scope if needed, ---
        # --- though global config
        might suffice if not changing keys ---
        # openai.api_key =
        AZURE_OPENAI_API_KEY
        # ... (rest of openai config)
...

        # --- ACS Setup: Identity
        Client, User, Token ---
        st.sidebar.text("Initializing
        ACS Identity...")
        identity_client =
        CommunicationIdentityClient.from_connection_string(COMMUNICATION_CONNECTION_STRING)
        user_streamlit = identity_client.create_user()
        token_streamlit = identity_client.get_token(user_streamlit, ["chat"])
        st.sidebar.text("ACS
        Identity Ready.")

        # --- Define Participant for
        the Thread ---
        participant_streamlit_user = ChatParticipant(
            identifier=user_streamlit,
            display_name=SENDER_USER,
            share_history_time=datetime.utcnow() - timedelta(days=1)
        )
        participants = [participant_streamlit_user]

        # --- Create base ACS Chat
        Client ---
        st.sidebar.text("Initializing
        ACS Chat Client...")
        chat_client_streamlit = ChatClient(COMMUNICATION_ENDPOINT,
        CommunicationTokenCredential(token_streamlit.token))
        initialization_details["chat_client"] = chat_client_streamlit
        st.sidebar.text("ACS
        Chat Client Ready.")

        # --- Create the ACS Chat
        Thread ---
        thread_id = None
        st.sidebar.text(f"Creating
        ACS Thread ('{TARGET_THREAD_TOPIC}')...")
        try:
            create_thread_result =
            chat_client_streamlit.create_chat_thread(
                topic=TARGET_THREAD_TOPIC,
                thread_participants=participants
            )
            thread_id =
            create_thread_result.chat_thread.id
            initialization_details["thread_id"] = thread_id
            st.sidebar.success(f"Thread Created:\nID: {thread_id[:12]}...", icon="✅")

            #
            --- Get Thread-Specific Client ---
            chat_thread_client_temp =
            chat_client_streamlit.get_chat_thread_client(thread_id)

            #
            --- Send Initial Messages ---
            st.sidebar.text("Sending initial messages...")
            try:
                chat_thread_client_temp.send_message(f"Chat
                thread '{TARGET_THREAD_TOPIC}' started.",
                sender_display_name=SENDER_SYSTEM)
                time.sleep(0.5) # Small delay helps messages appear in order
                chat_thread_client_temp.send_message("Hello!
                I am your AI assistant. How can I help?",
                sender_display_name=SENDER_AI_AGENT)
                st.sidebar.info("Initial messages sent.", icon="✉️")
            except Exception as msg_err:
                st.sidebar.warning(f"Could not send initial messages: {msg_err}", icon="⚠️")

        # --- Handle Thread Creation
        Errors ---
        except
        ResourceExistsError:
            st.sidebar.warning("Thread creation conflict
            (ResourceExistsError).", icon="⚠️")
            initialization_details["error"] = "Failed - Thread conflict."
            return initialization_details
        except
        Exception as
        e:
            initialization_details["error"] = f"Error during thread creation: {e}"
            st.exception(e)
            return initialization_details

        if not
        thread_id:
            initialization_details["error"] = "Thread ID is missing after
            creation attempt."
            return initialization_details

        # --- Store the final
        ChatThreadClient ---
        chat_thread_client =
        chat_client_streamlit.get_chat_thread_client(thread_id)
        initialization_details["chat_thread_client"] = chat_thread_client
        st.sidebar.text("Initialization
        Complete.")
        return
        initialization_details

    #
    --- Handle Overall Initialization Errors ---
    except
    Exception as
    e:
        st.error("❌ Failed during Azure
        client initialization process.")
        st.exception(e)
        initialization_details["error"] = f"Failed
        overall initialization: {e}"
        return
        initialization_details

Explanation: This function performs the critical setup on the first run of the session.

  • st.cache_resource: Makes Streamlit store the returned dictionary (initialization_details) and reuse it on subsequent script reruns within the same user session, avoiding costly re-creation of clients and threads unless the cache is explicitly cleared (e.g., by the "Clear Chat" button).
  • ACS Identity: Connects to ACS, creates a unique user identity and a temporary chat access token for this specific app session.
  • ACS Chat Client: Creates the main client object used to interact with the ACS chat service (e.g., creating/deleting threads).
  • Thread Creation: Attempts to create a new chat thread in ACS with a defined topic and adds the app user as a participant. It handles potential errors if a thread creation conflicts.
  • Initial Messages: Sends introductory messages from "System" and "AI Agent" to the newly created thread.
  • Thread Client: Gets the specific ChatThreadClient needed to interact with messages within the created thread.
  • Return Value: Returns a dictionary containing the base client, the thread-specific client, the thread ID, and any error message encountered. Sidebar messages provide visual feedback during this process.

4.4 Helper Function: Getting the AI Agent's Response

This function encapsulates the logic for generating the AI's conversational reply.

# --- Helper Function to get AI chat response ---
def get_ai_agent_response(thread_client:
ChatThreadClient):
    """Fetches
    history from ACS, formats it, calls OpenAI, returns AI response
    text."""
    if not
    thread_client:
        st.error("Cannot get AI response:
        ChatThreadClient is not initialized.")
        return "[Error: Chat client not available]"
    try:
        # 1. Fetch recent message
        history from ACS thread
        messages_for_prompt = []
        history_limit = 20 # Limit
        context window
        message_iterator =
        thread_client.list_messages(results_per_page=history_limit)
        acs_messages = list(message_iterator)
        acs_messages.reverse() # Process
        oldest first for context

        # 2. Format messages into
        OpenAI ChatCompletion structure
        for
        msg in
        acs_messages:
            sender_name =
            msg.sender_display_name
            content = msg.content.message if
            msg.content and
            msg.content.message else ""
            if not content: continue # Skip
            empty messages

            #
            Map sender to OpenAI role (user/assistant)
            if sender_name == SENDER_AI_AGENT: role = "assistant"
            elif sender_name == SENDER_USER: role = "user"
            else: role = "user"; content = f"[{sender_name}]: {content}" # Treat
            others as user context

            messages_for_prompt.append({"role":
            role, "content": content})

        if not
        messages_for_prompt: # Should
        not happen after init, but safeguard
            return "Hello!
            How may I assist you?"

        # 3. Define AI's persona via
        system message
        system_message = {"role": "system", "content": "You
        are a helpful and concise AI Agent providing support. Respond naturally to the
        user's last message based on the conversation history."}
        openai_messages = [system_message] + messages_for_prompt

        # 4. Call Azure OpenAI API
        response = openai.ChatCompletion.create(
            engine=AZURE_OPENAI_DEPLOYMENT_NAME,
            messages=openai_messages,
            max_tokens=150,
            temperature=0.7
        )
        ai_response_text = response['choices'][0]['message']['content']
        return
        ai_response_text.strip()

    except
    Exception as
    e:
        st.error("Error getting AI
        response:")
        st.exception(e) # Log full
        error
        return "[Error occurred while generating AI response.
        Please check logs.]"

Explanation: This function is called whenever the AI needs to generate a reply. It fetches the recent conversation history from the ACS thread using the provided thread_client, formats it into the [{role: 'user'/'assistant', content: '...'}] structure required by the OpenAI Chat Completion API, adds a system message to guide the AI's behavior, calls the API, and returns the AI's generated text response.

4.5 Streamlit App Structure and Initialization

This sets up the main page configuration and title and calls the initialization function.

# --- Streamlit App ---
st.set_page_config(layout="wide",
page_title="AI Chat & Summary
(ACS + OpenAI)")
st.title("💬
Interactive AI Agent Chat & Summarizer")
st.markdown(f"Using
**Azure Communication Services** & **Azure OpenAI**. Date: {datetime.now().strftime('%Y-%m-%d')}")
st.info("Type
a message, click 'Send'. AI Agent responds. Use sidebar to clear/reset. Use
summary button for analysis.")

# --- Initialize Clients and Thread ---
# This calls the cached function defined
earlier
init_details =
initialize_azure_clients_and_thread()
chat_client = init_details.get("chat_client")
chat_thread_client = init_details.get("chat_thread_client")
thread_id = init_details.get("thread_id")
init_error = init_details.get("error")

# --- Sidebar ---
st.sidebar.title("Controls")
# Sidebar button logic to clear chat
if
st.sidebar.button("🧹 Clear Chat
& Start New Thread"):
    if
    chat_client and
    thread_id:
        with
        st.spinner("Clearing chat
        thread..."):
            try:
                print(f"Attempting
                to delete thread: {thread_id}") # Server log
                chat_client.delete_chat_thread(thread_id=thread_id)
                st.sidebar.success("Previous thread deleted.")
            except ResourceNotFoundError:
                st.sidebar.warning("Thread already deleted or not found.")
            except Exception as e:
                st.sidebar.error(f"Error deleting thread: {e}")
                st.exception(e)

            #
            Clear state, cache, and rerun
            st.session_state.clear()
            initialize_azure_clients_and_thread.clear() #
            Clear cache
            st.rerun() #
            Restart script execution
    else:
        st.sidebar.error("Cannot
        clear chat - client/thread not initialized.")


# --- Initialize Session State Variables
(Safety check) ---
# Ensures these keys exist after
potential clearing
if 'current_summary' not in
st.session_state: st.session_state.current_summary = None
if 'messages' not in
st.session_state: st.session_state.messages = []
if 'send_button_clicked' not in
st.session_state: st.session_state.send_button_clicked = False

Explanation: This section configures the web page (title, layout), displays introductory text, and then calls the cached initialize_azure_clients_and_thread function to get the necessary client objects and thread ID. It defines the sidebar, including the "Clear Chat" button which deletes the ACS thread, clears session state and the resource cache, then forces a rerun. Finally, it ensures essential keys exist in Streamlit's session_state dictionary.

4.6 Main Application Logic and UI

This is the core part that runs only if the initialization is successful. It defines the layout and handles the interactive elements.

# --- Main Application Logic ---
# Only proceed if initialization was
successful (clients/thread ready)
if
chat_thread_client and not
init_error:

    # Define layout using columns
    (Chat on left, Summary on right)
    col1, col2 = st.columns([0.6, 0.4])

    # --- Column 1: Live Chat
    Interface ---
    with
    col1:
        st.subheader(f"💬 Live Chat: {TARGET_THREAD_TOPIC}")
        st.markdown("---")

        # --- Display Chat Messages ---
        message_container = st.container(height=600) #
        Scrollable container
        try:
            #
            Fetch latest messages from ACS on every script run
            chat_messages_iterator =
            chat_thread_client.list_messages(results_per_page=50)
            live_messages = list(chat_messages_iterator)
            #
            Store messages in session state for potential use by summary function
            st.session_state.messages =
            live_messages

            #
            Render messages within the container
            with message_container:
                if not live_messages:
                    st.info("No messages yet. Send a message to start!")
                else:
                    #
                    Display messages, newest at the bottom
                    for msg in reversed(live_messages):
                        sender_name =
                        msg.sender_display_name or "Unknown"
                        # Assign avatars based on sender name constants
                        if
                        sender_name == SENDER_AI_AGENT: avatar = "🤖"
                        elif
                        sender_name == SENDER_USER: avatar = "👤"
                        elif
                        sender_name == SENDER_SYSTEM: avatar = "⚙️"
                        else: avatar ="🧑‍💻" # Default

                        # Use Streamlit's chat elements for display
                        with
                        st.chat_message(name=sender_name, avatar=avatar):
                            st.caption(f"{msg.created_on.strftime('%Y-%m-%d %H:%M:%S')}") # Timestamp
                            st.write(msg.content.message or "*empty
                            message*") #
                            Message content

        except
        Exception as
        e: # Handle errors fetching messages
            with message_container:
                st.error("Error fetching messages from ACS:")
                st.exception(e)

        st.markdown("---") #
        Separator

        # --- Send Message Input Form
        ---
        # Use a form to group input and
        button click
        with
        st.form(key="send_message_form", clear_on_submit=True):
            new_message_content =
            st.text_input("Your Message:", key="new_message_input", placeholder="Type
            your message here...")
            submitted = st.form_submit_button("Send Message")
            if submitted:
                st.session_state.send_button_clicked = True # Flag
                submission

        # --- Process message sending
        (Runs after form check) ---
        if
        st.session_state.send_button_clicked:
            st.session_state.send_button_clicked = False # Reset
            flag

            if new_message_content: #
            Check if user actually typed something
                #
                Show spinner during processing
                with st.spinner("Sending
                message and getting AI response..."):
                    try:
                        # 1. Send User message to ACS
                        chat_thread_client.send_message(new_message_content,
                        sender_display_name=SENDER_USER)
                        # 2. Get AI response (calls helper function)
                        ai_reply =
                        get_ai_agent_response(chat_thread_client)
                        # 3. Send AI response back to ACS
                        if ai_reply:
                            chat_thread_client.send_message(ai_reply, sender_display_name=SENDER_AI_AGENT)
                        # 4. Rerun script to refresh chat display
                        st.rerun()
                    except Exception as e: # Handle
                    errors during send/response cycle
                        st.error("Error during send/response cycle:")
                        st.exception(e)
            else:
                st.warning("Please enter a message to send.")


    # --- Column 2: AI Summary Area
    ---
    with
    col2:
        st.subheader("✨ Conversation Summary")
        st.markdown("---")

        summary_error_message = None #
        Placeholder for specific summary errors

        # --- Generate Summary Button
        and Logic ---
        if
        st.button("Generate Full
        Conversation Summary", key="generate_summary_btn"):
            st.session_state.summary_processing = True # Flag
            processing
            st.session_state.current_summary =
            {"Info":"Processing..."} # Show
            status

            #
            Use messages already fetched and stored in session state
            fetched_messages =
            st.session_state.get('messages', [])

            if fetched_messages:
                prompt_text = ""
                #
                Build prompt text from stored messages (oldest first)
                for msg in reversed(fetched_messages):
                    #
                    ... (build prompt_text, skipping empty content) ...
                    sender_name =
                    msg.sender_display_name if msg.sender_display_name else "Unknown"
                    content =
                    msg.content.message if
                    msg.content and
                    msg.content.message else ""
                    if content: prompt_text += f"{sender_name}: {content}\n"


                if not prompt_text.strip():
                    st.session_state.current_summary = {"Info": "No
                    message content to summarize."}
                else:
                    #
                    Prepare prompt with instructions
                    instruction_phrase = 'Summarize the following conversation. Extract the
                    main topic, a brief summary, key highlights (1-3 bullet points), and overall
                    sentiment. Format clearly with headings: ## Topic, ## Summary, ## Highlights,
                    ## Sentiment.\n\n'
                    full_prompt =
                    instruction_phrase + prompt_text.strip()

                    #
                    Call OpenAI within a spinner and try/except block
                    with st.spinner("Generating
                    summary..."):
                        try:
                            response =
                            openai.ChatCompletion.create( # Or
                            Completion API
                                engine=AZURE_OPENAI_DEPLOYMENT_NAME,
                                messages=[{"role":"user","content": full_prompt}],
                                max_tokens=1024,
                                temperature=0.2
                            )
                            summary_text =
                            response['choices'][0]['message']['content']
                            # Parse and store result
                            st.session_state.current_summary = parse_summary(summary_text)
                        # Handle various OpenAI/other errors
                        except
                        openai.error.AuthenticationError as e: ... # Set
                        Error state
                        # ... other except blocks ...
                        except Exceptionas
                        e: ... # Set Error state

            else: # No
            messages fetched/stored
                st.session_state.current_summary = {"Info": "Empty
                conversation history."}

            st.session_state.summary_processing = False # Unflag
            processing
            st.rerun() #
            Rerun to update the summary display area


        # --- Display Summary (or
        Status) ---
        # Checks the state variable and
        displays Error, Warning, Info, or the parsed summary
        if
        st.session_state.get('current_summary'):
            summary_data =
            st.session_state.current_summary
            #
            --- Handle Error State ---
            if isinstance(summary_data, dict) and "Error" in summary_data: ... #
            Display st.error(...)
            #
            --- Handle Warning State ---
            elif isinstance(summary_data, dict) and "Warning" in summary_data: ... #
            Display st.warning(...) and partial data
            #
            --- Handle Info State ---
            elif isinstance(summary_data, dict) and "Info" in summary_data: ... #
            Display st.info(...)
            #
            --- Handle Successful Summary Display ---
            elif isinstance(summary_data, dict): ... # Display
            formatted markdown sections
            #
            --- Fallback ---
            else: st.error("Summary
            data unexpected format.")
        # --- Default Placeholder Text
        ---
        elif not
        st.session_state.get('summary_processing', False):
            st.info("Click
            'Generate Summary' for an analysis...")


# --- Handle Initialization Errors ---
# This runs if the main 'if
chat_thread_client...' block is False
elif
init_error:
    st.error(f"❌ Application cannot
    start due to initialization errors:")
    st.error(init_error) # Display
    the error caught during initialization
else:
    # Fallback if client is None
    but no specific error was caught
    st.warning("⏳ Initializing Azure
    services or waiting for resources...")

# --- End of Streamlit App Code ---

Explanation: This is the main part of the application that runs if the initial Azure setup was successful.

  • Layout: It defines a two-column layout (st.columns).
  • Column 1 (Chat):
    • Fetches messages from the ACS thread on every rerun using chat_thread_client.list_messages().
    • Stores these messages in st.session_state.messages (primarily for the summary function's use).
    • Displays the messages chronologically in a scrollable container using st.chat_message.
    • Provides a text input and send button within a st.form.
    • Handles the form submission by sending the user's message, getting the AI response via the helper function, sending the AI response, and triggering a st.rerun to refresh the display.
  • Column 2 (Summary):
    • Includes the "Generate Full Conversation Summary" button.
    • When clicked, it takes the messages stored in session state, creates a summarization prompt, calls Azure OpenAI, parses the result using parse_summary, and stores the output (or error/info) in st.session_state.current_summary.
    • It then conditionally displays the summary information (Topic, Summary, Highlights, Sentiment) or any errors/warnings/info messages based on the content of st.session_state.current_summary.
  • Initialization Error Handling: The final elif/else catches cases where the chat_thread_client wasn't successfully obtained during startup and displays relevant error or waiting messages.

5. Running the Streamlit Application

With your Azure resources configured, the Python environment set up, and the code saved in app.py (or your chosen filename), you are ready to run the interactive application.

5.1. Run the App:

Open your terminal or command prompt, make sure you are in your project directory (where app.py and .env are located), and execute the command:

streamlit run app.py

5.2. Initial Loading State:

Streamlit will start its server and provide you with a local URL (usually http://localhost:8501). When you open this URL in your web browser, you will first see the application's initial loading state, similar to this image:

  • The page displays the title "Interactive AI Agent Chat (V3)" and the initial informational text.
  • You'll notice a spinner element with text like "Running: initialize_azure_clients_and_thread()". This indicates that the application is currently performing the one-time setup for the session.
  • During this phase (which might take a few moments, especially on the very first run or after clearing the chat), the cached function initialize_azure_clients_and_thread is executing. It connects to Azure Communication Services, creates a user identity and token, establishes a connection to Azure OpenAI, and creates the specific chat thread in ACS, sending the initial messages.

5.3. Interact with the App:

  • Once the initialization is complete, the spinner will disappear.
  • The main chat interface will load, displaying the initial "System" and "AI Agent" messages fetched from the newly created ACS thread.
  • Type your messages into the input box at the bottom of the chat column and click "Send Message". You will see your message appear, followed by a response from the "AI Agent" after a brief processing time (indicated by a spinner).
  • Navigate to the right-hand column and click "Generate Full Conversation Summary" anytime to get an AI-powered analysis of the conversation so far.
  • Use the "Clear Chat & Start New Thread" button in the sidebar to delete the current conversation history (by deleting the ACS thread) and start a fresh session.

5.4. Review Output:

The AI's chat responses appear directly in the chat interface. The generated summary appears in the right-hand column after you click the button. Any errors during operation should be displayed within the Streamlit interface. You can also check the terminal where you ran the streamlit run command for any server-side print statements or error logs.

6. Conclusion

You've now walked through the process of setting up Azure resources and using Streamlit to build a contact center chat analysis solution! By leveraging Azure Communication Services to access chat data and Azure OpenAI Service for intelligent analysis, you can automate the extraction of valuable insights from user or customer interactions, moving from overwhelming data volume to clear, actionable intelligence.

7. Resources

Updated Apr 12, 2025
Version 1.0
No CommentsBe the first to comment
"}},"componentScriptGroups({\"componentId\":\"custom.widget.MicrosoftFooter\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/ranks/UserRankLabel\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserRegistrationDate\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserRegistrationDate-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeDescription\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeIcon\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1745505307000"}]},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Deleted","email":"","avatar":null,"rank":null,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":"ANONYMOUS","registrationTime":null,"confirmEmailStatus":false,"registrationAccessLevel":"VIEW","ssoRegistrationFields":[]},"ssoId":null,"profileSettings":{"__typename":"ProfileSettings","dateDisplayStyle":{"__typename":"InheritableStringSettingWithPossibleValues","key":"layout.friendly_dates_enabled","value":"false","localValue":"true","possibleValues":["true","false"]},"dateDisplayFormat":{"__typename":"InheritableStringSetting","key":"layout.format_pattern_date","value":"MMM dd yyyy","localValue":"MM-dd-yyyy"},"language":{"__typename":"InheritableStringSettingWithPossibleValues","key":"profile.language","value":"en-US","localValue":null,"possibleValues":["en-US","es-ES"]},"repliesSortOrder":{"__typename":"InheritableStringSettingWithPossibleValues","key":"config.user_replies_sort_order","value":"DEFAULT","localValue":"DEFAULT","possibleValues":["DEFAULT","LIKES","PUBLISH_TIME","REVERSE_PUBLISH_TIME"]}},"deleted":false},"CachedAsset:pages-1747123677058":{"__typename":"CachedAsset","id":"pages-1747123677058","value":[{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"UserBlogPermissions.Page","type":"COMMUNITY","urlPath":"/c/user-blog-permissions/page","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"AllEvents","type":"CUSTOM","urlPath":"/Events","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"CommunityHub.Page","type":"CUSTOM","urlPath":"/Directory","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"AllBlogs.Page","type":"CUSTOM","urlPath":"/blogs","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"HealthCheckPage","type":"COMMUNITY","urlPath":"/health","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1747123677058,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"}],"localOverride":false},"CachedAsset:text:en_US-components/context/AppContext/AppContextProvider-0":{"__typename":"CachedAsset","id":"text:en_US-components/context/AppContext/AppContextProvider-0","value":{"noCommunity":"Cannot find community","noUser":"Cannot find current user","noNode":"Cannot find node with id {nodeId}","noMessage":"Cannot find message with id {messageId}","userBanned":"We're sorry, but you have been banned from using this site.","userBannedReason":"You have been banned for the following reason: {reason}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-0":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-0","value":{"title":"Loading..."},"localOverride":false},"Rank:rank:37":{"__typename":"Rank","id":"rank:37","position":18,"name":"Copper Contributor","color":"333333","icon":null,"rankStyle":"TEXT"},"User:user:2612220":{"__typename":"User","id":"user:2612220","uid":2612220,"login":"PascalBurume08","deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/m_assets/avatars/default/avatar-12.svg?time=0"},"rank":{"__ref":"Rank:rank:37"},"email":"","messagesCount":3,"biography":null,"topicsCount":3,"kudosReceivedCount":4,"kudosGivenCount":0,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2024-08-06T23:21:15.969-07:00","confirmEmailStatus":null},"followersCount":null,"solutionsCount":0},"Category:category:EducationSector":{"__typename":"Category","id":"category:EducationSector","entityType":"CATEGORY","displayId":"EducationSector","nodeType":"category","depth":3,"title":"Education Sector","shortTitle":"Education Sector","parent":{"__ref":"Category:category:solutions"},"categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:top":{"__typename":"Category","id":"category:top","entityType":"CATEGORY","displayId":"top","nodeType":"category","depth":0,"title":"Top","shortTitle":"Top"},"Category:category:communities":{"__typename":"Category","id":"category:communities","entityType":"CATEGORY","displayId":"communities","nodeType":"category","depth":1,"parent":{"__ref":"Category:category:top"},"title":"Communities","shortTitle":"Communities"},"Category:category:solutions":{"__typename":"Category","id":"category:solutions","entityType":"CATEGORY","displayId":"solutions","nodeType":"category","depth":2,"parent":{"__ref":"Category:category:communities"},"title":"Topics","shortTitle":"Topics"},"Blog:board:EducatorDeveloperBlog":{"__typename":"Blog","id":"board:EducatorDeveloperBlog","entityType":"BLOG","displayId":"EducatorDeveloperBlog","nodeType":"board","depth":4,"conversationStyle":"BLOG","repliesProperties":{"__typename":"RepliesProperties","sortOrder":"REVERSE_PUBLISH_TIME","repliesFormat":"threaded"},"tagProperties":{"__typename":"TagNodeProperties","tagsEnabled":{"__typename":"PolicyResult","failureReason":null}},"requireTags":false,"tagType":"FREEFORM_ONLY","description":"","title":"Educator Developer Blog","shortTitle":"Educator Developer Blog","parent":{"__ref":"Category:category:EducationSector"},"ancestors":{"__typename":"CoreNodeConnection","edges":[{"__typename":"CoreNodeEdge","node":{"__ref":"Community:community:gxcuf89792"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:communities"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:solutions"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:EducationSector"}}]},"userContext":{"__typename":"NodeUserContext","canAddAttachments":false,"canUpdateNode":false,"canPostMessages":false,"isSubscribed":false},"theme":{"__ref":"Theme:customTheme1"},"boardPolicies":{"__typename":"BoardPolicies","canViewSpamDashBoard":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.access_spam_quarantine.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.access_spam_quarantine.allowed.accessDenied","args":[]}},"canArchiveMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.content_archivals.enable_content_archival_settings.accessDenied","key":"error.lithium.policies.content_archivals.enable_content_archival_settings.accessDenied","args":[]}},"canPublishArticleOnCreate":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","args":[]}}}},"BlogTopicMessage:message:4403710":{"__typename":"BlogTopicMessage","uid":4403710,"subject":"Step-by-Step Contact Center Chat Analysis with Azure OpenAI & Communication Services","id":"message:4403710","revisionNum":9,"repliesCount":0,"author":{"__ref":"User:user:2612220"},"depth":0,"hasGivenKudo":false,"board":{"__ref":"Blog:board:EducatorDeveloperBlog"},"conversation":{"__ref":"Conversation:conversation:4403710"},"messagePolicies":{"__typename":"MessagePolicies","canPublishArticleOnEdit":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","args":[]}},"canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}},"contentWorkflow":{"__typename":"ContentWorkflow","state":"PUBLISH","scheduledPublishTime":null,"scheduledTimezone":null,"userContext":{"__typename":"MessageWorkflowContext","canSubmitForReview":null,"canEdit":false,"canRecall":null,"canSubmitForPublication":null,"canReturnToAuthor":null,"canPublish":null,"canReturnToReview":null,"canSchedule":false},"shortScheduledTimezone":null},"readOnly":false,"editFrozen":false,"moderationData":{"__ref":"ModerationData:moderation_data:4403710"},"teaser":"","body":"

 

\n

1. Introduction

\n

Contact centers are the front lines of customer interaction, generating vast amounts of valuable data through chat logs, call transcripts, and emails. However, manually sifting through this data to find actionable insights is often a monumental task. Imagine the scenario of a thriving online service, like a food delivery app: as usage climbs, so does the number of customer support chats, making it incredibly difficult to pinpoint recurring problems or gauge overall satisfaction from the sea of text. How can businesses effectively tap into this wealth of information?

\n

This post explores a powerful solution: building an automated analytics platform using Azure Communication Services (ACS) combined with the intelligence of Azure OpenAI Service. We'll outline how this integration allows businesses to process, understand, and derive actionable insights from their contact center conversations at scale, transforming raw data into a strategic asset.

\n

(Why This Azure Stack for Analytics?)

\n

Combining ACS and Azure OpenAI offers a compelling approach for contact center analytics:

\n\n

What We'll Cover:

\n\n

2. Architecture

\n\n

2.1. Data Flow:

\n
    \n
  1. The user interacts with the Streamlit application.
  2. \n
  3. When instructed (e.g., by a button click or automatically), the Streamlit application uses the ACS SDK to fetch chat messages from relevant threads within the Azure Communication Services.
  4. \n
  5. The Streamlit application prepares the chat data and formulates prompts based on the desired analysis (e.g., summarize, extract topics, analyze sentiment).
  6. \n
  7. These prompts and the chat data are sent to the Azure OpenAI Service.
  8. \n
  9. Azure OpenAI processes the information using its LLMs and returns the analysis results (summary, topics, sentiment) to the Streamlit application.
  10. \n
  11. The Streamlit application receives the analysis results and formats them for display in the user interface, alongside the original chat messages.
  12. \n
\n

2.2. Prerequisites

\n

Before you begin, ensure you have the following:

\n\n

3. Setting Up Azure Resources

\n

We need two core Azure services: Azure OpenAI (for the AI model) and Azure Communication Services (to access chat data).

\n

3.1 Azure OpenAI Service

\n\n\n

3.2 Azure Communication Services (ACS)

\n

This service will store or provide access to the chat threads we want to analyze.

\n\n

On the Communication Services page, click the \"+ Create\" button. (Alternatively: Click \"+ Create a resource\", search for \"Communication Services\" in the Marketplace, select it, and click \"Create\").

\n\n\n\n

 

\n\n\n\n\n\n\n

4. Implementing the Streamlit Application Code

\n

Now, we'll build the core of our application. Create a Python file named app.py (or your preferred name) in your project folder. We will add the code in logical sections.

\n

4.1 Imports and Configuration Loading

\n

First, we import all necessary libraries and load the configuration securely from our .env file. We also define some constants for sender names.

\n
# --- Start of Streamlit App Code (app.py) ---\nimport streamlit as st\nfrom azure.communication.chat import (\n    ChatClient, CommunicationTokenCredential, ChatMessageType,\n    ChatParticipant,\n    ChatThreadClient\n)\nfrom azure.communication.identity import CommunicationIdentityClient, CommunicationUserIdentifier\nfrom azure.core.exceptions import ResourceExistsError, ResourceNotFoundError\nfrom datetime import datetime, timedelta\nimport openai\nimport re\nimport os\nimport time\nfrom dotenv import load_dotenv # Import dotenv\n\n# --- Load Environment Variables ---\nload_dotenv() # Load variables from .env file at the start\n\n# --- Configuration ---\n# Load from environment with error\nchecking\nAZURE_OPENAI_API_KEY = os.getenv(\"AZURE_OPENAI_API_KEY\")\nAZURE_OPENAI_ENDPOINT = os.getenv(\"AZURE_OPENAI_ENDPOINT\")\nAZURE_API_VERSION = os.getenv(\"AZURE_API_VERSION\",'2024-05-01-preview') # Provide\na default API version if needed\nAZURE_OPENAI_DEPLOYMENT_NAME = os.getenv(\"AZURE_OPENAI_DEPLOYMENT_NAME\") # Your\nmodel deployment name\n\nCOMMUNICATION_CONNECTION_STRING = os.getenv(\"COMMUNICATION_CONNECTION_STRING\")\nCOMMUNICATION_ENDPOINT = os.getenv(\"COMMUNICATION_ENDPOINT\")\n\n# --- Early check for critical\nenvironment variables ---\n# Stop execution if essential configs\nare missing to prevent errors later\nif not all([AZURE_OPENAI_API_KEY,\n            AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT_NAME,\n            COMMUNICATION_CONNECTION_STRING, COMMUNICATION_ENDPOINT]):\n    # Use st.error which is visible\n    in the Streamlit app\n    st.error(\"❌ Critical environment variables are missing! Please ensure AZURE_OPENAI_API_KEY,\n             AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT_NAME,\n             COMMUNICATION_CONNECTION_STRING, and COMMUNICATION_ENDPOINT are set in your\n             .env file or system environment.\")\n    st.stop() # Halt script execution if\nconfiguration is incomplete\n\n# --- Application Specific Settings ---\nTARGET_THREAD_TOPIC = \"Streamlit ACS Interactive Demo V3\" # Name for\nthe ACS chat thread\n\n# --- Constants for Sender Display Names\n---\nSENDER_USER = \"User\n(via Streamlit)\" # Display name for messages sent by the human user\nSENDER_AI_AGENT = \"AI Agent\"        #\nDisplay name for messages generated by OpenAI\nSENDER_SYSTEM = \"System\"            # Display name for automated system messages\n\n# --- Configure OpenAI library (using\nv0.28.1 syntax) ---\n# This configures the openai library\nglobally for this script run\nopenai.api_type = \"azure\"\nopenai.api_key = AZURE_OPENAI_API_KEY\nopenai.api_base = AZURE_OPENAI_ENDPOINT\nopenai.api_version = AZURE_API_VERSION\n
\n

Explanation: This section imports necessary modules from Streamlit, Azure SDKs, OpenAI, and standard Python libraries. It uses dotenv to load credentials stored in the.env file into environment variables. A crucial check is added using st.error and st.stop() to halt the app gracefully if essential configuration values are missing. Finally, it defines constants for sender display names and configures the openai library to point to your Azure OpenAI resource using the loaded credentials (note this uses syntax for openai library version 0.x).

\n

4.2 Helper Function: Parsing the AI Summary

\n

This function takes the raw text output from the OpenAI summarization call and extracts structured information using regular expressions.

\n
# --- Helper Function for Parsing Summary ---\ndef parse_summary(text):\n    \"\"\" Parses the\n    AI response for summary sections using regex. \"\"\"\n    parsed = {\n        \"Topic\": \"Not\nfound\", \"Summary\": \"Not\nfound\",\n        \"Highlights\": \"Not\nfound\", \"Sentiment\": \"Not\nfound\"\n    }\n    if not text:\n        parsed[\"Error\"] = \"AI\nreturned no text to parse.\"\n        return\nparsed\n    try:\n        # Regex patterns look for\n        Markdown headings (## Heading)\n        # re.DOTALL makes '.' match\n        newline characters within sections\n        topic_match = re.search(r\"##\\s*Topic\\s*\\n(.*?)(?=\\n##\\s*(Summary|Highlights|Sentiment)|$)\", text, re.IGNORECASE | re.DOTALL)\n        if\ntopic_match: parsed[\"Topic\"] = topic_match.group(1).strip()\n\n        summary_match = re.search(r\"##\\s*Summary\\s*\\n(.*?)(?=\\n##\\s*(Highlights|Sentiment)|$)\", text, re.IGNORECASE | re.DOTALL)\n        if\nsummary_match: parsed[\"Summary\"] = summary_match.group(1).strip()\n\n        highlights_match = re.search(r\"##\\s*Highlights\\s*\\n(.*?)(?=\\n##\\s*Sentiment|$)\", text, re.IGNORECASE | re.DOTALL)\n        if\nhighlights_match: parsed[\"Highlights\"] = highlights_match.group(1).strip()\n\n        sentiment_match = re.search(r\"##\\s*Sentiment\\s*\\n(.*)\", text, re.IGNORECASE | re.DOTALL)\n        if\nsentiment_match: parsed[\"Sentiment\"] = sentiment_match.group(1).strip()\n\n        # Check if parsing failed\n        completely or partially\n        if all(v == \"Not found\" for k, v in\nparsed.items() if\nk not in [\"RawOutput\",\"Error\", \"Warning\"]):\n            parsed[\"Error\"] = \"Could\nnot parse specific sections. Displaying raw output.\"\n            parsed[\"RawOutput\"] = text # Include\nraw output if parsing fails\n        elif any(v == \"Not found\" for k, v in\nparsed.items() if\nk not in [\"RawOutput\",\"Error\", \"Warning\"]):\n            parsed[\"Warning\"] = \"AI\nresponse formatting inconsistent; some sections might be missing.\"\n\n    except\nException as\ne:\n        st.exception(f\"Error during summary\nparsing: {e}\") # Log full\nerror\n        parsed[\"Error\"] = f\"An\nerror occurred during parsing. Raw response:\\n{text}\"\n        parsed[\"RawOutput\"] = text\n        parsed[\"Topic\"], parsed[\"Summary\"], parsed[\"Highlights\"], parsed[\"Sentiment\"] = \"Error\", \"Error\", \"Error\", \"Error\"\n    return\nparsed\n
\n

Explanation: The parse_summary function takes the text generated by the AI summarization prompt. It uses regular expressions (re.search) to find sections starting with markdown headings like ## Topic. It extracts the content under each heading and stores it in a dictionary. It includes error handling for cases where the AI output doesn't match the expected format, returning an Error or Warning key in the dictionary along with the raw output if possible.

\n

4.3 Helper Function: Initializing Azure Clients and ACS Thread

\n

This is a critical function that sets up connections to Azure services and creates the specific chat thread for the application session. It's decorated with st.cache_resource to ensure it only runs once per session, improving performance and preventing accidental resource recreation.

\n
# --- Initialize Clients and Thread (Cached Resource)\n---\n@st.cache_resource # IMPORTANT: Caches the returned objects for the\nsession\ndef initialize_azure_clients_and_thread():\n    \"\"\"\n    Initializes Azure OpenAI & ACS clients, creates the chat thread for\n    this session.\n    Returns a dictionary containing clients, thread_id, and error status.\n    Cached by Streamlit to run only once per session unless cache is\n    cleared.\n    \"\"\"\n    initialization_details = {\n        \"chat_client\": None, \"chat_thread_client\": None,\n        \"thread_id\": None, \"error\": None\n    }\n    try:\n        # --- (Re)Configure OpenAI\n        library within function scope if needed, ---\n        # --- though global config\n        might suffice if not changing keys ---\n        # openai.api_key =\n        AZURE_OPENAI_API_KEY\n        # ... (rest of openai config)\n...\n\n        # --- ACS Setup: Identity\n        Client, User, Token ---\n        st.sidebar.text(\"Initializing\n        ACS Identity...\")\n        identity_client =\n        CommunicationIdentityClient.from_connection_string(COMMUNICATION_CONNECTION_STRING)\n        user_streamlit = identity_client.create_user()\n        token_streamlit = identity_client.get_token(user_streamlit, [\"chat\"])\n        st.sidebar.text(\"ACS\n        Identity Ready.\")\n\n        # --- Define Participant for\n        the Thread ---\n        participant_streamlit_user = ChatParticipant(\n            identifier=user_streamlit,\n            display_name=SENDER_USER,\n            share_history_time=datetime.utcnow() - timedelta(days=1)\n        )\n        participants = [participant_streamlit_user]\n\n        # --- Create base ACS Chat\n        Client ---\n        st.sidebar.text(\"Initializing\n        ACS Chat Client...\")\n        chat_client_streamlit = ChatClient(COMMUNICATION_ENDPOINT,\n        CommunicationTokenCredential(token_streamlit.token))\n        initialization_details[\"chat_client\"] = chat_client_streamlit\n        st.sidebar.text(\"ACS\n        Chat Client Ready.\")\n\n        # --- Create the ACS Chat\n        Thread ---\n        thread_id = None\n        st.sidebar.text(f\"Creating\n        ACS Thread ('{TARGET_THREAD_TOPIC}')...\")\n        try:\n            create_thread_result =\n            chat_client_streamlit.create_chat_thread(\n                topic=TARGET_THREAD_TOPIC,\n                thread_participants=participants\n            )\n            thread_id =\n            create_thread_result.chat_thread.id\n            initialization_details[\"thread_id\"] = thread_id\n            st.sidebar.success(f\"Thread Created:\\nID: {thread_id[:12]}...\", icon=\"✅\")\n\n            #\n            --- Get Thread-Specific Client ---\n            chat_thread_client_temp =\n            chat_client_streamlit.get_chat_thread_client(thread_id)\n\n            #\n            --- Send Initial Messages ---\n            st.sidebar.text(\"Sending initial messages...\")\n            try:\n                chat_thread_client_temp.send_message(f\"Chat\n                thread '{TARGET_THREAD_TOPIC}' started.\",\n                sender_display_name=SENDER_SYSTEM)\n                time.sleep(0.5) # Small delay helps messages appear in order\n                chat_thread_client_temp.send_message(\"Hello!\n                I am your AI assistant. How can I help?\",\n                sender_display_name=SENDER_AI_AGENT)\n                st.sidebar.info(\"Initial messages sent.\", icon=\"✉️\")\n            except Exception as msg_err:\n                st.sidebar.warning(f\"Could not send initial messages: {msg_err}\", icon=\"⚠️\")\n\n        # --- Handle Thread Creation\n        Errors ---\n        except\n        ResourceExistsError:\n            st.sidebar.warning(\"Thread creation conflict\n            (ResourceExistsError).\", icon=\"⚠️\")\n            initialization_details[\"error\"] = \"Failed - Thread conflict.\"\n            return initialization_details\n        except\n        Exception as\n        e:\n            initialization_details[\"error\"] = f\"Error during thread creation: {e}\"\n            st.exception(e)\n            return initialization_details\n\n        if not\n        thread_id:\n            initialization_details[\"error\"] = \"Thread ID is missing after\n            creation attempt.\"\n            return initialization_details\n\n        # --- Store the final\n        ChatThreadClient ---\n        chat_thread_client =\n        chat_client_streamlit.get_chat_thread_client(thread_id)\n        initialization_details[\"chat_thread_client\"] = chat_thread_client\n        st.sidebar.text(\"Initialization\n        Complete.\")\n        return\n        initialization_details\n\n    #\n    --- Handle Overall Initialization Errors ---\n    except\n    Exception as\n    e:\n        st.error(\"❌ Failed during Azure\n        client initialization process.\")\n        st.exception(e)\n        initialization_details[\"error\"] = f\"Failed\n        overall initialization: {e}\"\n        return\n        initialization_details\n
\n

Explanation: This function performs the critical setup on the first run of the session.

\n\n

4.4 Helper Function: Getting the AI Agent's Response

\n

This function encapsulates the logic for generating the AI's conversational reply.

\n
# --- Helper Function to get AI chat response ---\ndef get_ai_agent_response(thread_client:\nChatThreadClient):\n    \"\"\"Fetches\n    history from ACS, formats it, calls OpenAI, returns AI response\n    text.\"\"\"\n    if not\n    thread_client:\n        st.error(\"Cannot get AI response:\n        ChatThreadClient is not initialized.\")\n        return \"[Error: Chat client not available]\"\n    try:\n        # 1. Fetch recent message\n        history from ACS thread\n        messages_for_prompt = []\n        history_limit = 20 # Limit\n        context window\n        message_iterator =\n        thread_client.list_messages(results_per_page=history_limit)\n        acs_messages = list(message_iterator)\n        acs_messages.reverse() # Process\n        oldest first for context\n\n        # 2. Format messages into\n        OpenAI ChatCompletion structure\n        for\n        msg in\n        acs_messages:\n            sender_name =\n            msg.sender_display_name\n            content = msg.content.message if\n            msg.content and\n            msg.content.message else \"\"\n            if not content: continue # Skip\n            empty messages\n\n            #\n            Map sender to OpenAI role (user/assistant)\n            if sender_name == SENDER_AI_AGENT: role = \"assistant\"\n            elif sender_name == SENDER_USER: role = \"user\"\n            else: role = \"user\"; content = f\"[{sender_name}]: {content}\" # Treat\n            others as user context\n\n            messages_for_prompt.append({\"role\":\n            role, \"content\": content})\n\n        if not\n        messages_for_prompt: # Should\n        not happen after init, but safeguard\n            return \"Hello!\n            How may I assist you?\"\n\n        # 3. Define AI's persona via\n        system message\n        system_message = {\"role\": \"system\", \"content\": \"You\n        are a helpful and concise AI Agent providing support. Respond naturally to the\n        user's last message based on the conversation history.\"}\n        openai_messages = [system_message] + messages_for_prompt\n\n        # 4. Call Azure OpenAI API\n        response = openai.ChatCompletion.create(\n            engine=AZURE_OPENAI_DEPLOYMENT_NAME,\n            messages=openai_messages,\n            max_tokens=150,\n            temperature=0.7\n        )\n        ai_response_text = response['choices'][0]['message']['content']\n        return\n        ai_response_text.strip()\n\n    except\n    Exception as\n    e:\n        st.error(\"Error getting AI\n        response:\")\n        st.exception(e) # Log full\n        error\n        return \"[Error occurred while generating AI response.\n        Please check logs.]\"\n
\n

Explanation: This function is called whenever the AI needs to generate a reply. It fetches the recent conversation history from the ACS thread using the provided thread_client, formats it into the [{role: 'user'/'assistant', content: '...'}] structure required by the OpenAI Chat Completion API, adds a system message to guide the AI's behavior, calls the API, and returns the AI's generated text response.

\n

4.5 Streamlit App Structure and Initialization

\n

This sets up the main page configuration and title and calls the initialization function.

\n
# --- Streamlit App ---\nst.set_page_config(layout=\"wide\",\npage_title=\"AI Chat & Summary\n(ACS + OpenAI)\")\nst.title(\"💬\nInteractive AI Agent Chat & Summarizer\")\nst.markdown(f\"Using\n**Azure Communication Services** & **Azure OpenAI**. Date: {datetime.now().strftime('%Y-%m-%d')}\")\nst.info(\"Type\na message, click 'Send'. AI Agent responds. Use sidebar to clear/reset. Use\nsummary button for analysis.\")\n\n# --- Initialize Clients and Thread ---\n# This calls the cached function defined\nearlier\ninit_details =\ninitialize_azure_clients_and_thread()\nchat_client = init_details.get(\"chat_client\")\nchat_thread_client = init_details.get(\"chat_thread_client\")\nthread_id = init_details.get(\"thread_id\")\ninit_error = init_details.get(\"error\")\n\n# --- Sidebar ---\nst.sidebar.title(\"Controls\")\n# Sidebar button logic to clear chat\nif\nst.sidebar.button(\"🧹 Clear Chat\n& Start New Thread\"):\n    if\n    chat_client and\n    thread_id:\n        with\n        st.spinner(\"Clearing chat\n        thread...\"):\n            try:\n                print(f\"Attempting\n                to delete thread: {thread_id}\") # Server log\n                chat_client.delete_chat_thread(thread_id=thread_id)\n                st.sidebar.success(\"Previous thread deleted.\")\n            except ResourceNotFoundError:\n                st.sidebar.warning(\"Thread already deleted or not found.\")\n            except Exception as e:\n                st.sidebar.error(f\"Error deleting thread: {e}\")\n                st.exception(e)\n\n            #\n            Clear state, cache, and rerun\n            st.session_state.clear()\n            initialize_azure_clients_and_thread.clear() #\n            Clear cache\n            st.rerun() #\n            Restart script execution\n    else:\n        st.sidebar.error(\"Cannot\n        clear chat - client/thread not initialized.\")\n\n\n# --- Initialize Session State Variables\n(Safety check) ---\n# Ensures these keys exist after\npotential clearing\nif 'current_summary' not in\nst.session_state: st.session_state.current_summary = None\nif 'messages' not in\nst.session_state: st.session_state.messages = []\nif 'send_button_clicked' not in\nst.session_state: st.session_state.send_button_clicked = False\n
\n

Explanation: This section configures the web page (title, layout), displays introductory text, and then calls the cached initialize_azure_clients_and_thread function to get the necessary client objects and thread ID. It defines the sidebar, including the \"Clear Chat\" button which deletes the ACS thread, clears session state and the resource cache, then forces a rerun. Finally, it ensures essential keys exist in Streamlit's session_state dictionary.

\n

4.6 Main Application Logic and UI

\n

This is the core part that runs only if the initialization is successful. It defines the layout and handles the interactive elements.

\n
# --- Main Application Logic ---\n# Only proceed if initialization was\nsuccessful (clients/thread ready)\nif\nchat_thread_client and not\ninit_error:\n\n    # Define layout using columns\n    (Chat on left, Summary on right)\n    col1, col2 = st.columns([0.6, 0.4])\n\n    # --- Column 1: Live Chat\n    Interface ---\n    with\n    col1:\n        st.subheader(f\"💬 Live Chat: {TARGET_THREAD_TOPIC}\")\n        st.markdown(\"---\")\n\n        # --- Display Chat Messages ---\n        message_container = st.container(height=600) #\n        Scrollable container\n        try:\n            #\n            Fetch latest messages from ACS on every script run\n            chat_messages_iterator =\n            chat_thread_client.list_messages(results_per_page=50)\n            live_messages = list(chat_messages_iterator)\n            #\n            Store messages in session state for potential use by summary function\n            st.session_state.messages =\n            live_messages\n\n            #\n            Render messages within the container\n            with message_container:\n                if not live_messages:\n                    st.info(\"No messages yet. Send a message to start!\")\n                else:\n                    #\n                    Display messages, newest at the bottom\n                    for msg in reversed(live_messages):\n                        sender_name =\n                        msg.sender_display_name or \"Unknown\"\n                        # Assign avatars based on sender name constants\n                        if\n                        sender_name == SENDER_AI_AGENT: avatar = \"🤖\"\n                        elif\n                        sender_name == SENDER_USER: avatar = \"👤\"\n                        elif\n                        sender_name == SENDER_SYSTEM: avatar = \"⚙️\"\n                        else: avatar =\"🧑‍💻\" # Default\n\n                        # Use Streamlit's chat elements for display\n                        with\n                        st.chat_message(name=sender_name, avatar=avatar):\n                            st.caption(f\"{msg.created_on.strftime('%Y-%m-%d %H:%M:%S')}\") # Timestamp\n                            st.write(msg.content.message or \"*empty\n                            message*\") #\n                            Message content\n\n        except\n        Exception as\n        e: # Handle errors fetching messages\n            with message_container:\n                st.error(\"Error fetching messages from ACS:\")\n                st.exception(e)\n\n        st.markdown(\"---\") #\n        Separator\n\n        # --- Send Message Input Form\n        ---\n        # Use a form to group input and\n        button click\n        with\n        st.form(key=\"send_message_form\", clear_on_submit=True):\n            new_message_content =\n            st.text_input(\"Your Message:\", key=\"new_message_input\", placeholder=\"Type\n            your message here...\")\n            submitted = st.form_submit_button(\"Send Message\")\n            if submitted:\n                st.session_state.send_button_clicked = True # Flag\n                submission\n\n        # --- Process message sending\n        (Runs after form check) ---\n        if\n        st.session_state.send_button_clicked:\n            st.session_state.send_button_clicked = False # Reset\n            flag\n\n            if new_message_content: #\n            Check if user actually typed something\n                #\n                Show spinner during processing\n                with st.spinner(\"Sending\n                message and getting AI response...\"):\n                    try:\n                        # 1. Send User message to ACS\n                        chat_thread_client.send_message(new_message_content,\n                        sender_display_name=SENDER_USER)\n                        # 2. Get AI response (calls helper function)\n                        ai_reply =\n                        get_ai_agent_response(chat_thread_client)\n                        # 3. Send AI response back to ACS\n                        if ai_reply:\n                            chat_thread_client.send_message(ai_reply, sender_display_name=SENDER_AI_AGENT)\n                        # 4. Rerun script to refresh chat display\n                        st.rerun()\n                    except Exception as e: # Handle\n                    errors during send/response cycle\n                        st.error(\"Error during send/response cycle:\")\n                        st.exception(e)\n            else:\n                st.warning(\"Please enter a message to send.\")\n\n\n    # --- Column 2: AI Summary Area\n    ---\n    with\n    col2:\n        st.subheader(\"✨ Conversation Summary\")\n        st.markdown(\"---\")\n\n        summary_error_message = None #\n        Placeholder for specific summary errors\n\n        # --- Generate Summary Button\n        and Logic ---\n        if\n        st.button(\"Generate Full\n        Conversation Summary\", key=\"generate_summary_btn\"):\n            st.session_state.summary_processing = True # Flag\n            processing\n            st.session_state.current_summary =\n            {\"Info\":\"Processing...\"} # Show\n            status\n\n            #\n            Use messages already fetched and stored in session state\n            fetched_messages =\n            st.session_state.get('messages', [])\n\n            if fetched_messages:\n                prompt_text = \"\"\n                #\n                Build prompt text from stored messages (oldest first)\n                for msg in reversed(fetched_messages):\n                    #\n                    ... (build prompt_text, skipping empty content) ...\n                    sender_name =\n                    msg.sender_display_name if msg.sender_display_name else \"Unknown\"\n                    content =\n                    msg.content.message if\n                    msg.content and\n                    msg.content.message else \"\"\n                    if content: prompt_text += f\"{sender_name}: {content}\\n\"\n\n\n                if not prompt_text.strip():\n                    st.session_state.current_summary = {\"Info\": \"No\n                    message content to summarize.\"}\n                else:\n                    #\n                    Prepare prompt with instructions\n                    instruction_phrase = 'Summarize the following conversation. Extract the\n                    main topic, a brief summary, key highlights (1-3 bullet points), and overall\n                    sentiment. Format clearly with headings: ## Topic, ## Summary, ## Highlights,\n                    ## Sentiment.\\n\\n'\n                    full_prompt =\n                    instruction_phrase + prompt_text.strip()\n\n                    #\n                    Call OpenAI within a spinner and try/except block\n                    with st.spinner(\"Generating\n                    summary...\"):\n                        try:\n                            response =\n                            openai.ChatCompletion.create( # Or\n                            Completion API\n                                engine=AZURE_OPENAI_DEPLOYMENT_NAME,\n                                messages=[{\"role\":\"user\",\"content\": full_prompt}],\n                                max_tokens=1024,\n                                temperature=0.2\n                            )\n                            summary_text =\n                            response['choices'][0]['message']['content']\n                            # Parse and store result\n                            st.session_state.current_summary = parse_summary(summary_text)\n                        # Handle various OpenAI/other errors\n                        except\n                        openai.error.AuthenticationError as e: ... # Set\n                        Error state\n                        # ... other except blocks ...\n                        except Exceptionas\n                        e: ... # Set Error state\n\n            else: # No\n            messages fetched/stored\n                st.session_state.current_summary = {\"Info\": \"Empty\n                conversation history.\"}\n\n            st.session_state.summary_processing = False # Unflag\n            processing\n            st.rerun() #\n            Rerun to update the summary display area\n\n\n        # --- Display Summary (or\n        Status) ---\n        # Checks the state variable and\n        displays Error, Warning, Info, or the parsed summary\n        if\n        st.session_state.get('current_summary'):\n            summary_data =\n            st.session_state.current_summary\n            #\n            --- Handle Error State ---\n            if isinstance(summary_data, dict) and \"Error\" in summary_data: ... #\n            Display st.error(...)\n            #\n            --- Handle Warning State ---\n            elif isinstance(summary_data, dict) and \"Warning\" in summary_data: ... #\n            Display st.warning(...) and partial data\n            #\n            --- Handle Info State ---\n            elif isinstance(summary_data, dict) and \"Info\" in summary_data: ... #\n            Display st.info(...)\n            #\n            --- Handle Successful Summary Display ---\n            elif isinstance(summary_data, dict): ... # Display\n            formatted markdown sections\n            #\n            --- Fallback ---\n            else: st.error(\"Summary\n            data unexpected format.\")\n        # --- Default Placeholder Text\n        ---\n        elif not\n        st.session_state.get('summary_processing', False):\n            st.info(\"Click\n            'Generate Summary' for an analysis...\")\n\n\n# --- Handle Initialization Errors ---\n# This runs if the main 'if\nchat_thread_client...' block is False\nelif\ninit_error:\n    st.error(f\"❌ Application cannot\n    start due to initialization errors:\")\n    st.error(init_error) # Display\n    the error caught during initialization\nelse:\n    # Fallback if client is None\n    but no specific error was caught\n    st.warning(\"⏳ Initializing Azure\n    services or waiting for resources...\")\n\n# --- End of Streamlit App Code ---\n
\n

Explanation: This is the main part of the application that runs if the initial Azure setup was successful.

\n\n

5. Running the Streamlit Application

\n

With your Azure resources configured, the Python environment set up, and the code saved in app.py (or your chosen filename), you are ready to run the interactive application.

\n

5.1. Run the App:

\n

Open your terminal or command prompt, make sure you are in your project directory (where app.py and .env are located), and execute the command:

\n
streamlit run app.py
\n

5.2. Initial Loading State:

\n

Streamlit will start its server and provide you with a local URL (usually http://localhost:8501). When you open this URL in your web browser, you will first see the application's initial loading state, similar to this image:

\n\n\n

5.3. Interact with the App:

\n\n

5.4. Review Output:

\n

The AI's chat responses appear directly in the chat interface. The generated summary appears in the right-hand column after you click the button. Any errors during operation should be displayed within the Streamlit interface. You can also check the terminal where you ran the streamlit run command for any server-side print statements or error logs.

\n\n

6. Conclusion

\n

You've now walked through the process of setting up Azure resources and using Streamlit to build a contact center chat analysis solution! By leveraging Azure Communication Services to access chat data and Azure OpenAI Service for intelligent analysis, you can automate the extraction of valuable insights from user or customer interactions, moving from overwhelming data volume to clear, actionable intelligence.

\n

7. Resources

\n","body@stringLength":"47082","rawBody":"

 

\n

1. Introduction

\n

Contact centers are the front lines of customer interaction, generating vast amounts of valuable data through chat logs, call transcripts, and emails. However, manually sifting through this data to find actionable insights is often a monumental task. Imagine the scenario of a thriving online service, like a food delivery app: as usage climbs, so does the number of customer support chats, making it incredibly difficult to pinpoint recurring problems or gauge overall satisfaction from the sea of text. How can businesses effectively tap into this wealth of information?

\n

This post explores a powerful solution: building an automated analytics platform using Azure Communication Services (ACS) combined with the intelligence of Azure OpenAI Service. We'll outline how this integration allows businesses to process, understand, and derive actionable insights from their contact center conversations at scale, transforming raw data into a strategic asset.

\n

(Why This Azure Stack for Analytics?)

\n

Combining ACS and Azure OpenAI offers a compelling approach for contact center analytics:

\n\n

What We'll Cover:

\n\n

2. Architecture

\n\n

2.1. Data Flow:

\n
    \n
  1. The user interacts with the Streamlit application.
  2. \n
  3. When instructed (e.g., by a button click or automatically), the Streamlit application uses the ACS SDK to fetch chat messages from relevant threads within the Azure Communication Services.
  4. \n
  5. The Streamlit application prepares the chat data and formulates prompts based on the desired analysis (e.g., summarize, extract topics, analyze sentiment).
  6. \n
  7. These prompts and the chat data are sent to the Azure OpenAI Service.
  8. \n
  9. Azure OpenAI processes the information using its LLMs and returns the analysis results (summary, topics, sentiment) to the Streamlit application.
  10. \n
  11. The Streamlit application receives the analysis results and formats them for display in the user interface, alongside the original chat messages.
  12. \n
\n

2.2. Prerequisites

\n

Before you begin, ensure you have the following:

\n\n

3. Setting Up Azure Resources

\n

We need two core Azure services: Azure OpenAI (for the AI model) and Azure Communication Services (to access chat data).

\n

3.1 Azure OpenAI Service

\n\n\n

3.2 Azure Communication Services (ACS)

\n

This service will store or provide access to the chat threads we want to analyze.

\n\n

On the Communication Services page, click the \"+ Create\" button. (Alternatively: Click \"+ Create a resource\", search for \"Communication Services\" in the Marketplace, select it, and click \"Create\").

\n\n\n\n

 

\n\n\n\n\n\n\n

4. Implementing the Streamlit Application Code

\n

Now, we'll build the core of our application. Create a Python file named app.py (or your preferred name) in your project folder. We will add the code in logical sections.

\n

4.1 Imports and Configuration Loading

\n

First, we import all necessary libraries and load the configuration securely from our .env file. We also define some constants for sender names.

\n
# --- Start of Streamlit App Code (app.py) ---\nimport streamlit as st\nfrom azure.communication.chat import (\n    ChatClient, CommunicationTokenCredential, ChatMessageType,\n    ChatParticipant,\n    ChatThreadClient\n)\nfrom azure.communication.identity import CommunicationIdentityClient, CommunicationUserIdentifier\nfrom azure.core.exceptions import ResourceExistsError, ResourceNotFoundError\nfrom datetime import datetime, timedelta\nimport openai\nimport re\nimport os\nimport time\nfrom dotenv import load_dotenv # Import dotenv\n\n# --- Load Environment Variables ---\nload_dotenv() # Load variables from .env file at the start\n\n# --- Configuration ---\n# Load from environment with error\nchecking\nAZURE_OPENAI_API_KEY = os.getenv(\"AZURE_OPENAI_API_KEY\")\nAZURE_OPENAI_ENDPOINT = os.getenv(\"AZURE_OPENAI_ENDPOINT\")\nAZURE_API_VERSION = os.getenv(\"AZURE_API_VERSION\",'2024-05-01-preview') # Provide\na default API version if needed\nAZURE_OPENAI_DEPLOYMENT_NAME = os.getenv(\"AZURE_OPENAI_DEPLOYMENT_NAME\") # Your\nmodel deployment name\n\nCOMMUNICATION_CONNECTION_STRING = os.getenv(\"COMMUNICATION_CONNECTION_STRING\")\nCOMMUNICATION_ENDPOINT = os.getenv(\"COMMUNICATION_ENDPOINT\")\n\n# --- Early check for critical\nenvironment variables ---\n# Stop execution if essential configs\nare missing to prevent errors later\nif not all([AZURE_OPENAI_API_KEY,\n            AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT_NAME,\n            COMMUNICATION_CONNECTION_STRING, COMMUNICATION_ENDPOINT]):\n    # Use st.error which is visible\n    in the Streamlit app\n    st.error(\"❌ Critical environment variables are missing! Please ensure AZURE_OPENAI_API_KEY,\n             AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT_NAME,\n             COMMUNICATION_CONNECTION_STRING, and COMMUNICATION_ENDPOINT are set in your\n             .env file or system environment.\")\n    st.stop() # Halt script execution if\nconfiguration is incomplete\n\n# --- Application Specific Settings ---\nTARGET_THREAD_TOPIC = \"Streamlit ACS Interactive Demo V3\" # Name for\nthe ACS chat thread\n\n# --- Constants for Sender Display Names\n---\nSENDER_USER = \"User\n(via Streamlit)\" # Display name for messages sent by the human user\nSENDER_AI_AGENT = \"AI Agent\"        #\nDisplay name for messages generated by OpenAI\nSENDER_SYSTEM = \"System\"            # Display name for automated system messages\n\n# --- Configure OpenAI library (using\nv0.28.1 syntax) ---\n# This configures the openai library\nglobally for this script run\nopenai.api_type = \"azure\"\nopenai.api_key = AZURE_OPENAI_API_KEY\nopenai.api_base = AZURE_OPENAI_ENDPOINT\nopenai.api_version = AZURE_API_VERSION\n
\n

Explanation: This section imports necessary modules from Streamlit, Azure SDKs, OpenAI, and standard Python libraries. It uses dotenv to load credentials stored in the.env file into environment variables. A crucial check is added using st.error and st.stop() to halt the app gracefully if essential configuration values are missing. Finally, it defines constants for sender display names and configures the openai library to point to your Azure OpenAI resource using the loaded credentials (note this uses syntax for openai library version 0.x).

\n

4.2 Helper Function: Parsing the AI Summary

\n

This function takes the raw text output from the OpenAI summarization call and extracts structured information using regular expressions.

\n
# --- Helper Function for Parsing Summary ---\ndef parse_summary(text):\n    \"\"\" Parses the\n    AI response for summary sections using regex. \"\"\"\n    parsed = {\n        \"Topic\": \"Not\nfound\", \"Summary\": \"Not\nfound\",\n        \"Highlights\": \"Not\nfound\", \"Sentiment\": \"Not\nfound\"\n    }\n    if not text:\n        parsed[\"Error\"] = \"AI\nreturned no text to parse.\"\n        return\nparsed\n    try:\n        # Regex patterns look for\n        Markdown headings (## Heading)\n        # re.DOTALL makes '.' match\n        newline characters within sections\n        topic_match = re.search(r\"##\\s*Topic\\s*\\n(.*?)(?=\\n##\\s*(Summary|Highlights|Sentiment)|$)\", text, re.IGNORECASE | re.DOTALL)\n        if\ntopic_match: parsed[\"Topic\"] = topic_match.group(1).strip()\n\n        summary_match = re.search(r\"##\\s*Summary\\s*\\n(.*?)(?=\\n##\\s*(Highlights|Sentiment)|$)\", text, re.IGNORECASE | re.DOTALL)\n        if\nsummary_match: parsed[\"Summary\"] = summary_match.group(1).strip()\n\n        highlights_match = re.search(r\"##\\s*Highlights\\s*\\n(.*?)(?=\\n##\\s*Sentiment|$)\", text, re.IGNORECASE | re.DOTALL)\n        if\nhighlights_match: parsed[\"Highlights\"] = highlights_match.group(1).strip()\n\n        sentiment_match = re.search(r\"##\\s*Sentiment\\s*\\n(.*)\", text, re.IGNORECASE | re.DOTALL)\n        if\nsentiment_match: parsed[\"Sentiment\"] = sentiment_match.group(1).strip()\n\n        # Check if parsing failed\n        completely or partially\n        if all(v == \"Not found\" for k, v in\nparsed.items() if\nk not in [\"RawOutput\",\"Error\", \"Warning\"]):\n            parsed[\"Error\"] = \"Could\nnot parse specific sections. Displaying raw output.\"\n            parsed[\"RawOutput\"] = text # Include\nraw output if parsing fails\n        elif any(v == \"Not found\" for k, v in\nparsed.items() if\nk not in [\"RawOutput\",\"Error\", \"Warning\"]):\n            parsed[\"Warning\"] = \"AI\nresponse formatting inconsistent; some sections might be missing.\"\n\n    except\nException as\ne:\n        st.exception(f\"Error during summary\nparsing: {e}\") # Log full\nerror\n        parsed[\"Error\"] = f\"An\nerror occurred during parsing. Raw response:\\n{text}\"\n        parsed[\"RawOutput\"] = text\n        parsed[\"Topic\"], parsed[\"Summary\"], parsed[\"Highlights\"], parsed[\"Sentiment\"] = \"Error\", \"Error\", \"Error\", \"Error\"\n    return\nparsed\n
\n

Explanation: The parse_summary function takes the text generated by the AI summarization prompt. It uses regular expressions (re.search) to find sections starting with markdown headings like ## Topic. It extracts the content under each heading and stores it in a dictionary. It includes error handling for cases where the AI output doesn't match the expected format, returning an Error or Warning key in the dictionary along with the raw output if possible.

\n

4.3 Helper Function: Initializing Azure Clients and ACS Thread

\n

This is a critical function that sets up connections to Azure services and creates the specific chat thread for the application session. It's decorated with .cache_resource to ensure it only runs once per session, improving performance and preventing accidental resource recreation.

\n
# --- Initialize Clients and Thread (Cached Resource)\n---\n@st.cache_resource # IMPORTANT: Caches the returned objects for the\nsession\ndef initialize_azure_clients_and_thread():\n    \"\"\"\n    Initializes Azure OpenAI & ACS clients, creates the chat thread for\n    this session.\n    Returns a dictionary containing clients, thread_id, and error status.\n    Cached by Streamlit to run only once per session unless cache is\n    cleared.\n    \"\"\"\n    initialization_details = {\n        \"chat_client\": None, \"chat_thread_client\": None,\n        \"thread_id\": None, \"error\": None\n    }\n    try:\n        # --- (Re)Configure OpenAI\n        library within function scope if needed, ---\n        # --- though global config\n        might suffice if not changing keys ---\n        # openai.api_key =\n        AZURE_OPENAI_API_KEY\n        # ... (rest of openai config)\n...\n\n        # --- ACS Setup: Identity\n        Client, User, Token ---\n        st.sidebar.text(\"Initializing\n        ACS Identity...\")\n        identity_client =\n        CommunicationIdentityClient.from_connection_string(COMMUNICATION_CONNECTION_STRING)\n        user_streamlit = identity_client.create_user()\n        token_streamlit = identity_client.get_token(user_streamlit, [\"chat\"])\n        st.sidebar.text(\"ACS\n        Identity Ready.\")\n\n        # --- Define Participant for\n        the Thread ---\n        participant_streamlit_user = ChatParticipant(\n            identifier=user_streamlit,\n            display_name=SENDER_USER,\n            share_history_time=datetime.utcnow() - timedelta(days=1)\n        )\n        participants = [participant_streamlit_user]\n\n        # --- Create base ACS Chat\n        Client ---\n        st.sidebar.text(\"Initializing\n        ACS Chat Client...\")\n        chat_client_streamlit = ChatClient(COMMUNICATION_ENDPOINT,\n        CommunicationTokenCredential(token_streamlit.token))\n        initialization_details[\"chat_client\"] = chat_client_streamlit\n        st.sidebar.text(\"ACS\n        Chat Client Ready.\")\n\n        # --- Create the ACS Chat\n        Thread ---\n        thread_id = None\n        st.sidebar.text(f\"Creating\n        ACS Thread ('{TARGET_THREAD_TOPIC}')...\")\n        try:\n            create_thread_result =\n            chat_client_streamlit.create_chat_thread(\n                topic=TARGET_THREAD_TOPIC,\n                thread_participants=participants\n            )\n            thread_id =\n            create_thread_result.chat_thread.id\n            initialization_details[\"thread_id\"] = thread_id\n            st.sidebar.success(f\"Thread Created:\\nID: {thread_id[:12]}...\", icon=\"✅\")\n\n            #\n            --- Get Thread-Specific Client ---\n            chat_thread_client_temp =\n            chat_client_streamlit.get_chat_thread_client(thread_id)\n\n            #\n            --- Send Initial Messages ---\n            st.sidebar.text(\"Sending initial messages...\")\n            try:\n                chat_thread_client_temp.send_message(f\"Chat\n                thread '{TARGET_THREAD_TOPIC}' started.\",\n                sender_display_name=SENDER_SYSTEM)\n                time.sleep(0.5) # Small delay helps messages appear in order\n                chat_thread_client_temp.send_message(\"Hello!\n                I am your AI assistant. How can I help?\",\n                sender_display_name=SENDER_AI_AGENT)\n                st.sidebar.info(\"Initial messages sent.\", icon=\"✉️\")\n            except Exception as msg_err:\n                st.sidebar.warning(f\"Could not send initial messages: {msg_err}\", icon=\"⚠️\")\n\n        # --- Handle Thread Creation\n        Errors ---\n        except\n        ResourceExistsError:\n            st.sidebar.warning(\"Thread creation conflict\n            (ResourceExistsError).\", icon=\"⚠️\")\n            initialization_details[\"error\"] = \"Failed - Thread conflict.\"\n            return initialization_details\n        except\n        Exception as\n        e:\n            initialization_details[\"error\"] = f\"Error during thread creation: {e}\"\n            st.exception(e)\n            return initialization_details\n\n        if not\n        thread_id:\n            initialization_details[\"error\"] = \"Thread ID is missing after\n            creation attempt.\"\n            return initialization_details\n\n        # --- Store the final\n        ChatThreadClient ---\n        chat_thread_client =\n        chat_client_streamlit.get_chat_thread_client(thread_id)\n        initialization_details[\"chat_thread_client\"] = chat_thread_client\n        st.sidebar.text(\"Initialization\n        Complete.\")\n        return\n        initialization_details\n\n    #\n    --- Handle Overall Initialization Errors ---\n    except\n    Exception as\n    e:\n        st.error(\"❌ Failed during Azure\n        client initialization process.\")\n        st.exception(e)\n        initialization_details[\"error\"] = f\"Failed\n        overall initialization: {e}\"\n        return\n        initialization_details\n
\n

Explanation: This function performs the critical setup on the first run of the session.

\n\n

4.4 Helper Function: Getting the AI Agent's Response

\n

This function encapsulates the logic for generating the AI's conversational reply.

\n
# --- Helper Function to get AI chat response ---\ndef get_ai_agent_response(thread_client:\nChatThreadClient):\n    \"\"\"Fetches\n    history from ACS, formats it, calls OpenAI, returns AI response\n    text.\"\"\"\n    if not\n    thread_client:\n        st.error(\"Cannot get AI response:\n        ChatThreadClient is not initialized.\")\n        return \"[Error: Chat client not available]\"\n    try:\n        # 1. Fetch recent message\n        history from ACS thread\n        messages_for_prompt = []\n        history_limit = 20 # Limit\n        context window\n        message_iterator =\n        thread_client.list_messages(results_per_page=history_limit)\n        acs_messages = list(message_iterator)\n        acs_messages.reverse() # Process\n        oldest first for context\n\n        # 2. Format messages into\n        OpenAI ChatCompletion structure\n        for\n        msg in\n        acs_messages:\n            sender_name =\n            msg.sender_display_name\n            content = msg.content.message if\n            msg.content and\n            msg.content.message else \"\"\n            if not content: continue # Skip\n            empty messages\n\n            #\n            Map sender to OpenAI role (user/assistant)\n            if sender_name == SENDER_AI_AGENT: role = \"assistant\"\n            elif sender_name == SENDER_USER: role = \"user\"\n            else: role = \"user\"; content = f\"[{sender_name}]: {content}\" # Treat\n            others as user context\n\n            messages_for_prompt.append({\"role\":\n            role, \"content\": content})\n\n        if not\n        messages_for_prompt: # Should\n        not happen after init, but safeguard\n            return \"Hello!\n            How may I assist you?\"\n\n        # 3. Define AI's persona via\n        system message\n        system_message = {\"role\": \"system\", \"content\": \"You\n        are a helpful and concise AI Agent providing support. Respond naturally to the\n        user's last message based on the conversation history.\"}\n        openai_messages = [system_message] + messages_for_prompt\n\n        # 4. Call Azure OpenAI API\n        response = openai.ChatCompletion.create(\n            engine=AZURE_OPENAI_DEPLOYMENT_NAME,\n            messages=openai_messages,\n            max_tokens=150,\n            temperature=0.7\n        )\n        ai_response_text = response['choices'][0]['message']['content']\n        return\n        ai_response_text.strip()\n\n    except\n    Exception as\n    e:\n        st.error(\"Error getting AI\n        response:\")\n        st.exception(e) # Log full\n        error\n        return \"[Error occurred while generating AI response.\n        Please check logs.]\"\n
\n

Explanation: This function is called whenever the AI needs to generate a reply. It fetches the recent conversation history from the ACS thread using the provided thread_client, formats it into the [{role: 'user'/'assistant', content: '...'}] structure required by the OpenAI Chat Completion API, adds a system message to guide the AI's behavior, calls the API, and returns the AI's generated text response.

\n

4.5 Streamlit App Structure and Initialization

\n

This sets up the main page configuration and title and calls the initialization function.

\n
# --- Streamlit App ---\nst.set_page_config(layout=\"wide\",\npage_title=\"AI Chat & Summary\n(ACS + OpenAI)\")\nst.title(\"💬\nInteractive AI Agent Chat & Summarizer\")\nst.markdown(f\"Using\n**Azure Communication Services** & **Azure OpenAI**. Date: {datetime.now().strftime('%Y-%m-%d')}\")\nst.info(\"Type\na message, click 'Send'. AI Agent responds. Use sidebar to clear/reset. Use\nsummary button for analysis.\")\n\n# --- Initialize Clients and Thread ---\n# This calls the cached function defined\nearlier\ninit_details =\ninitialize_azure_clients_and_thread()\nchat_client = init_details.get(\"chat_client\")\nchat_thread_client = init_details.get(\"chat_thread_client\")\nthread_id = init_details.get(\"thread_id\")\ninit_error = init_details.get(\"error\")\n\n# --- Sidebar ---\nst.sidebar.title(\"Controls\")\n# Sidebar button logic to clear chat\nif\nst.sidebar.button(\"🧹 Clear Chat\n& Start New Thread\"):\n    if\n    chat_client and\n    thread_id:\n        with\n        st.spinner(\"Clearing chat\n        thread...\"):\n            try:\n                print(f\"Attempting\n                to delete thread: {thread_id}\") # Server log\n                chat_client.delete_chat_thread(thread_id=thread_id)\n                st.sidebar.success(\"Previous thread deleted.\")\n            except ResourceNotFoundError:\n                st.sidebar.warning(\"Thread already deleted or not found.\")\n            except Exception as e:\n                st.sidebar.error(f\"Error deleting thread: {e}\")\n                st.exception(e)\n\n            #\n            Clear state, cache, and rerun\n            st.session_state.clear()\n            initialize_azure_clients_and_thread.clear() #\n            Clear cache\n            st.rerun() #\n            Restart script execution\n    else:\n        st.sidebar.error(\"Cannot\n        clear chat - client/thread not initialized.\")\n\n\n# --- Initialize Session State Variables\n(Safety check) ---\n# Ensures these keys exist after\npotential clearing\nif 'current_summary' not in\nst.session_state: st.session_state.current_summary = None\nif 'messages' not in\nst.session_state: st.session_state.messages = []\nif 'send_button_clicked' not in\nst.session_state: st.session_state.send_button_clicked = False\n
\n

Explanation: This section configures the web page (title, layout), displays introductory text, and then calls the cached initialize_azure_clients_and_thread function to get the necessary client objects and thread ID. It defines the sidebar, including the \"Clear Chat\" button which deletes the ACS thread, clears session state and the resource cache, then forces a rerun. Finally, it ensures essential keys exist in Streamlit's session_state dictionary.

\n

4.6 Main Application Logic and UI

\n

This is the core part that runs only if the initialization is successful. It defines the layout and handles the interactive elements.

\n
# --- Main Application Logic ---\n# Only proceed if initialization was\nsuccessful (clients/thread ready)\nif\nchat_thread_client and not\ninit_error:\n\n    # Define layout using columns\n    (Chat on left, Summary on right)\n    col1, col2 = st.columns([0.6, 0.4])\n\n    # --- Column 1: Live Chat\n    Interface ---\n    with\n    col1:\n        st.subheader(f\"💬 Live Chat: {TARGET_THREAD_TOPIC}\")\n        st.markdown(\"---\")\n\n        # --- Display Chat Messages ---\n        message_container = st.container(height=600) #\n        Scrollable container\n        try:\n            #\n            Fetch latest messages from ACS on every script run\n            chat_messages_iterator =\n            chat_thread_client.list_messages(results_per_page=50)\n            live_messages = list(chat_messages_iterator)\n            #\n            Store messages in session state for potential use by summary function\n            st.session_state.messages =\n            live_messages\n\n            #\n            Render messages within the container\n            with message_container:\n                if not live_messages:\n                    st.info(\"No messages yet. Send a message to start!\")\n                else:\n                    #\n                    Display messages, newest at the bottom\n                    for msg in reversed(live_messages):\n                        sender_name =\n                        msg.sender_display_name or \"Unknown\"\n                        # Assign avatars based on sender name constants\n                        if\n                        sender_name == SENDER_AI_AGENT: avatar = \"🤖\"\n                        elif\n                        sender_name == SENDER_USER: avatar = \"👤\"\n                        elif\n                        sender_name == SENDER_SYSTEM: avatar = \"⚙️\"\n                        else: avatar =\"🧑‍💻\" # Default\n\n                        # Use Streamlit's chat elements for display\n                        with\n                        st.chat_message(name=sender_name, avatar=avatar):\n                            st.caption(f\"{msg.created_on.strftime('%Y-%m-%d %H:%M:%S')}\") # Timestamp\n                            st.write(msg.content.message or \"*empty\n                            message*\") #\n                            Message content\n\n        except\n        Exception as\n        e: # Handle errors fetching messages\n            with message_container:\n                st.error(\"Error fetching messages from ACS:\")\n                st.exception(e)\n\n        st.markdown(\"---\") #\n        Separator\n\n        # --- Send Message Input Form\n        ---\n        # Use a form to group input and\n        button click\n        with\n        st.form(key=\"send_message_form\", clear_on_submit=True):\n            new_message_content =\n            st.text_input(\"Your Message:\", key=\"new_message_input\", placeholder=\"Type\n            your message here...\")\n            submitted = st.form_submit_button(\"Send Message\")\n            if submitted:\n                st.session_state.send_button_clicked = True # Flag\n                submission\n\n        # --- Process message sending\n        (Runs after form check) ---\n        if\n        st.session_state.send_button_clicked:\n            st.session_state.send_button_clicked = False # Reset\n            flag\n\n            if new_message_content: #\n            Check if user actually typed something\n                #\n                Show spinner during processing\n                with st.spinner(\"Sending\n                message and getting AI response...\"):\n                    try:\n                        # 1. Send User message to ACS\n                        chat_thread_client.send_message(new_message_content,\n                        sender_display_name=SENDER_USER)\n                        # 2. Get AI response (calls helper function)\n                        ai_reply =\n                        get_ai_agent_response(chat_thread_client)\n                        # 3. Send AI response back to ACS\n                        if ai_reply:\n                            chat_thread_client.send_message(ai_reply, sender_display_name=SENDER_AI_AGENT)\n                        # 4. Rerun script to refresh chat display\n                        st.rerun()\n                    except Exception as e: # Handle\n                    errors during send/response cycle\n                        st.error(\"Error during send/response cycle:\")\n                        st.exception(e)\n            else:\n                st.warning(\"Please enter a message to send.\")\n\n\n    # --- Column 2: AI Summary Area\n    ---\n    with\n    col2:\n        st.subheader(\"✨ Conversation Summary\")\n        st.markdown(\"---\")\n\n        summary_error_message = None #\n        Placeholder for specific summary errors\n\n        # --- Generate Summary Button\n        and Logic ---\n        if\n        st.button(\"Generate Full\n        Conversation Summary\", key=\"generate_summary_btn\"):\n            st.session_state.summary_processing = True # Flag\n            processing\n            st.session_state.current_summary =\n            {\"Info\":\"Processing...\"} # Show\n            status\n\n            #\n            Use messages already fetched and stored in session state\n            fetched_messages =\n            st.session_state.get('messages', [])\n\n            if fetched_messages:\n                prompt_text = \"\"\n                #\n                Build prompt text from stored messages (oldest first)\n                for msg in reversed(fetched_messages):\n                    #\n                    ... (build prompt_text, skipping empty content) ...\n                    sender_name =\n                    msg.sender_display_name if msg.sender_display_name else \"Unknown\"\n                    content =\n                    msg.content.message if\n                    msg.content and\n                    msg.content.message else \"\"\n                    if content: prompt_text += f\"{sender_name}: {content}\\n\"\n\n\n                if not prompt_text.strip():\n                    st.session_state.current_summary = {\"Info\": \"No\n                    message content to summarize.\"}\n                else:\n                    #\n                    Prepare prompt with instructions\n                    instruction_phrase = 'Summarize the following conversation. Extract the\n                    main topic, a brief summary, key highlights (1-3 bullet points), and overall\n                    sentiment. Format clearly with headings: ## Topic, ## Summary, ## Highlights,\n                    ## Sentiment.\\n\\n'\n                    full_prompt =\n                    instruction_phrase + prompt_text.strip()\n\n                    #\n                    Call OpenAI within a spinner and try/except block\n                    with st.spinner(\"Generating\n                    summary...\"):\n                        try:\n                            response =\n                            openai.ChatCompletion.create( # Or\n                            Completion API\n                                engine=AZURE_OPENAI_DEPLOYMENT_NAME,\n                                messages=[{\"role\":\"user\",\"content\": full_prompt}],\n                                max_tokens=1024,\n                                temperature=0.2\n                            )\n                            summary_text =\n                            response['choices'][0]['message']['content']\n                            # Parse and store result\n                            st.session_state.current_summary = parse_summary(summary_text)\n                        # Handle various OpenAI/other errors\n                        except\n                        openai.error.AuthenticationError as e: ... # Set\n                        Error state\n                        # ... other except blocks ...\n                        except Exceptionas\n                        e: ... # Set Error state\n\n            else: # No\n            messages fetched/stored\n                st.session_state.current_summary = {\"Info\": \"Empty\n                conversation history.\"}\n\n            st.session_state.summary_processing = False # Unflag\n            processing\n            st.rerun() #\n            Rerun to update the summary display area\n\n\n        # --- Display Summary (or\n        Status) ---\n        # Checks the state variable and\n        displays Error, Warning, Info, or the parsed summary\n        if\n        st.session_state.get('current_summary'):\n            summary_data =\n            st.session_state.current_summary\n            #\n            --- Handle Error State ---\n            if isinstance(summary_data, dict) and \"Error\" in summary_data: ... #\n            Display st.error(...)\n            #\n            --- Handle Warning State ---\n            elif isinstance(summary_data, dict) and \"Warning\" in summary_data: ... #\n            Display st.warning(...) and partial data\n            #\n            --- Handle Info State ---\n            elif isinstance(summary_data, dict) and \"Info\" in summary_data: ... #\n            Display st.info(...)\n            #\n            --- Handle Successful Summary Display ---\n            elif isinstance(summary_data, dict): ... # Display\n            formatted markdown sections\n            #\n            --- Fallback ---\n            else: st.error(\"Summary\n            data unexpected format.\")\n        # --- Default Placeholder Text\n        ---\n        elif not\n        st.session_state.get('summary_processing', False):\n            st.info(\"Click\n            'Generate Summary' for an analysis...\")\n\n\n# --- Handle Initialization Errors ---\n# This runs if the main 'if\nchat_thread_client...' block is False\nelif\ninit_error:\n    st.error(f\"❌ Application cannot\n    start due to initialization errors:\")\n    st.error(init_error) # Display\n    the error caught during initialization\nelse:\n    # Fallback if client is None\n    but no specific error was caught\n    st.warning(\"⏳ Initializing Azure\n    services or waiting for resources...\")\n\n# --- End of Streamlit App Code ---\n
\n

Explanation: This is the main part of the application that runs if the initial Azure setup was successful.

\n\n

5. Running the Streamlit Application

\n

With your Azure resources configured, the Python environment set up, and the code saved in app.py (or your chosen filename), you are ready to run the interactive application.

\n

5.1. Run the App:

\n

Open your terminal or command prompt, make sure you are in your project directory (where app.py and .env are located), and execute the command:

\n
streamlit run app.py
\n

5.2. Initial Loading State:

\n

Streamlit will start its server and provide you with a local URL (usually http://localhost:8501). When you open this URL in your web browser, you will first see the application's initial loading state, similar to this image:

\n\n\n

5.3. Interact with the App:

\n\n

5.4. Review Output:

\n

The AI's chat responses appear directly in the chat interface. The generated summary appears in the right-hand column after you click the button. Any errors during operation should be displayed within the Streamlit interface. You can also check the terminal where you ran the streamlit run command for any server-side print statements or error logs.

\n\n

6. Conclusion

\n

You've now walked through the process of setting up Azure resources and using Streamlit to build a contact center chat analysis solution! By leveraging Azure Communication Services to access chat data and Azure OpenAI Service for intelligent analysis, you can automate the extraction of valuable insights from user or customer interactions, moving from overwhelming data volume to clear, actionable intelligence.

\n

7. Resources

\n","kudosSumWeight":0,"postTime":"2025-04-17T00:00:00.016-07:00","images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLWx0R0JOOA?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDI","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLVU3R3l3WA?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDM","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLXdDOEhZYQ?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDQ","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLTVibEFVeg?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDU","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLUh1aFpjNg?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDY","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLURCakJUSQ?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDc","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLWxoZGp2Vw?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDg","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLVJPeHpyNA?revision=9\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDk","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLTRqNGYwcw?revision=9\"}"}}],"totalCount":9,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"tags":{"__typename":"TagConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"timeToRead":19,"rawTeaser":"","introduction":"","coverImage":null,"coverImageProperties":{"__typename":"CoverImageProperties","style":"STANDARD","titlePosition":"BOTTOM","altText":""},"currentRevision":{"__ref":"Revision:revision:4403710_9"},"latestVersion":{"__typename":"FriendlyVersion","major":"1","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":284},"visibilityScope":"PUBLIC","canonicalUrl":null,"seoTitle":null,"seoDescription":null,"placeholder":false,"originalMessageForPlaceholder":null,"contributors":{"__typename":"UserConnection","edges":[]},"nonCoAuthorContributors":{"__typename":"UserConnection","edges":[]},"coAuthors":{"__typename":"UserConnection","edges":[]},"blogMessagePolicies":{"__typename":"BlogMessagePolicies","canDoAuthoringActionsOnBlog":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","key":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","args":[]}}},"archivalData":null,"replies":{"__typename":"MessageConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"customFields":[],"revisions({\"constraints\":{\"isPublished\":{\"eq\":true}},\"first\":1})":{"__typename":"RevisionConnection","totalCount":9}},"Conversation:conversation:4403710":{"__typename":"Conversation","id":"conversation:4403710","solved":false,"topic":{"__ref":"BlogTopicMessage:message:4403710"},"lastPostingActivityTime":"2025-04-17T00:00:00.016-07:00","lastPostTime":"2025-04-17T00:00:00.016-07:00","unreadReplyCount":0,"isSubscribed":false},"ModerationData:moderation_data:4403710":{"__typename":"ModerationData","id":"moderation_data:4403710","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLWx0R0JOOA?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLWx0R0JOOA?revision=9","title":"image.png","associationType":"BODY","width":1171,"height":647,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLVU3R3l3WA?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLVU3R3l3WA?revision=9","title":"image.png","associationType":"BODY","width":1610,"height":724,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLXdDOEhZYQ?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLXdDOEhZYQ?revision=9","title":"image.png","associationType":"BODY","width":927,"height":168,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLTVibEFVeg?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLTVibEFVeg?revision=9","title":"image.png","associationType":"BODY","width":1057,"height":600,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLUh1aFpjNg?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLUh1aFpjNg?revision=9","title":"image.png","associationType":"BODY","width":1072,"height":619,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLURCakJUSQ?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLURCakJUSQ?revision=9","title":"image.png","associationType":"BODY","width":1064,"height":664,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLWxoZGp2Vw?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLWxoZGp2Vw?revision=9","title":"image.png","associationType":"BODY","width":1317,"height":589,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLVJPeHpyNA?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLVJPeHpyNA?revision=9","title":"image.png","associationType":"BODY","width":940,"height":692,"altText":""},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLTRqNGYwcw?revision=9\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00NDAzNzEwLTRqNGYwcw?revision=9","title":"image.png","associationType":"BODY","width":1222,"height":933,"altText":""},"Revision:revision:4403710_9":{"__typename":"Revision","id":"revision:4403710_9","lastEditTime":"2025-04-12T12:44:39.059-07:00"},"CachedAsset:theme:customTheme1-1747123676492":{"__typename":"CachedAsset","id":"theme:customTheme1-1747123676492","value":{"id":"customTheme1","animation":{"fast":"150ms","normal":"250ms","slow":"500ms","slowest":"750ms","function":"cubic-bezier(0.07, 0.91, 0.51, 1)","__typename":"AnimationThemeSettings"},"avatar":{"borderRadius":"50%","collections":["default"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"favicon-1730836283320.png","imageLastModified":"1730836286415","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"favicon-1730836271365.png","imageLastModified":"1730836274203","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1300px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_BROWSER","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"3px","borderRadius":"3px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"16px","paddingXHero":"60px","fontStyle":"NORMAL","fontWeight":"700","textTransform":"NONE","disabledOpacity":0.5,"primaryTextColor":"var(--lia-bs-white)","primaryTextHoverColor":"var(--lia-bs-white)","primaryTextActiveColor":"var(--lia-bs-white)","primaryBgColor":"var(--lia-bs-primary)","primaryBgHoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.85))","primaryBgActiveColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.7))","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","primaryBorderActive":"1px solid transparent","primaryBorderFocus":"1px solid var(--lia-bs-white)","primaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","secondaryTextColor":"var(--lia-bs-gray-900)","secondaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","secondaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","secondaryBgColor":"var(--lia-bs-gray-200)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","secondaryBorder":"1px solid transparent","secondaryBorderHover":"1px solid transparent","secondaryBorderActive":"1px solid transparent","secondaryBorderFocus":"1px solid transparent","secondaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","tertiaryTextColor":"var(--lia-bs-gray-900)","tertiaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","tertiaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","tertiaryBgColor":"transparent","tertiaryBgHoverColor":"transparent","tertiaryBgActiveColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.04)","tertiaryBorder":"1px solid transparent","tertiaryBorderHover":"1px solid hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","tertiaryBorderActive":"1px solid transparent","tertiaryBorderFocus":"1px solid transparent","tertiaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","destructiveTextColor":"var(--lia-bs-danger)","destructiveTextHoverColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.95))","destructiveTextActiveColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.9))","destructiveBgColor":"var(--lia-bs-gray-200)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","destructiveBorder":"1px solid transparent","destructiveBorderHover":"1px solid transparent","destructiveBorderActive":"1px solid transparent","destructiveBorderFocus":"1px solid transparent","destructiveBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","__typename":"ButtonsThemeSettings"},"border":{"color":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","mainContent":"NONE","sideContent":"LIGHT","radiusSm":"3px","radius":"5px","radiusLg":"9px","radius50":"100vw","__typename":"BorderThemeSettings"},"boxShadow":{"xs":"0 0 0 1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08), 0 3px 0 -1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.16)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.12)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","__typename":"BoxShadowThemeSettings"},"cards":{"bgColor":"var(--lia-panel-bg-color)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":"var(--lia-box-shadow-xs)","__typename":"CardsThemeSettings"},"chip":{"maxWidth":"300px","height":"30px","__typename":"ChipThemeSettings"},"coreTypes":{"defaultMessageLinkColor":"var(--lia-bs-link-color)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","defaultMessageFontFamily":"var(--lia-bs-font-family-base)","forumColor":"#4099E2","forumFontFamily":"var(--lia-bs-font-family-base)","forumFontWeight":"var(--lia-default-message-font-weight)","forumLineHeight":"var(--lia-bs-line-height-base)","forumFontStyle":"var(--lia-default-message-font-style)","forumMessageLinkColor":"var(--lia-default-message-link-color)","forumMessageLinkDecoration":"var(--lia-default-message-link-decoration)","forumMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","forumMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","forumSolvedColor":"#148563","blogColor":"#1CBAA0","blogFontFamily":"var(--lia-bs-font-family-base)","blogFontWeight":"var(--lia-default-message-font-weight)","blogLineHeight":"1.75","blogFontStyle":"var(--lia-default-message-font-style)","blogMessageLinkColor":"var(--lia-default-message-link-color)","blogMessageLinkDecoration":"var(--lia-default-message-link-decoration)","blogMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","blogMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","tkbColor":"#4C6B90","tkbFontFamily":"var(--lia-bs-font-family-base)","tkbFontWeight":"var(--lia-default-message-font-weight)","tkbLineHeight":"1.75","tkbFontStyle":"var(--lia-default-message-font-style)","tkbMessageLinkColor":"var(--lia-default-message-link-color)","tkbMessageLinkDecoration":"var(--lia-default-message-link-decoration)","tkbMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","tkbMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaColor":"#4099E2","qandaFontFamily":"var(--lia-bs-font-family-base)","qandaFontWeight":"var(--lia-default-message-font-weight)","qandaLineHeight":"var(--lia-bs-line-height-base)","qandaFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkColor":"var(--lia-default-message-link-color)","qandaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","qandaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaSolvedColor":"#3FA023","ideaColor":"#FF8000","ideaFontFamily":"var(--lia-bs-font-family-base)","ideaFontWeight":"var(--lia-default-message-font-weight)","ideaLineHeight":"var(--lia-bs-line-height-base)","ideaFontStyle":"var(--lia-default-message-font-style)","ideaMessageLinkColor":"var(--lia-default-message-link-color)","ideaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","ideaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","ideaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","contestColor":"#FCC845","contestFontFamily":"var(--lia-bs-font-family-base)","contestFontWeight":"var(--lia-default-message-font-weight)","contestLineHeight":"var(--lia-bs-line-height-base)","contestFontStyle":"var(--lia-default-message-link-font-style)","contestMessageLinkColor":"var(--lia-default-message-link-color)","contestMessageLinkDecoration":"var(--lia-default-message-link-decoration)","contestMessageLinkFontStyle":"ITALIC","contestMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","occasionColor":"#D13A1F","occasionFontFamily":"var(--lia-bs-font-family-base)","occasionFontWeight":"var(--lia-default-message-font-weight)","occasionLineHeight":"var(--lia-bs-line-height-base)","occasionFontStyle":"var(--lia-default-message-font-style)","occasionMessageLinkColor":"var(--lia-default-message-link-color)","occasionMessageLinkDecoration":"var(--lia-default-message-link-decoration)","occasionMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","occasionMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","grouphubColor":"#333333","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#717171","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0069D4","secondary":"#333333","bodyText":"#1E1E1E","bodyBg":"#FFFFFF","info":"#409AE2","success":"#41C5AE","warning":"#FCC844","danger":"#BC341B","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#D3F5A4","#243A5E"],"__typename":"ColorsThemeSettings"},"divider":{"size":"3px","marginLeft":"4px","marginRight":"4px","borderRadius":"50%","bgColor":"var(--lia-bs-gray-600)","bgColorActive":"var(--lia-bs-gray-600)","__typename":"DividerThemeSettings"},"dropdown":{"fontSize":"var(--lia-bs-font-size-sm)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius-sm)","dividerBg":"var(--lia-bs-gray-300)","itemPaddingY":"5px","itemPaddingX":"20px","headerColor":"var(--lia-bs-gray-700)","__typename":"DropdownThemeSettings"},"email":{"link":{"color":"#0069D4","hoverColor":"#0061c2","decoration":"none","hoverDecoration":"underline","__typename":"EmailLinkSettings"},"border":{"color":"#e4e4e4","__typename":"EmailBorderSettings"},"buttons":{"borderRadiusLg":"5px","paddingXLg":"16px","paddingYLg":"7px","fontWeight":"700","primaryTextColor":"#ffffff","primaryTextHoverColor":"#ffffff","primaryBgColor":"#0069D4","primaryBgHoverColor":"#005cb8","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","__typename":"EmailButtonsSettings"},"panel":{"borderRadius":"5px","borderColor":"#e4e4e4","__typename":"EmailPanelSettings"},"__typename":"EmailThemeSettings"},"emoji":{"skinToneDefault":"#ffcd43","skinToneLight":"#fae3c5","skinToneMediumLight":"#e2cfa5","skinToneMedium":"#daa478","skinToneMediumDark":"#a78058","skinToneDark":"#5e4d43","__typename":"EmojiThemeSettings"},"heading":{"color":"var(--lia-bs-body-color)","fontFamily":"Segoe UI","fontStyle":"NORMAL","fontWeight":"400","h1FontSize":"34px","h2FontSize":"32px","h3FontSize":"28px","h4FontSize":"24px","h5FontSize":"20px","h6FontSize":"16px","lineHeight":"1.3","subHeaderFontSize":"11px","subHeaderFontWeight":"500","h1LetterSpacing":"normal","h2LetterSpacing":"normal","h3LetterSpacing":"normal","h4LetterSpacing":"normal","h5LetterSpacing":"normal","h6LetterSpacing":"normal","subHeaderLetterSpacing":"2px","h1FontWeight":"var(--lia-bs-headings-font-weight)","h2FontWeight":"var(--lia-bs-headings-font-weight)","h3FontWeight":"var(--lia-bs-headings-font-weight)","h4FontWeight":"var(--lia-bs-headings-font-weight)","h5FontWeight":"var(--lia-bs-headings-font-weight)","h6FontWeight":"var(--lia-bs-headings-font-weight)","__typename":"HeadingThemeSettings"},"icons":{"size10":"10px","size12":"12px","size14":"14px","size16":"16px","size20":"20px","size24":"24px","size30":"30px","size40":"40px","size50":"50px","size60":"60px","size80":"80px","size120":"120px","size160":"160px","__typename":"IconsThemeSettings"},"imagePreview":{"bgColor":"var(--lia-bs-gray-900)","titleColor":"var(--lia-bs-white)","controlColor":"var(--lia-bs-white)","controlBgColor":"var(--lia-bs-gray-800)","__typename":"ImagePreviewThemeSettings"},"input":{"borderColor":"var(--lia-bs-gray-600)","disabledColor":"var(--lia-bs-gray-600)","focusBorderColor":"var(--lia-bs-primary)","labelMarginBottom":"10px","btnFontSize":"var(--lia-bs-font-size-sm)","focusBoxShadow":"0 0 0 3px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","checkLabelMarginBottom":"2px","checkboxBorderRadius":"3px","borderRadiusSm":"var(--lia-bs-border-radius-sm)","borderRadius":"var(--lia-bs-border-radius)","borderRadiusLg":"var(--lia-bs-border-radius-lg)","formTextMarginTop":"4px","textAreaBorderRadius":"var(--lia-bs-border-radius)","activeFillColor":"var(--lia-bs-primary)","__typename":"InputThemeSettings"},"loading":{"dotDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.2)","dotLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.5)","barDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.06)","barLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.4)","__typename":"LoadingThemeSettings"},"link":{"color":"var(--lia-bs-primary)","hoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) - 10%))","decoration":"none","hoverDecoration":"underline","__typename":"LinkThemeSettings"},"listGroup":{"itemPaddingY":"15px","itemPaddingX":"15px","borderColor":"var(--lia-bs-gray-300)","__typename":"ListGroupThemeSettings"},"modal":{"contentTextColor":"var(--lia-bs-body-color)","contentBg":"var(--lia-bs-white)","backgroundBg":"var(--lia-bs-black)","smSize":"440px","mdSize":"760px","lgSize":"1080px","backdropOpacity":0.3,"contentBoxShadowXs":"var(--lia-bs-box-shadow-sm)","contentBoxShadow":"var(--lia-bs-box-shadow)","headerFontWeight":"700","__typename":"ModalThemeSettings"},"navbar":{"position":"FIXED","background":{"attachment":null,"clip":null,"color":"var(--lia-bs-white)","imageAssetName":"","imageLastModified":"0","origin":null,"position":"CENTER_CENTER","repeat":"NO_REPEAT","size":"COVER","__typename":"BackgroundProps"},"backgroundOpacity":0.8,"paddingTop":"15px","paddingBottom":"15px","borderBottom":"1px solid var(--lia-bs-border-color)","boxShadow":"var(--lia-bs-box-shadow-sm)","brandMarginRight":"30px","brandMarginRightSm":"10px","brandLogoHeight":"30px","linkGap":"10px","linkJustifyContent":"flex-start","linkPaddingY":"5px","linkPaddingX":"10px","linkDropdownPaddingY":"9px","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkColor":"var(--lia-bs-body-color)","linkHoverColor":"var(--lia-bs-primary)","linkFontSize":"var(--lia-bs-font-size-sm)","linkFontStyle":"NORMAL","linkFontWeight":"400","linkTextTransform":"NONE","linkLetterSpacing":"normal","linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkBgColor":"transparent","linkBgHoverColor":"transparent","linkBorder":"none","linkBorderHover":"none","linkBoxShadow":"none","linkBoxShadowHover":"none","linkTextBorderBottom":"none","linkTextBorderBottomHover":"none","dropdownPaddingTop":"10px","dropdownPaddingBottom":"15px","dropdownPaddingX":"10px","dropdownMenuOffset":"2px","dropdownDividerMarginTop":"10px","dropdownDividerMarginBottom":"10px","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","controllerIconColor":"var(--lia-bs-body-color)","controllerIconHoverColor":"var(--lia-bs-body-color)","controllerTextColor":"var(--lia-nav-controller-icon-color)","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","controllerHighlightColor":"hsla(30, 100%, 50%)","controllerHighlightTextColor":"var(--lia-yiq-light)","controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerColor":"var(--lia-nav-controller-icon-color)","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","hamburgerBgColor":"transparent","hamburgerBgHoverColor":"transparent","hamburgerBorder":"none","hamburgerBorderHover":"none","collapseMenuMarginLeft":"20px","collapseMenuDividerBg":"var(--lia-nav-link-color)","collapseMenuDividerOpacity":0.16,"__typename":"NavbarThemeSettings"},"pager":{"textColor":"var(--lia-bs-link-color)","textFontWeight":"var(--lia-font-weight-md)","textFontSize":"var(--lia-bs-font-size-sm)","__typename":"PagerThemeSettings"},"panel":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-bs-border-radius)","borderColor":"var(--lia-bs-border-color)","boxShadow":"none","__typename":"PanelThemeSettings"},"popover":{"arrowHeight":"8px","arrowWidth":"16px","maxWidth":"300px","minWidth":"100px","headerBg":"var(--lia-bs-white)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius)","boxShadow":"0 0.5rem 1rem hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.15)","__typename":"PopoverThemeSettings"},"prism":{"color":"#000000","bgColor":"#f5f2f0","fontFamily":"var(--font-family-monospace)","fontSize":"var(--lia-bs-font-size-base)","fontWeightBold":"var(--lia-bs-font-weight-bold)","fontStyleItalic":"italic","tabSize":2,"highlightColor":"#b3d4fc","commentColor":"#62707e","punctuationColor":"#6f6f6f","namespaceOpacity":"0.7","propColor":"#990055","selectorColor":"#517a00","operatorColor":"#906736","operatorBgColor":"hsla(0, 0%, 100%, 0.5)","keywordColor":"#0076a9","functionColor":"#d3284b","variableColor":"#c14700","__typename":"PrismThemeSettings"},"rte":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":" var(--lia-panel-box-shadow)","customColor1":"#bfedd2","customColor2":"#fbeeb8","customColor3":"#f8cac6","customColor4":"#eccafa","customColor5":"#c2e0f4","customColor6":"#2dc26b","customColor7":"#f1c40f","customColor8":"#e03e2d","customColor9":"#b96ad9","customColor10":"#3598db","customColor11":"#169179","customColor12":"#e67e23","customColor13":"#ba372a","customColor14":"#843fa1","customColor15":"#236fa1","customColor16":"#ecf0f1","customColor17":"#ced4d9","customColor18":"#95a5a6","customColor19":"#7e8c8d","customColor20":"#34495e","customColor21":"#000000","customColor22":"#ffffff","defaultMessageHeaderMarginTop":"40px","defaultMessageHeaderMarginBottom":"20px","defaultMessageItemMarginTop":"0","defaultMessageItemMarginBottom":"10px","diffAddedColor":"hsla(170, 53%, 51%, 0.4)","diffChangedColor":"hsla(43, 97%, 63%, 0.4)","diffNoneColor":"hsla(0, 0%, 80%, 0.4)","diffRemovedColor":"hsla(9, 74%, 47%, 0.4)","specialMessageHeaderMarginTop":"40px","specialMessageHeaderMarginBottom":"20px","specialMessageItemMarginTop":"0","specialMessageItemMarginBottom":"10px","__typename":"RteThemeSettings"},"tags":{"bgColor":"var(--lia-bs-gray-200)","bgHoverColor":"var(--lia-bs-gray-400)","borderRadius":"var(--lia-bs-border-radius-sm)","color":"var(--lia-bs-body-color)","hoverColor":"var(--lia-bs-body-color)","fontWeight":"var(--lia-font-weight-md)","fontSize":"var(--lia-font-size-xxs)","textTransform":"UPPERCASE","letterSpacing":"0.5px","__typename":"TagsThemeSettings"},"toasts":{"borderRadius":"var(--lia-bs-border-radius)","paddingX":"12px","__typename":"ToastsThemeSettings"},"typography":{"fontFamilyBase":"Segoe UI","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.5","fontSizeBase":"16px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"14px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[{"source":"SERVER","name":"Segoe UI","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"},{"style":"NORMAL","weight":"300","__typename":"FontStyleData"},{"style":"NORMAL","weight":"600","__typename":"FontStyleData"},{"style":"NORMAL","weight":"700","__typename":"FontStyleData"},{"style":"ITALIC","weight":"400","__typename":"FontStyleData"}],"assetNames":["SegoeUI-normal-400.woff2","SegoeUI-normal-300.woff2","SegoeUI-normal-600.woff2","SegoeUI-normal-700.woff2","SegoeUI-italic-400.woff2"],"__typename":"CustomFont"},{"source":"SERVER","name":"MWF Fluent Icons","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"}],"assetNames":["MWFFluentIcons-normal-400.woff2"],"__typename":"CustomFont"}],"__typename":"TypographyThemeSettings"},"unstyledListItem":{"marginBottomSm":"5px","marginBottomMd":"10px","marginBottomLg":"15px","marginBottomXl":"20px","marginBottomXxl":"25px","__typename":"UnstyledListItemThemeSettings"},"yiq":{"light":"#ffffff","dark":"#000000","__typename":"YiqThemeSettings"},"colorLightness":{"primaryDark":0.36,"primaryLight":0.74,"primaryLighter":0.89,"primaryLightest":0.95,"infoDark":0.39,"infoLight":0.72,"infoLighter":0.85,"infoLightest":0.93,"successDark":0.24,"successLight":0.62,"successLighter":0.8,"successLightest":0.91,"warningDark":0.39,"warningLight":0.68,"warningLighter":0.84,"warningLightest":0.93,"dangerDark":0.41,"dangerLight":0.72,"dangerLighter":0.89,"dangerLightest":0.95,"__typename":"ColorLightnessThemeSettings"},"localOverride":false,"__typename":"Theme"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1745505307000","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:quilt:o365.prod:pages/blogs/BlogMessagePage:board:EducatorDeveloperBlog-1747123674709":{"__typename":"CachedAsset","id":"quilt:o365.prod:pages/blogs/BlogMessagePage:board:EducatorDeveloperBlog-1747123674709","value":{"id":"BlogMessagePage","container":{"id":"Common","headerProps":{"backgroundImageProps":null,"backgroundColor":null,"addComponents":null,"removeComponents":["community.widget.bannerWidget"],"componentOrder":null,"__typename":"QuiltContainerSectionProps"},"headerComponentProps":{"community.widget.breadcrumbWidget":{"disableLastCrumbForDesktop":false}},"footerProps":null,"footerComponentProps":null,"items":[{"id":"blog-article","layout":"ONE_COLUMN","bgColor":null,"showTitle":null,"showDescription":null,"textPosition":null,"textColor":null,"sectionEditLevel":"LOCKED","bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"OneColumnQuiltSection","columnMap":{"main":[{"id":"blogs.widget.blogArticleWidget","className":"lia-blog-container","props":null,"__typename":"QuiltComponent"}],"__typename":"OneSectionColumns"}},{"id":"section-1729184836777","layout":"MAIN_SIDE","bgColor":"transparent","showTitle":false,"showDescription":false,"textPosition":"CENTER","textColor":"var(--lia-bs-body-color)","sectionEditLevel":null,"bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"MainSideQuiltSection","columnMap":{"main":[],"side":[],"__typename":"MainSideSectionColumns"}}],"__typename":"QuiltContainer"},"__typename":"Quilt","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1745505307000","value":{"email.verification.title":"Email Verification Required","email.verification.message.update.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. To change your email, visit My Settings.","email.verification.message.resend.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. Resend email."},"localOverride":false},"CachedAsset:text:en_US-pages/blogs/BlogMessagePage-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-pages/blogs/BlogMessagePage-1745505307000","value":{"title":"{contextMessageSubject} | {communityTitle}","errorMissing":"This blog post cannot be found","name":"Blog Message Page","section.blog-article.title":"Blog Post","archivedMessageTitle":"This Content Has Been Archived","section.section-1729184836777.title":"","section.section-1729184836777.description":"","section.CncIde.title":"Blog Post","section.tifEmD.description":"","section.tifEmD.title":""},"localOverride":false},"CachedAsset:quiltWrapper:o365.prod:Common:1747123608412":{"__typename":"CachedAsset","id":"quiltWrapper:o365.prod:Common:1747123608412","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"community.widget.navbarWidget","props":{"showUserName":true,"showRegisterLink":true,"useIconLanguagePicker":true,"useLabelLanguagePicker":true,"className":"QuiltComponent_lia-component-edit-mode__0nCcm","links":{"sideLinks":[],"mainLinks":[{"children":[],"linkType":"INTERNAL","id":"gxcuf89792","params":{},"routeName":"CommunityPage"},{"children":[],"linkType":"EXTERNAL","id":"external-link","url":"/Directory","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft365","params":{"categoryId":"microsoft365"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows","params":{"categoryId":"Windows"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"Common-microsoft365-copilot-link","params":{"categoryId":"Microsoft365Copilot"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-teams","params":{"categoryId":"MicrosoftTeams"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-securityand-compliance","params":{"categoryId":"microsoft-security"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"azure","params":{"categoryId":"Azure"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"Common-content_management-link","params":{"categoryId":"Content_Management"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"exchange","params":{"categoryId":"Exchange"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows-server","params":{"categoryId":"Windows-Server"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"outlook","params":{"categoryId":"Outlook"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-endpoint-manager","params":{"categoryId":"microsoftintune"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-2","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities","url":"/","target":"BLANK"},{"children":[{"linkType":"INTERNAL","id":"a-i","params":{"categoryId":"AI"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"education-sector","params":{"categoryId":"EducationSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"partner-community","params":{"categoryId":"PartnerCommunity"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"i-t-ops-talk","params":{"categoryId":"ITOpsTalk"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"healthcare-and-life-sciences","params":{"categoryId":"HealthcareAndLifeSciences"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-mechanics","params":{"categoryId":"MicrosoftMechanics"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"public-sector","params":{"categoryId":"PublicSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"s-m-b","params":{"categoryId":"MicrosoftforNonprofits"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"io-t","params":{"categoryId":"IoT"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"startupsat-microsoft","params":{"categoryId":"StartupsatMicrosoft"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"driving-adoption","params":{"categoryId":"DrivingAdoption"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-1","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities-1","url":"/","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external","url":"/Blogs","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external-1","url":"/Events","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft-learn-1","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-learn-blog","params":{"boardId":"MicrosoftLearnBlog","categoryId":"MicrosoftLearn"},"routeName":"BlogBoardPage"},{"linkType":"EXTERNAL","id":"external-10","url":"https://learningroomdirectory.microsoft.com/","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-3","url":"https://docs.microsoft.com/learn/dynamics365/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-4","url":"https://docs.microsoft.com/learn/m365/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-5","url":"https://docs.microsoft.com/learn/topics/sci/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-6","url":"https://docs.microsoft.com/learn/powerplatform/?wt.mc_id=techcom_header-webpage-powerplatform","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-7","url":"https://docs.microsoft.com/learn/github/?wt.mc_id=techcom_header-webpage-github","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-8","url":"https://docs.microsoft.com/learn/teams/?wt.mc_id=techcom_header-webpage-teams","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-9","url":"https://docs.microsoft.com/learn/dotnet/?wt.mc_id=techcom_header-webpage-dotnet","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-2","url":"https://docs.microsoft.com/learn/azure/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"}],"linkType":"INTERNAL","id":"microsoft-learn","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"community-info-center","params":{"categoryId":"Community-Info-Center"},"routeName":"CategoryPage"}]},"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","controllerHighlightColor":"hsla(30, 100%, 50%)","linkFontWeight":"400","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkBoxShadowHover":"none","linkFontSize":"14px","backgroundOpacity":0.8,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","hamburgerColor":"var(--lia-nav-controller-icon-color)","linkTextBorderBottom":"none","brandLogoHeight":"30px","linkBgHoverColor":"transparent","linkLetterSpacing":"normal","collapseMenuDividerOpacity":0.16,"dropdownPaddingBottom":"15px","paddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"1px solid var(--lia-bs-border-color)","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","collapseMenuDividerBg":"var(--lia-nav-link-color)","linkColor":"var(--lia-bs-body-color)","linkJustifyContent":"flex-start","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","controllerTextColor":"var(--lia-nav-controller-icon-color)","background":{"imageAssetName":"","color":"var(--lia-bs-white)","size":"COVER","repeat":"NO_REPEAT","position":"CENTER_CENTER","imageLastModified":""},"linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkHoverColor":"var(--lia-bs-body-color)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid var(--lia-bs-body-color)","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","linkPaddingX":"10px","linkPaddingY":"5px","paddingTop":"15px","linkTextTransform":"NONE","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","linkBgColor":"transparent","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkDropdownPaddingY":"9px","controllerIconColor":"var(--lia-bs-body-color)","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"var(--lia-bs-body-color)"},"showSearchIcon":false,"languagePickerStyle":"iconAndLabel"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"transparent","linkHighlightColor":"var(--lia-bs-primary)","visualEffects":{"showBottomBorder":true},"linkTextColor":"var(--lia-bs-gray-700)"},"__typename":"QuiltComponent"},{"id":"custom.widget.HeroBanner","props":{"widgetVisibility":"signedInOrAnonymous","usePageWidth":false,"useTitle":true,"cMax_items":3,"useBackground":false,"title":"","lazyLoad":false,"widgetChooser":"custom.widget.HeroBanner"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.MicrosoftFooter","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"__typename":"QuiltWrapper","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/ActionFeedback-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1745505307000","value":{"joinedGroupHub.title":"Welcome","joinedGroupHub.message":"You are now a member of this group and are subscribed to updates.","groupHubInviteNotFound.title":"Invitation Not Found","groupHubInviteNotFound.message":"Sorry, we could not find your invitation to the group. The owner may have canceled the invite.","groupHubNotFound.title":"Group Not Found","groupHubNotFound.message":"The grouphub you tried to join does not exist. It may have been deleted.","existingGroupHubMember.title":"Already Joined","existingGroupHubMember.message":"You are already a member of this group.","accountLocked.title":"Account Locked","accountLocked.message":"Your account has been locked due to multiple failed attempts. Try again in {lockoutTime} minutes.","editedGroupHub.title":"Changes Saved","editedGroupHub.message":"Your group has been updated.","leftGroupHub.title":"Goodbye","leftGroupHub.message":"You are no longer a member of this group and will not receive future updates.","deletedGroupHub.title":"Deleted","deletedGroupHub.message":"The group has been deleted.","groupHubCreated.title":"Group Created","groupHubCreated.message":"{groupHubName} is ready to use","accountClosed.title":"Account Closed","accountClosed.message":"The account has been closed and you will now be redirected to the homepage","resetTokenExpired.title":"Reset Password Link has Expired","resetTokenExpired.message":"Try resetting your password again","invalidUrl.title":"Invalid URL","invalidUrl.message":"The URL you're using is not recognized. Verify your URL and try again.","accountClosedForUser.title":"Account Closed","accountClosedForUser.message":"{userName}'s account is closed","inviteTokenInvalid.title":"Invitation Invalid","inviteTokenInvalid.message":"Your invitation to the community has been canceled or expired.","inviteTokenError.title":"Invitation Verification Failed","inviteTokenError.message":"The url you are utilizing is not recognized. Verify your URL and try again","pageNotFound.title":"Access Denied","pageNotFound.message":"You do not have access to this area of the community or it doesn't exist","eventAttending.title":"Responded as Attending","eventAttending.message":"You'll be notified when there's new activity and reminded as the event approaches","eventInterested.title":"Responded as Interested","eventInterested.message":"You'll be notified when there's new activity and reminded as the event approaches","eventNotFound.title":"Event Not Found","eventNotFound.message":"The event you tried to respond to does not exist.","redirectToRelatedPage.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.message":"The content you are trying to access is archived","redirectToRelatedPage.message":"The content you are trying to access is archived","relatedUrl.archivalLink.flyoutMessage":"The content you are trying to access is archived View Archived Content"},"localOverride":false},"QueryVariables:TopicReplyList:message:4403710:9":{"__typename":"QueryVariables","id":"TopicReplyList:message:4403710:9","value":{"id":"message:4403710","first":10,"sorts":{"postTime":{"direction":"DESC"}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"postTime":{"direction":"DESC"}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":false,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":true,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"ROOT_MUTATION":{"__typename":"Mutation"},"CachedAsset:component:custom.widget.HeroBanner-en-us-1747150703148":{"__typename":"CachedAsset","id":"component:custom.widget.HeroBanner-en-us-1747150703148","value":{"component":{"id":"custom.widget.HeroBanner","template":{"id":"HeroBanner","markupLanguage":"REACT","style":null,"texts":{"searchPlaceholderText":"Search this community","followActionText":"Follow","unfollowActionText":"Following","searchOnHoverText":"Please enter your search term(s) and then press return key to complete a search.","blogs.sidebar.pagetitle":"Latest Blogs | Microsoft Tech Community","followThisNode":"Follow this node","unfollowThisNode":"Unfollow this node"},"defaults":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.HeroBanner","form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"__typename":"Component","localOverride":false},"globalCss":null,"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"}},"localOverride":false},"CachedAsset:component:custom.widget.MicrosoftFooter-en-us-1747150703148":{"__typename":"CachedAsset","id":"component:custom.widget.MicrosoftFooter-en-us-1747150703148","value":{"component":{"id":"custom.widget.MicrosoftFooter","template":{"id":"MicrosoftFooter","markupLanguage":"HANDLEBARS","style":".context-uhf {\n min-width: 280px;\n font-size: 15px;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.c-uhff-link {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.c-uhff {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.c-uhff-nav {\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n .c-heading-4 {\n color: #616161;\n word-break: break-word;\n font-size: 15px;\n line-height: 20px;\n padding: 36px 0 4px;\n font-weight: 600;\n }\n .c-uhff-nav-row {\n .c-uhff-nav-group {\n display: block;\n float: left;\n min-height: 1px;\n vertical-align: text-top;\n padding: 0 12px;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.c-list.f-bare {\n font-size: 11px;\n line-height: 16px;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 8px 0;\n margin: 0;\n }\n }\n }\n }\n}\n.c-uhff-base {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 30px 5% 16px;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.c-uhff-ccpa {\n font-size: 11px;\n line-height: 16px;\n float: left;\n margin: 3px 0;\n }\n a.c-uhff-ccpa:hover {\n text-decoration: underline;\n }\n ul.c-list {\n font-size: 11px;\n line-height: 16px;\n float: right;\n margin: 3px 0;\n color: #616161;\n li {\n padding: 0 24px 4px 0;\n display: inline-block;\n }\n }\n .c-list.f-bare {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 30px 24px 16px;\n }\n}\n\n.social-share {\n position: fixed;\n top: 60%;\n transform: translateY(-50%);\n left: 0;\n z-index: 1000;\n}\n\n.sharing-options {\n list-style: none;\n padding: 0;\n margin: 0;\n display: block;\n flex-direction: column;\n background-color: white;\n width: 43px;\n border-radius: 0px 7px 7px 0px;\n}\n.linkedin-icon {\n border-top-right-radius: 7px;\n}\n.linkedin-icon:hover {\n border-radius: 0;\n}\n.social-share-rss-image {\n border-bottom-right-radius: 7px;\n}\n.social-share-rss-image:hover {\n border-radius: 0;\n}\n\n.social-link-footer {\n position: relative;\n display: block;\n margin: -2px 0;\n transition: all 0.2s ease;\n}\n.social-link-footer:hover .linkedin-icon {\n border-radius: 0;\n}\n.social-link-footer:hover .social-share-rss-image {\n border-radius: 0;\n}\n\n.social-link-footer img {\n width: 40px;\n height: auto;\n transition: filter 0.3s ease;\n}\n\n.social-share-list {\n width: 40px;\n}\n.social-share-rss-image {\n width: 40px;\n}\n\n.share-icon {\n border: 2px solid transparent;\n display: inline-block;\n position: relative;\n}\n\n.share-icon:hover {\n opacity: 1;\n border: 2px solid white;\n box-sizing: border-box;\n}\n\n.share-icon:hover .label {\n opacity: 1;\n visibility: visible;\n border: 2px solid white;\n box-sizing: border-box;\n border-left: none;\n}\n\n.label {\n position: absolute;\n left: 100%;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.2s ease;\n color: white;\n border-radius: 0 10 0 10px;\n top: 50%;\n transform: translateY(-50%);\n height: 40px;\n border-radius: 0 6px 6px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px 5px 20px 8px;\n margin-left: -1px;\n}\n.linkedin {\n background-color: #0474b4;\n}\n.facebook {\n background-color: #3c5c9c;\n}\n.twitter {\n background-color: white;\n color: black;\n}\n.reddit {\n background-color: #fc4404;\n}\n.mail {\n background-color: #848484;\n}\n.bluesky {\n background-color: white;\n color: black;\n}\n.rss {\n background-color: #ec7b1c;\n}\n#RSS {\n width: 40px;\n height: 40px;\n}\n\n@media (max-width: 991px) {\n .social-share {\n display: none;\n }\n}\n","texts":{"New tab":"What's New","New 1":"Surface Laptop Studio 2","New 2":"Surface Laptop Go 3","New 3":"Surface Pro 9","New 4":"Surface Laptop 5","New 5":"Surface Studio 2+","New 6":"Copilot in Windows","New 7":"Microsoft 365","New 8":"Windows 11 apps","Store tab":"Microsoft Store","Store 1":"Account Profile","Store 2":"Download Center","Store 3":"Microsoft Store Support","Store 4":"Returns","Store 5":"Order tracking","Store 6":"Certified Refurbished","Store 7":"Microsoft Store Promise","Store 8":"Flexible Payments","Education tab":"Education","Edu 1":"Microsoft in education","Edu 2":"Devices for education","Edu 3":"Microsoft Teams for Education","Edu 4":"Microsoft 365 Education","Edu 5":"How to buy for your school","Edu 6":"Educator Training and development","Edu 7":"Deals for students and parents","Edu 8":"Azure for students","Business tab":"Business","Bus 1":"Microsoft Cloud","Bus 2":"Microsoft Security","Bus 3":"Dynamics 365","Bus 4":"Microsoft 365","Bus 5":"Microsoft Power Platform","Bus 6":"Microsoft Teams","Bus 7":"Microsoft Industry","Bus 8":"Small Business","Developer tab":"Developer & IT","Dev 1":"Azure","Dev 2":"Developer Center","Dev 3":"Documentation","Dev 4":"Microsoft Learn","Dev 5":"Microsoft Tech Community","Dev 6":"Azure Marketplace","Dev 7":"AppSource","Dev 8":"Visual Studio","Company tab":"Company","Com 1":"Careers","Com 2":"About Microsoft","Com 3":"Company News","Com 4":"Privacy at Microsoft","Com 5":"Investors","Com 6":"Diversity and inclusion","Com 7":"Accessiblity","Com 8":"Sustainibility"},"defaults":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.MicrosoftFooter","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":{"css":".custom_widget_MicrosoftFooter_context-uhf_105bp_1 {\n min-width: 17.5rem;\n font-size: 0.9375rem;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-link_105bp_12 {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff_105bp_12 {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.custom_widget_MicrosoftFooter_c-uhff-nav_105bp_35 {\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n .custom_widget_MicrosoftFooter_c-heading-4_105bp_49 {\n color: #616161;\n word-break: break-word;\n font-size: 0.9375rem;\n line-height: 1.25rem;\n padding: 2.25rem 0 0.25rem;\n font-weight: 600;\n }\n .custom_widget_MicrosoftFooter_c-uhff-nav-row_105bp_57 {\n .custom_widget_MicrosoftFooter_c-uhff-nav-group_105bp_58 {\n display: block;\n float: left;\n min-height: 0.0625rem;\n vertical-align: text-top;\n padding: 0 0.75rem;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.custom_widget_MicrosoftFooter_c-list_105bp_78.custom_widget_MicrosoftFooter_f-bare_105bp_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 0.5rem 0;\n margin: 0;\n }\n }\n }\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff-base_105bp_94 {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 1.875rem 5% 1rem;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: left;\n margin: 0.1875rem 0;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107:hover {\n text-decoration: underline;\n }\n ul.custom_widget_MicrosoftFooter_c-list_105bp_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: right;\n margin: 0.1875rem 0;\n color: #616161;\n li {\n padding: 0 1.5rem 0.25rem 0;\n display: inline-block;\n }\n }\n .custom_widget_MicrosoftFooter_c-list_105bp_78.custom_widget_MicrosoftFooter_f-bare_105bp_78 {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 1.875rem 1.5rem 1rem;\n }\n}\n.custom_widget_MicrosoftFooter_social-share_105bp_138 {\n position: fixed;\n top: 60%;\n transform: translateY(-50%);\n left: 0;\n z-index: 1000;\n}\n.custom_widget_MicrosoftFooter_sharing-options_105bp_146 {\n list-style: none;\n padding: 0;\n margin: 0;\n display: block;\n flex-direction: column;\n background-color: white;\n width: 2.6875rem;\n border-radius: 0 0.4375rem 0.4375rem 0;\n}\n.custom_widget_MicrosoftFooter_linkedin-icon_105bp_156 {\n border-top-right-radius: 7px;\n}\n.custom_widget_MicrosoftFooter_linkedin-icon_105bp_156:hover {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n border-bottom-right-radius: 7px;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162:hover {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169 {\n position: relative;\n display: block;\n margin: -0.125rem 0;\n transition: all 0.2s ease;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169:hover .custom_widget_MicrosoftFooter_linkedin-icon_105bp_156 {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169:hover .custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169 img {\n width: 2.5rem;\n height: auto;\n transition: filter 0.3s ease;\n}\n.custom_widget_MicrosoftFooter_social-share-list_105bp_188 {\n width: 2.5rem;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n width: 2.5rem;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195 {\n border: 2px solid transparent;\n display: inline-block;\n position: relative;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195:hover {\n opacity: 1;\n border: 2px solid white;\n box-sizing: border-box;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195:hover .custom_widget_MicrosoftFooter_label_105bp_207 {\n opacity: 1;\n visibility: visible;\n border: 2px solid white;\n box-sizing: border-box;\n border-left: none;\n}\n.custom_widget_MicrosoftFooter_label_105bp_207 {\n position: absolute;\n left: 100%;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.2s ease;\n color: white;\n border-radius: 0 10 0 0.625rem;\n top: 50%;\n transform: translateY(-50%);\n height: 2.5rem;\n border-radius: 0 0.375rem 0.375rem 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1.25rem 0.3125rem 1.25rem 0.5rem;\n margin-left: -0.0625rem;\n}\n.custom_widget_MicrosoftFooter_linkedin_105bp_156 {\n background-color: #0474b4;\n}\n.custom_widget_MicrosoftFooter_facebook_105bp_237 {\n background-color: #3c5c9c;\n}\n.custom_widget_MicrosoftFooter_twitter_105bp_240 {\n background-color: white;\n color: black;\n}\n.custom_widget_MicrosoftFooter_reddit_105bp_244 {\n background-color: #fc4404;\n}\n.custom_widget_MicrosoftFooter_mail_105bp_247 {\n background-color: #848484;\n}\n.custom_widget_MicrosoftFooter_bluesky_105bp_250 {\n background-color: white;\n color: black;\n}\n.custom_widget_MicrosoftFooter_rss_105bp_254 {\n background-color: #ec7b1c;\n}\n#custom_widget_MicrosoftFooter_RSS_105bp_1 {\n width: 2.5rem;\n height: 2.5rem;\n}\n@media (max-width: 991px) {\n .custom_widget_MicrosoftFooter_social-share_105bp_138 {\n display: none;\n }\n}\n","tokens":{"context-uhf":"custom_widget_MicrosoftFooter_context-uhf_105bp_1","c-uhff-link":"custom_widget_MicrosoftFooter_c-uhff-link_105bp_12","c-uhff":"custom_widget_MicrosoftFooter_c-uhff_105bp_12","c-uhff-nav":"custom_widget_MicrosoftFooter_c-uhff-nav_105bp_35","c-heading-4":"custom_widget_MicrosoftFooter_c-heading-4_105bp_49","c-uhff-nav-row":"custom_widget_MicrosoftFooter_c-uhff-nav-row_105bp_57","c-uhff-nav-group":"custom_widget_MicrosoftFooter_c-uhff-nav-group_105bp_58","c-list":"custom_widget_MicrosoftFooter_c-list_105bp_78","f-bare":"custom_widget_MicrosoftFooter_f-bare_105bp_78","c-uhff-base":"custom_widget_MicrosoftFooter_c-uhff-base_105bp_94","c-uhff-ccpa":"custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107","social-share":"custom_widget_MicrosoftFooter_social-share_105bp_138","sharing-options":"custom_widget_MicrosoftFooter_sharing-options_105bp_146","linkedin-icon":"custom_widget_MicrosoftFooter_linkedin-icon_105bp_156","social-share-rss-image":"custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162","social-link-footer":"custom_widget_MicrosoftFooter_social-link-footer_105bp_169","social-share-list":"custom_widget_MicrosoftFooter_social-share-list_105bp_188","share-icon":"custom_widget_MicrosoftFooter_share-icon_105bp_195","label":"custom_widget_MicrosoftFooter_label_105bp_207","linkedin":"custom_widget_MicrosoftFooter_linkedin_105bp_156","facebook":"custom_widget_MicrosoftFooter_facebook_105bp_237","twitter":"custom_widget_MicrosoftFooter_twitter_105bp_240","reddit":"custom_widget_MicrosoftFooter_reddit_105bp_244","mail":"custom_widget_MicrosoftFooter_mail_105bp_247","bluesky":"custom_widget_MicrosoftFooter_bluesky_105bp_250","rss":"custom_widget_MicrosoftFooter_rss_105bp_254","RSS":"custom_widget_MicrosoftFooter_RSS_105bp_1"}},"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1745505307000","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBanner-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBanner-1745505307000","value":{"messageMarkedAsSpam":"This post has been marked as spam","messageMarkedAsSpam@board:TKB":"This article has been marked as spam","messageMarkedAsSpam@board:BLOG":"This post has been marked as spam","messageMarkedAsSpam@board:FORUM":"This discussion has been marked as spam","messageMarkedAsSpam@board:OCCASION":"This event has been marked as spam","messageMarkedAsSpam@board:IDEA":"This idea has been marked as spam","manageSpam":"Manage Spam","messageMarkedAsAbuse":"This post has been marked as abuse","messageMarkedAsAbuse@board:TKB":"This article has been marked as abuse","messageMarkedAsAbuse@board:BLOG":"This post has been marked as abuse","messageMarkedAsAbuse@board:FORUM":"This discussion has been marked as abuse","messageMarkedAsAbuse@board:OCCASION":"This event has been marked as abuse","messageMarkedAsAbuse@board:IDEA":"This idea has been marked as abuse","preModCommentAuthorText":"This comment will be published as soon as it is approved","preModCommentModeratorText":"This comment is awaiting moderation","messageMarkedAsOther":"This post has been rejected due to other reasons","messageMarkedAsOther@board:TKB":"This article has been rejected due to other reasons","messageMarkedAsOther@board:BLOG":"This post has been rejected due to other reasons","messageMarkedAsOther@board:FORUM":"This discussion has been rejected due to other reasons","messageMarkedAsOther@board:OCCASION":"This event has been rejected due to other reasons","messageMarkedAsOther@board:IDEA":"This idea has been rejected due to other reasons","messageArchived":"This post was archived on {date}","relatedUrl":"View Related Content","relatedContentText":"Showing related content","archivedContentLink":"View Archived Content"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewStandard-1745505307000","value":{"anonymous":"Anonymous","author":"{messageAuthorLogin}","authorBy":"{messageAuthorLogin}","board":"{messageBoardTitle}","replyToUser":" to {parentAuthor}","showMoreReplies":"Show More","replyText":"Reply","repliesText":"Replies","markedAsSolved":"Marked as Solution","movedMessagePlaceholder.BLOG":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.TKB":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.FORUM":"{count, plural, =0 {This reply has been} other {These replies have been} }","movedMessagePlaceholder.IDEA":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.OCCASION":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholderUrlText":"moved.","messageStatus":"Status: ","statusChanged":"Status changed: {previousStatus} to {currentStatus}","statusAdded":"Status added: {status}","statusRemoved":"Status removed: {status}","labelExpand":"expand replies","labelCollapse":"collapse replies","unhelpfulReason.reason1":"Content is outdated","unhelpfulReason.reason2":"Article is missing information","unhelpfulReason.reason3":"Content is for a different Product","unhelpfulReason.reason4":"Doesn't match what I was searching for"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1745505307000","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyCallToAction-1745505307000","value":{"leaveReply":"Leave a reply...","leaveReply@board:BLOG@message:root":"Leave a comment...","leaveReply@board:TKB@message:root":"Leave a comment...","leaveReply@board:IDEA@message:root":"Leave a comment...","leaveReply@board:OCCASION@message:root":"Leave a comment...","repliesTurnedOff.FORUM":"Replies are turned off for this topic","repliesTurnedOff.BLOG":"Comments are turned off for this topic","repliesTurnedOff.TKB":"Comments are turned off for this topic","repliesTurnedOff.IDEA":"Comments are turned off for this topic","repliesTurnedOff.OCCASION":"Comments are turned off for this topic","infoText":"Stop poking me!"},"localOverride":false},"Category:category:Exchange":{"__typename":"Category","id":"category:Exchange","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Outlook":{"__typename":"Category","id":"category:Outlook","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Community-Info-Center":{"__typename":"Category","id":"category:Community-Info-Center","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:DrivingAdoption":{"__typename":"Category","id":"category:DrivingAdoption","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Azure":{"__typename":"Category","id":"category:Azure","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows-Server":{"__typename":"Category","id":"category:Windows-Server","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftTeams":{"__typename":"Category","id":"category:MicrosoftTeams","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PublicSector":{"__typename":"Category","id":"category:PublicSector","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft365":{"__typename":"Category","id":"category:microsoft365","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:IoT":{"__typename":"Category","id":"category:IoT","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:HealthcareAndLifeSciences":{"__typename":"Category","id":"category:HealthcareAndLifeSciences","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:ITOpsTalk":{"__typename":"Category","id":"category:ITOpsTalk","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftLearn":{"__typename":"Category","id":"category:MicrosoftLearn","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Blog:board:MicrosoftLearnBlog":{"__typename":"Blog","id":"board:MicrosoftLearnBlog","blogPolicies":{"__typename":"BlogPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:AI":{"__typename":"Category","id":"category:AI","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftMechanics":{"__typename":"Category","id":"category:MicrosoftMechanics","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftforNonprofits":{"__typename":"Category","id":"category:MicrosoftforNonprofits","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:StartupsatMicrosoft":{"__typename":"Category","id":"category:StartupsatMicrosoft","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PartnerCommunity":{"__typename":"Category","id":"category:PartnerCommunity","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Microsoft365Copilot":{"__typename":"Category","id":"category:Microsoft365Copilot","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows":{"__typename":"Category","id":"category:Windows","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Content_Management":{"__typename":"Category","id":"category:Content_Management","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft-security":{"__typename":"Category","id":"category:microsoft-security","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoftintune":{"__typename":"Category","id":"category:microsoftintune","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"CachedAsset:text:en_US-components/community/Navbar-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1745505307000","value":{"community":"Community Home","inbox":"Inbox","manageContent":"Manage Content","tos":"Terms of Service","forgotPassword":"Forgot Password","themeEditor":"Theme Editor","edit":"Edit Navigation Bar","skipContent":"Skip to content","gxcuf89792":"Tech Community","external-1":"Events","s-m-b":"Nonprofit Community","windows-server":"Windows Server","education-sector":"Education Sector","driving-adoption":"Driving Adoption","Common-content_management-link":"Content Management","microsoft-learn":"Microsoft Learn","s-q-l-server":"Content Management","partner-community":"Microsoft Partner Community","microsoft365":"Microsoft 365","external-9":".NET","external-8":"Teams","external-7":"Github","products-services":"Products","external-6":"Power Platform","communities-1":"Topics","external-5":"Microsoft Security","planner":"Outlook","external-4":"Microsoft 365","external-3":"Dynamics 365","azure":"Azure","healthcare-and-life-sciences":"Healthcare and Life Sciences","external-2":"Azure","microsoft-mechanics":"Microsoft Mechanics","microsoft-learn-1":"Community","external-10":"Learning Room Directory","microsoft-learn-blog":"Blog","windows":"Windows","i-t-ops-talk":"ITOps Talk","external-link-1":"View All","microsoft-securityand-compliance":"Microsoft Security","public-sector":"Public Sector","community-info-center":"Lounge","external-link-2":"View All","microsoft-teams":"Microsoft Teams","external":"Blogs","microsoft-endpoint-manager":"Microsoft Intune","startupsat-microsoft":"Startups at Microsoft","exchange":"Exchange","a-i":"AI and Machine Learning","io-t":"Internet of Things (IoT)","Common-microsoft365-copilot-link":"Microsoft 365 Copilot","outlook":"Microsoft 365 Copilot","external-link":"Community Hubs","communities":"Products"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1745505307000","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1745505307000","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1745505307000","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1745505307000","value":{"title.login":"Sign In","title.registration":"Register","title.forgotPassword":"Forgot Password","title.multiAuthLogin":"Sign In"},"localOverride":false},"CachedAsset:text:en_US-components/nodes/NodeLink-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1745505307000","value":{"place":"Place {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCoverImage-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCoverImage-1745505307000","value":{"coverImageTitle":"Cover Image"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeTitle-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeTitle-1745505307000","value":{"nodeTitle":"{nodeTitle, select, community {Community} other {{nodeTitle}}} "},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTimeToRead-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTimeToRead-1745505307000","value":{"minReadText":"{min} MIN READ"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageSubject-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1745505307000","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1745505307000","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserRank-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserRank-1745505307000","value":{"rankName":"{rankName}","userRank":"Author rank {rankName}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTime-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTime-1745505307000","value":{"postTime":"Published: {time}","lastPublishTime":"Last Update: {time}","conversation.lastPostingActivityTime":"Last posting activity time: {time}","conversation.lastPostTime":"Last post time: {time}","moderationData.rejectTime":"Rejected time: {time}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1745505307000","value":{"showMessageBody":"Show More","mentionsErrorTitle":"{mentionsType, select, board {Board} user {User} message {Message} other {}} No Longer Available","mentionsErrorMessage":"The {mentionsType} you are trying to view has been removed from the community.","videoProcessing":"Video is being processed. Please try again in a few minutes.","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCustomFields-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCustomFields-1745505307000","value":{"CustomField.default.label":"Value of {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRevision-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRevision-1745505307000","value":{"lastUpdatedDatePublished":"{publishCount, plural, one{Published} other{Updated}} {date}","lastUpdatedDateDraft":"Created {date}","version":"Version {major}.{minor}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1745505307000","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyButton-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyButton-1745505307000","value":{"repliesCount":"{count}","title":"Reply","title@board:BLOG@message:root":"Comment","title@board:TKB@message:root":"Comment","title@board:IDEA@message:root":"Comment","title@board:OCCASION@message:root":"Comment"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageAuthorBio-1745505307000","value":{"sendMessage":"Send Message","actionMessage":"Follow this blog board to get notified when there's new activity","coAuthor":"CO-PUBLISHER","contributor":"CONTRIBUTOR","userProfile":"View Profile","iconlink":"Go to {name} {type}"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1745505307000","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1745505307000","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1745505307000","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserRegistrationDate-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserRegistrationDate-1745505307000","value":{"noPrefix":"{date}","withPrefix":"Joined {date}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeAvatar-1745505307000","value":{"altTitle":"Node avatar for {nodeTitle}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeDescription-1745505307000","value":{"description":"{description}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeIcon-1745505307000","value":{"contentType":"Content Type {style, select, FORUM {Forum} BLOG {Blog} TKB {Knowledge Base} IDEA {Ideas} OCCASION {Events} other {}} icon"},"localOverride":false}}}},"page":"/blogs/BlogMessagePage/BlogMessagePage","query":{"boardId":"educatordeveloperblog","messageSubject":"step-by-step-contact-center-chat-analysis-with-azure-openai--communication-servi","messageId":"4403710"},"buildId":"YK32GCbhJqbL-HLk4DLXM","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"o365","openTelemetryServiceVersion":"25.3.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/customComponent/CustomComponent/CustomComponent.tsx","./components/blogs/BlogArticleWidget/BlogArticleWidget.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/messages/ThreadedReplyList/ThreadedReplyList.tsx","./components/external/components/ExternalComponent.tsx","./components/customComponent/CustomComponentContent/TemplateContent.tsx"],"appGip":true,"scriptLoader":[{"id":"analytics","src":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/pagescripts/1730819800000/analytics.js?page.id=BlogMessagePage&entity.id=board%3Aeducatordeveloperblog&entity.id=message%3A4403710","strategy":"afterInteractive"}]}