This is a follow up question on this question about reopening a historic conversation
In line with the chatinterface of ChatGPT I would like to allow users of a webchat interface to reopen historic conversation.
What is working
- Conversation metadata is stored in table storage
- The transcripts are stored via middleware in blob storage
- Users can start a new conversation in the web chat agent or select an old conversation
- In the last case, a new conversation is started and the activities of the selected old conversationId are retrieved from transcriptblogstorage
-> These activities are send to the conversation via conversations.sendConversationHistory
The last step only works in the emulator. Not when the webchat agent is used.
What is not working
In webchat, the activities sent via sendConversationHistory are not visible
When I add logging:
const result = await connector.conversations.sendConversationHistory(turnContext.activity.conversation.id, { activities: validActivities });
debuggingMode && console.log('Conversation history sent successfully.');
console.log(result);
When using the emulator I see something like this and the old the historic conversation is visible in the emulator chatwindow
Conversation history sent successfully.
{ id: 'Processed 7 of 7 activities successfully.' }
When using webchat I see this and nothing is visible in the webchat chatwindow
Conversation history sent successfully.
{}
I understand that webchat (or DIrectLine) will be more strict and the emulator is more forgiving. I am unable however to find any documentation about what is required and what not.
What did I already try
.1 Based on what I found on SO I tried multiple ways to structure the activities. Not with good result.
.2 I tried to send the activities in Postman to try and figure out what the best way was.
Sending the activities to https://europe.directline.botframework.com/v3/directline/conversations/<conversationId/activities/history/ results in The requested resource does not support http method 'POST'
Sending the activities to https://europe.directline.botframework.com/v3/directline/conversations/<conversationId/activities results in
{
"error": {
"code": "MissingProperty",
"message": "Missing 'Activity.From' field"
}
}
whatever I try with the from fields.
What do I need help with
I need to understand what the structure of the activities is that will be accepted and processed. When using sendConversationHistory in combination with webchat
I am using "botbuilder": "^4.21.0",
the connector is created like this:
const { BotFrameworkAdapter, ConversationState, MemoryStorage } = require('botbuilder');
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
This is a sample of the current set of activities that is sent. What is important (eg ids that are unique and incremental) is correct in my opinion
{
"activities": [
{
"text": "Hello! Select a button.",
"inputHint": "acceptingInput",
"channelId": "directline",
"locale": "en-US",
"serviceUrl": "https://europe.directline.botframework.com/",
"conversation": {
"id": "<conversationid>"
},
"from": {
"id": "<botid>",
"name": "Bot",
"role": "bot"
},
"recipient": {
"id": "<userd>",
"name": "User",
"role": "user"
},
"type": "message",
"id": "<conversationid>|0000001",
"timestamp": "2024-11-20T16:40:25.396Z"
},
{
"text": "Selected Button Text",
"textFormat": "plain",
"type": "message",
"channelId": "directline",
"from": {
"id": "<userd>",
"name": "User",
"role": "user"
},
"locale": "en-US",
"localTimestamp": "2024-11-20T14:05:02.000Z",
"localTimezone": "Europe/Amsterdam",
"attachments": [],
"entities": [
{
"requiresBotState": true,
"supportsListening": true,
"supportsTts": true,
"type": "ClientCapabilities"
}
],
"conversation": {
"id": "<conversationid>"
},
"id": "<conversationid>|0000002",
"recipient": {
"id": "<botid>",
"name": "Bot",
"role": "bot"
},
"timestamp": "2024-11-20T16:40:25.396Z",
"serviceUrl": "https://europe.directline.botframework.com/",
"rawTimestamp": "2024-11-20T14:05:02.362Z",
"rawLocalTimestamp": "2024-11-20T15:05:02+01:00",
"callerId": "urn:botframework:azure"
}
]
}
The code that retrieves the historic activities from transcript log storage and sends in to directline
async function loadAndSendConversationHistory(turnContext, transcriptStore, conversationId, conversationHistoryAccessor, userId) {
// Retrieve saved conversation reference
const savedConversationReference = await getConversationReference(userId, conversationId);
if (!savedConversationReference) {
console.error('No saved conversation reference found.');
return;
}
// Retrieve transcript activities
const rawActivities = [];
let continuationToken;
do {
const page = await transcriptStore.getTranscriptActivities(turnContext.activity.channelId, conversationId, continuationToken);
continuationToken = page.continuationToken;
rawActivities.push(...page.items);
} while (continuationToken);
// Validate and filter activities
const validActivities = rawActivities.filter(activity => {
if (!validateActivity(activity)) {
debuggingMode && console.log(`Invalid activity filtered out: ${JSON.stringify(activity)}`);
return false;
}
// Remove suggestedActions for activities without text
if (!activity.text && activity.suggestedActions) {
delete activity.suggestedActions;
}
return true;
});
// Assign sequential IDs only to valid activities
validActivities.forEach((activity, index) => {
assignActivityDefaults(activity, conversationId, index + 1);
});
debuggingMode && console.log(`Activities after assigning IDs: ${JSON.stringify(validActivities, null, 2)}`);
try {
const connector = turnContext.adapter.createConnectorClient(turnContext.activity.serviceUrl);
const conversations = await connector.conversations.getConversations();
const result = await connector.conversations.sendConversationHistory(turnContext.activity.conversation.id, { activities: validActivities });
debuggingMode && console.log('Conversation history sent successfully.');
console.log(result);
} catch (error) {
console.error('Error sending activities:', error);
}
}
// Helper function to validate activity structure
function validateActivity(activity) {
const requiredFields = ["type", "id", "timestamp", "from", "recipient", "conversation", "serviceUrl", "text"];
const hasRequiredFields = requiredFields.every(field => field in activity);
if (!hasRequiredFields) {
debuggingMode && console.log('Activity validation failed:', activity);
return false;
}
// Additional checks for fields
if (!activity.from.id || !activity.recipient.id || !activity.conversation.id) {
debuggingMode && console.log('Activity has missing identifiers:', activity);
return false;
}
return true;
}
// Helper function to assign default values and remove irrelevant fields
function assignActivityDefaults(activity, conversationId, sequenceNumber) {
activity.id = `${conversationId}|${sequenceNumber.toString().padStart(7, '0')}`;
activity.timestamp = new Date().toISOString(); // Use current timestamp
activity.serviceUrl = "https://europe.directline.botframework.com/"; // Set service URL
activity.channelId = "directline"; // Set channel ID
// Default values for missing fields
if (!activity.from.name) {
activity.from.name = "User"; // Default to empty string
}
if (!activity.recipient.name) {
activity.recipient.name = "Bot"; // Default recipient name
}
// Remove irrelevant fields
delete activity.channelData;
delete activity.replyToId;
}