-
Notifications
You must be signed in to change notification settings - Fork 209
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DO NOT MERGE [JS] feat: custom feedback form + citation changes (#2182)
## Linked issues closes: #2167 ## Details update types/handlers for new custom feedback loop and citations changes for ignite.
- Loading branch information
Showing
9 changed files
with
248 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import sinon from 'sinon'; | ||
import { strict as assert } from 'assert'; | ||
import { ActivityTypes, Channels, INVOKE_RESPONSE_KEY, TestAdapter } from 'botbuilder'; | ||
|
||
import { Application } from './Application'; | ||
import { createTestInvoke } from './internals/testing/TestUtilities'; | ||
import { MessageInvokeNames, Messages } from './Messages'; | ||
|
||
describe('Messages', () => { | ||
const adapter = new TestAdapter(); | ||
let mockApp: Application; | ||
|
||
beforeEach(() => { | ||
mockApp = new Application(); | ||
sinon.stub(mockApp, 'adapter').get(() => adapter); | ||
}); | ||
|
||
it('should exist when Application is instantiated', () => { | ||
assert.notEqual(mockApp.messages, undefined); | ||
assert.equal(mockApp.messages instanceof Messages, true); | ||
}); | ||
|
||
describe(MessageInvokeNames.FETCH_INVOKE_NAME, () => { | ||
it('fetch() with custom RouteSelector handler result is falsy', async () => { | ||
const activity = createTestInvoke(MessageInvokeNames.FETCH_INVOKE_NAME, {}); | ||
activity.channelId = Channels.Msteams; | ||
mockApp.messages.fetch(async (_context, _state, _data) => { | ||
return {}; | ||
}); | ||
|
||
await adapter.processActivity(activity, async (context) => { | ||
await mockApp.run(context); | ||
const response = context.turnState.get(INVOKE_RESPONSE_KEY); | ||
assert.deepEqual(response.value, { | ||
status: 200, | ||
body: { task: { type: 'continue', value: {} } } | ||
}); | ||
}); | ||
}); | ||
|
||
it('fetch() with custom RouteSelector unhappy path', async () => { | ||
const activity = { channelId: Channels.Msteams, type: ActivityTypes.Invoke, name: 'incorrectName' }; | ||
const spy = sinon.spy(async (context, _state, _data) => { | ||
return Promise.resolve(''); | ||
}); | ||
|
||
mockApp.messages.fetch(spy); | ||
|
||
await adapter.processActivity(activity, async (context) => { | ||
await mockApp.run(context); | ||
}); | ||
|
||
assert.equal(spy.called, false); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/** | ||
* @module teams-ai | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { | ||
ActivityTypes, | ||
Channels, | ||
INVOKE_RESPONSE_KEY, | ||
InvokeResponse, | ||
TaskModuleResponse, | ||
TaskModuleTaskInfo, | ||
TurnContext | ||
} from 'botbuilder'; | ||
import { Application } from './Application'; | ||
import { TurnState } from './TurnState'; | ||
|
||
export enum MessageInvokeNames { | ||
FETCH_INVOKE_NAME = `message/fetchTask` | ||
} | ||
|
||
/** | ||
* TaskModules class to enable fluent style registration of handlers related to Task Modules. | ||
* @template TState Type of the turn state object being persisted. | ||
*/ | ||
export class Messages<TState extends TurnState> { | ||
private readonly _app: Application<TState>; | ||
|
||
/** | ||
* Creates a new instance of the TaskModules class. | ||
* @param {Application} app Top level application class to register handlers with. | ||
*/ | ||
public constructor(app: Application<TState>) { | ||
this._app = app; | ||
} | ||
|
||
/** | ||
* Registers a handler to process the initial fetch of the task module. | ||
* @remarks | ||
* Handlers should respond with either an initial TaskInfo object or a string containing | ||
* a message to display to the user. | ||
* @template TData Optional. Type of the data object being passed to the handler. | ||
* @param {(context: TurnContext, state: TState, data: TData) => Promise<TaskModuleTaskInfo | string>} handler - Function to call when the handler is triggered. | ||
* @param {TurnContext} handler.context - Context for the current turn of conversation with the user. | ||
* @param {TState} handler.state - Current state of the turn. | ||
* @param {TData} handler.data - Data object passed to the handler. | ||
* @returns {Application<TState>} The application for chaining purposes. | ||
*/ | ||
public fetch<TData extends Record<string, any> = Record<string, any>>( | ||
handler: (context: TurnContext, state: TState, data: TData) => Promise<TaskModuleTaskInfo | string> | ||
): Application<TState> { | ||
this._app.addRoute( | ||
async (context) => { | ||
return ( | ||
context?.activity?.type === ActivityTypes.Invoke && | ||
context?.activity?.name === MessageInvokeNames.FETCH_INVOKE_NAME | ||
); | ||
}, | ||
async (context, state) => { | ||
if (context?.activity?.channelId === Channels.Msteams) { | ||
const result = await handler(context, state, context.activity.value?.data ?? {}); | ||
|
||
if (!context.turnState.get(INVOKE_RESPONSE_KEY)) { | ||
// Format invoke response | ||
let response: TaskModuleResponse; | ||
if (typeof result == 'string') { | ||
// Return message | ||
response = { | ||
task: { | ||
type: 'message', | ||
value: result | ||
} | ||
}; | ||
} else { | ||
// Return card | ||
response = { | ||
task: { | ||
type: 'continue', | ||
value: result | ||
} | ||
}; | ||
} | ||
|
||
// Queue up invoke response | ||
await context.sendActivity({ | ||
value: { body: response, status: 200 } as InvokeResponse, | ||
type: ActivityTypes.InvokeResponse | ||
}); | ||
} | ||
} | ||
}, | ||
true | ||
); | ||
|
||
return this._app; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.