Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions TypeaheadSuggestions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Typeahed Suggestions for Copilot Studio
This code sample allows you to create a typeahead suggestion functionality for your custome copilot that can assist users finding things like frequently asked questions, auto correcting typos and showing a list of menu items like product names or topic names before sending a message to the copilot. The version one uses an API that returns topic names for given bot but the sample can be used with any RestFul endpoint to show relevant suggestions based on your usecase.

Please note that version 1 calls the API once and loads the suggestions for your bot when the chat is initiated. It then filters through these suggestions based on users questions.
Please note that version 2 which is built using REACT component calls the API every time users asks a questions and shows the relevant suggestion based on each question when user type '/'



## Getting Started

1. Create your custom copilot using copilot studio with required topics and actions.
2. Once you have published your copilot go to mobile channel and copy the directline endpoint url.
3. Download the TypeAheadSuggestion.HTML canvas to your local machine and open in any code editor.
4. On line 158 replace the direct enpoint you copied for your copilot. Please note that for best practices you can use directline token instead of using secret in the canvas.
<img width="853" alt="image" src="https://github.com/user-attachments/assets/fd8a9800-2e95-40e1-b10f-a8b060fa61bb">


5. Next you will need to add a RestAPI that returns an array of suggestions for your bot. For testing, you can use the TypeAheadSolutionAPI Zip file that has a power automate flow that returns topic names for a given copilot bot. On the power automate flow you will need to edit the list row action and replace the exisiting botid with your botid.

<img width="731" alt="image" src="https://github.com/user-attachments/assets/628fa504-e534-4e5c-bd03-29fa351d96bf">




7. On line 222 Add your RestAPI endpoint that returns an array of suggested topics.
<img width="830" alt="image" src="https://github.com/user-attachments/assets/58f1608e-c87f-42e9-b8de-547142edefe6">


8. Optional : If you would like to exclude any suggestions based on your business logic you can create a exclusion list as shown below on line 173.
<img width="306" alt="image" src="https://github.com/user-attachments/assets/c7abb501-e3f4-4e67-a6d4-c7852fadca4a">

9. Click Save and open the file in the browser to test. You can launch the bot by clicking on the chat bubble and initiating the chat.

10. To see list of suggestions before typying click the space bar which opens a dropdow list of suggested topcis

11. For getting type ahead suggestions continue to type in the chat bot canvas and suggestions will be shown below the chat area. Clicking on any suggestion submits the utterance to the bot.

# Watch below Demo to Get Started with Version 1

[![Watch the video](https://th.bing.com/th/id/OIP.9k6Gz3sbmi5b8r6YxTSG-QHaEK?w=289&h=180&c=7&r=0&o=5&dpr=1.5&pid=1.7
)](https://www.youtube.com/watch?v=7xbSpzmQcIg)

# Watch below Demo to Get Started with Version 2
[![Watch the video](https://th.bing.com/th/id/OIP.9k6Gz3sbmi5b8r6YxTSG-QHaEK?w=289&h=180&c=7&r=0&o=5&dpr=1.5&pid=1.7
)](https://youtu.be/H9_r7R4iNOU)







Binary file not shown.
308 changes: 308 additions & 0 deletions TypeaheadSuggestions/TypeAheadSuggestions_V1.0.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Copilot Studio Intelligent Suggestions Version 1.0</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
height: 100vh;
display: flex;
background: url('https://github.com/paragdessai/PVAClickAnalytics/blob/main/Chatbotbackground.png?raw=true') no-repeat center center;
background-size: cover;
justify-content: center;
align-items: center;
}

/* Chat bubble style */
#chatBubble {
position: fixed;
bottom: 30px;
right: 30px;
background-color: #0078D4;
color: white;
padding: 15px;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

/* Bot container hidden initially */
#botContainer {
position: fixed;
display: none;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
z-index: 999;
background: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
}

/* Chat window style */
.chat-window {
background-color: white;
border-radius: 8px;
width: 600px;
height: 600px;
display: flex;
flex-direction: column;
position: relative;
padding: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

/* Close button inside chat */
#closeBot {
position: absolute;
top: 10px;
right: 50px;
background-color: #ff4d4f;
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
text-align: center;
font-weight: bold;
line-height: 30px;
}

/* Refresh button inside chat */
#refreshBot {
position: absolute;
top: 10px;
right: 10px;
background-color: #4CAF50; /* Green */
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
text-align: center;
font-weight: bold;
line-height: 30px;
}

#webchat {
flex: 1;
border: 1px solid #ddd;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.9);
margin-top: 20px;
overflow: hidden;
position: relative; /* Make it a positioning context */
}

/* Suggestions list Styling */
.suggestion-list {
position: relative;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
width: calc(100% - 20px);
max-width: 560px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
padding: 0;
display: none;
margin-top: 5px;
}

.suggestion-item {
padding: 10px;
cursor: pointer;
list-style-type: none;
border-bottom: 1px solid #ddd;
}

.suggestion-item:hover {
background-color: #f4f4f4;
}

.suggestion-item:last-child {
border-bottom: none;
}
</style>
</head>
<body>

<!-- Chat bubble to open the bot -->
<div id="chatBubble">💬</div>

<!-- Bot container with chat window -->
<div id="botContainer">
<div class="chat-window">
<button id="closeBot">X</button>
<button id="refreshBot">🔄</button> <!-- Refresh button -->
<div id="webchat" role="main"></div>
<ul id="suggestions" class="suggestion-list"></ul> <!-- Suggestions now positioned relative to the input -->
</div>
</div>

<!-- Include BotFramework WebChat -->
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>

<script>
let directLine;

// Function to retrieve the Direct Line token
async function getDirectLineToken() {
try {
const response = await fetch('Insert your directline token API');
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
return data.token; // Assuming the token is in the 'token' field
} catch (error) {
console.error('Error fetching token:', error);
return null;
}
}

// Direct Line connection initialization
async function initializeDirectLine() {
const token = await getDirectLineToken(); // Fetch the token
if (token) {
directLine = window.WebChat.createDirectLine({
token: token // Use the fetched token
});

// Render Web Chat in bot container
window.WebChat.renderWebChat(
{
directLine: directLine,
userID: 'user1',
username: 'User',
locale: 'en-US'
},
document.getElementById('webchat')
);

// Listen for user input in Web Chat (via Direct Line connection)
directLine.activity$.subscribe(activity => {
if (activity.type === 'message' && activity.from.id === 'user1') {
handleInput(activity.text); // Handle input to show suggestions
}
});
} else {
console.error('Failed to initialize Direct Line due to missing token');
}
}

// Initialize topics array
let topics = [];

// List of values to exclude from API Response
const excludedTopics = [
'Reset Conversation',
'Thank you',
'Sign in',
'Sign in ',
'Conversation Start',
'End of Conversation',
'On Error',
'Fallback',
'Conversational boosting',
'Multiple Topics Matched',
'Escalate',
'Start Over'
];

const suggestionsList = document.getElementById('suggestions');

// Function to fetch Suggestion using API. The API needs to return an Array of Suggestions
async function fetchTopics() {
try {
const response = await fetch('https://prod-177.westus.logic.azure.com:443/workflows/7539cccafbaa45a3b139c8e83492aa44/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=1iEAjAT50LuQI_54hHJYult5P1ygpj9G3i2qxQsoOKI'); // Add your RestAPI here
if (!response.ok) throw new Error('Network response was not ok');
const fetchedTopics = await response.json(); // Get the response data

// Filter out excluded topics
topics = fetchedTopics.filter(topic => !excludedTopics.includes(topic));
console.log('Topics fetched:', topics);
} catch (error) {
console.error('Error fetching topics:', error);
}
}

// Function to handle user typing and show suggestions
function handleInput(input) {
const userInput = input.toLowerCase();
suggestionsList.innerHTML = ''; // Clear previous suggestions

if (userInput) {
const filteredTopics = topics.filter(topic => topic.toLowerCase().includes(userInput));

// Show suggestions list only if there are filtered topics
if (filteredTopics.length > 0) {
filteredTopics.forEach(topic => {
const suggestionItem = document.createElement('li');
suggestionItem.classList.add('suggestion-item');
suggestionItem.textContent = topic;

// Handle click on suggestion
suggestionItem.addEventListener('click', function() {
if (directLine) {
// Send the selected suggestion as a message to the bot
directLine.postActivity({
from: { id: 'user1' }, // User ID
type: 'message',
text: topic // Send the selected topic
}).subscribe(
id => console.log('Message sent with ID:', id),
error => console.error('Error sending message:', error)
);

suggestionsList.style.display = 'none'; // Hide suggestions after selection
} else {
console.error('DirectLine is not initialized');
}
});

suggestionsList.appendChild(suggestionItem);
});

suggestionsList.style.display = 'block'; // Show suggestions list
} else {
suggestionsList.style.display = 'none'; // Hide if no suggestions
}
} else {
suggestionsList.style.display = 'none'; // Hide if input is cleared
}
}

// Open bot on chat bubble click
document.getElementById('chatBubble').addEventListener('click', async function() {
const botContainer = document.getElementById('botContainer');
botContainer.style.display = 'flex'; // Show the bot container
this.style.display = 'none'; // Hide chat bubble

await fetchTopics(); // Fetch topics when the chat bubble is clicked
initializeDirectLine(); // Initialize Direct Line after fetching topics
});

// Close bot on 'X' button click
document.getElementById('closeBot').addEventListener('click', function() {
document.getElementById('botContainer').style.display = 'none'; // Hide bot container
document.getElementById('chatBubble').style.display = 'block'; // Show chat bubble
});

// Refresh page on refresh button click
document.getElementById('refreshBot').addEventListener('click', function() {
location.reload(); // Refresh the page
});

// Display suggestions based on user typing in the web chat
document.getElementById('webchat').addEventListener('input', function(event) {
const userInput = event.target.value;
handleInput(userInput); // Call handleInput function to show suggestions
});
</script>
</body>
</html>
Loading