UNPKG

node-nlp

Version:

Library for NLU (Natural Language Understanding) done in Node.js

259 lines 9.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * @module botbuilder */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const fs = require("async-file"); const botbuilder_core_1 = require("botbuilder-core"); const filenamify = require("filenamify"); const path = require("path"); const rimraf = require("rimraf"); /** * The file transcript store stores transcripts in file system with each activity as a file. * * @remarks * This class provides an interface to log all incoming and outgoing activities to the filesystem. * It implements the features necessary to work alongside the TranscriptLoggerMiddleware plugin. * When used in concert, your bot will automatically log all conversations. * * Below is the boilerplate code needed to use this in your app: * ```javascript * const { FileTranscriptStore, TranscriptLoggerMiddleware } = require('botbuilder'); * * adapter.use(new TranscriptLoggerMiddleware(new FileTranscriptStore(__dirname + '/transcripts/'))); * ``` */ class FileTranscriptStore { /** * Creates an instance of FileTranscriptStore. * @param folder Root folder where transcript will be stored. */ constructor(folder) { if (!folder) { throw new Error('Missing folder.'); } this.rootFolder = folder; } /** * Log an activity to the transcript. * @param activity Activity being logged. */ logActivity(activity) { if (!activity) { throw new Error('activity cannot be null for logActivity()'); } const conversationFolder = this.getTranscriptFolder(activity.channelId, activity.conversation.id); const activityFileName = this.getActivityFilename(activity); return this.saveActivity(activity, conversationFolder, activityFileName); } /** * Get all activities associated with a conversation id (aka get the transcript). * @param channelId Channel Id. * @param conversationId Conversation Id. * @param continuationToken (Optional) Continuation token to page through results. * @param startDate (Optional) Earliest time to include. */ getTranscriptActivities(channelId, conversationId, continuationToken, startDate) { if (!channelId) { throw new Error('Missing channelId'); } if (!conversationId) { throw new Error('Missing conversationId'); } const pagedResult = new botbuilder_core_1.PagedResult(); const transcriptFolder = this.getTranscriptFolder(channelId, conversationId); return fs.exists(transcriptFolder).then((exists) => { if (!exists) { return pagedResult; } return fs.readdir(transcriptFolder) .then((files) => files .filter((f) => f.endsWith('.json')) // .json only .sort() // sorted .filter(withDateFilter(startDate))) // >= startDate .then((files) => { if (continuationToken) { return files .filter(withContinuationToken(continuationToken)) .slice(1, FileTranscriptStore.PageSize + 1); } else { return files.slice(0, FileTranscriptStore.PageSize); } }) .then((files) => files.map((activityFilename) => fs.readFile(path.join(transcriptFolder, activityFilename), 'utf8'))) .then((reads) => Promise.all(reads)) .then((jsons) => { const items = jsons.map(parseActivity); pagedResult.items = items; if (pagedResult.items.length === FileTranscriptStore.PageSize) { pagedResult.continuationToken = pagedResult.items[pagedResult.items.length - 1].id; } return pagedResult; }); }); } /** * List all the logged conversations for a given channelId. * @param channelId Channel Id. * @param continuationToken (Optional) Continuation token to page through results. */ listTranscripts(channelId, continuationToken) { if (!channelId) { throw new Error('Missing channelId'); } const pagedResult = new botbuilder_core_1.PagedResult(); const channelFolder = this.getChannelFolder(channelId); return fs.exists(channelFolder).then((exists) => { if (!exists) { return pagedResult; } return fs.readdir(channelFolder) .then((dirs) => { let items = []; if (continuationToken) { items = dirs .filter(skipWhileExpression((di) => di !== continuationToken)) .slice(1, FileTranscriptStore.PageSize + 1); } else { items = dirs.slice(0, FileTranscriptStore.PageSize); } pagedResult.items = items.map((i) => ({ channelId: channelId, id: i, created: null })); if (pagedResult.items.length === FileTranscriptStore.PageSize) { pagedResult.continuationToken = pagedResult.items[pagedResult.items.length - 1].id; } return pagedResult; }); }); } /** * Delete a conversation and all of it's activities. * @param channelId Channel Id where conversation took place. * @param conversationId Id of the conversation to delete. */ deleteTranscript(channelId, conversationId) { if (!channelId) { throw new Error('Missing channelId'); } if (!conversationId) { throw new Error('Missing conversationId'); } const transcriptFolder = this.getTranscriptFolder(channelId, conversationId); return new Promise((resolve) => rimraf(transcriptFolder, resolve)); } saveActivity(activity, transcriptPath, activityFilename) { const json = JSON.stringify(activity, null, '\t'); return this.ensureFolder(transcriptPath).then(() => { return fs.writeFile(path.join(transcriptPath, activityFilename), json, 'utf8'); }); } // tslint:disable-next-line:no-shadowed-variable ensureFolder(path) { return fs.exists(path).then((exists) => { if (!exists) { return fs.mkdirp(path); } }); } getActivityFilename(activity) { return `${getTicks(activity.timestamp)}-${this.sanitizeKey(activity.id)}.json`; } getChannelFolder(channelId) { return path.join(this.rootFolder, this.sanitizeKey(channelId)); } getTranscriptFolder(channelId, conversationId) { return path.join(this.rootFolder, this.sanitizeKey(channelId), this.sanitizeKey(conversationId)); } sanitizeKey(key) { return filenamify(key); } } FileTranscriptStore.PageSize = 20; exports.FileTranscriptStore = FileTranscriptStore; /** * @private * The number of .net ticks at the unix epoch. */ const epochTicks = 621355968000000000; /** * @private * There are 10000 .net ticks per millisecond. */ const ticksPerMillisecond = 10000; /** * @private * @param timestamp A date used to calculate future ticks. */ function getTicks(timestamp) { const ticks = epochTicks + (timestamp.getTime() * ticksPerMillisecond); return ticks.toString(16); } /** * @private * @param ticks A string containing ticks. */ function readDate(ticks) { const t = Math.round((parseInt(ticks, 16) - epochTicks) / ticksPerMillisecond); return new Date(t); } /** * @private * @param date A date used to create a filter. */ function withDateFilter(date) { if (!date) { return () => true; } return (filename) => { const ticks = filename.split('-')[0]; return readDate(ticks) >= date; }; } /** * @private * @param continuationToken A continuation token. */ function withContinuationToken(continuationToken) { if (!continuationToken) { return () => true; } return skipWhileExpression((fileName) => { const id = fileName.substring(fileName.indexOf('-') + 1, fileName.indexOf('.')); return id !== continuationToken; }); } /** * @private * @param expression A function that will be used to test items. */ function skipWhileExpression(expression) { let skipping = true; return (item) => { if (!skipping) { return true; } if (!expression(item)) { skipping = false; } return !skipping; }; } /** * @private * @param json A JSON string to be parsed into an activity. */ function parseActivity(json) { const activity = JSON.parse(json); activity.timestamp = new Date(activity.timestamp); return activity; } //# sourceMappingURL=fileTranscriptStore.js.map