Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] Migration of chat feature UI business logic #688

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@
"ENABLE_CUSTOMIZATIONS": "true"
// "HTTPS_PROXY": "http://127.0.0.1:8888",
// "AWS_CA_BUNDLE": "/path/to/cert.pem"
},
"preLaunchTask": "npm: compile"
}
// "preLaunchTask": "npm: compile"
},
{
"name": "CodeWhisperer Server IAM",
Expand Down
45 changes: 31 additions & 14 deletions chat-client/src/client/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { ServerMessage, TELEMETRY, TelemetryParams } from '../contracts/serverCo
import { Messager, OutboundChatApi } from './messager'
import { InboundChatApi, createMynahUi } from './mynahUi'
import { TabFactory } from './tabs/tabFactory'
import { Connector } from '../connectors/connector'

const DEFAULT_TAB_DATA = {
tabTitle: 'Chat',
Expand All @@ -56,19 +57,29 @@ type ChatClientConfig = Pick<MynahUIDataModel, 'quickActionCommands'>

export const createChat = (
clientApi: { postMessage: (msg: UiMessage | ServerMessage) => void },
config?: ChatClientConfig
config?: ChatClientConfig,
supportFeaturesThroughConnectors: boolean = false,
connectorsConfig?: ChatClientConfig
) => {
// eslint-disable-next-line semi
let mynahApi: InboundChatApi
let connector: Connector | undefined

const sendMessageToClient = (message: UiMessage | ServerMessage) => {
clientApi.postMessage(message)
}

const handleMessage = (event: MessageEvent): void => {
const handleMessage = async (event: MessageEvent): Promise<void> => {
console.log('Received message from IDE: ', event.data)
if (event.data === undefined) {
return
}

// NOTE: 01. Route incoming messages
if (await connector?.tryHandleMessageReceive(event)) {
return
}

const message = event.data

switch (message?.command) {
Expand All @@ -86,15 +97,13 @@ export const createChat = (
break
case CHAT_OPTIONS: {
const params = (message as ChatOptionsMessage).params
const chatConfig: ChatClientConfig = params?.quickActions?.quickActionsCommandGroups
? { quickActionCommands: params.quickActions.quickActionsCommandGroups }
: {}

tabFactory.updateDefaultTabData(chatConfig)
if (params?.quickActions?.quickActionsCommandGroups) {
tabFactory.updateQuickActionCommands(params?.quickActions?.quickActionsCommandGroups)
}

const allExistingTabs: MynahUITabStoreModel = mynahUi.getAllTabs()
for (const tabId in allExistingTabs) {
mynahUi.updateStore(tabId, chatConfig)
mynahUi.updateStore(tabId, tabFactory.getDefaultTabData())
}
break
}
Expand Down Expand Up @@ -157,14 +166,22 @@ export const createChat = (
}

const messager = new Messager(chatApi)
const tabFactory = new TabFactory({
...DEFAULT_TAB_DATA,
...(config?.quickActionCommands ? { quickActionCommands: config.quickActionCommands } : {}),
})

const [mynahUi, api] = createMynahUi(messager, tabFactory)
const tabFactory = new TabFactory(DEFAULT_TAB_DATA, [
...(config?.quickActionCommands ? config.quickActionCommands : []),
...(connectorsConfig?.quickActionCommands ? connectorsConfig.quickActionCommands : []),
])
const [mynahUi, api, featuresConnector] = createMynahUi(
messager,
tabFactory,
// NOTE 03: below is for connectors only
// messages sent to connector apps can be differentiated by `tabType` present on level 0,
// extensions will have to be aware of this
supportFeaturesThroughConnectors,
clientApi.postMessage
)

mynahApi = api
connector = featuresConnector

return mynahUi
}
2 changes: 1 addition & 1 deletion chat-client/src/client/mynahUi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('MynahUI', () => {
const tabFactory = new TabFactory({})
createTabStub = sinon.stub(tabFactory, 'createTab')
createTabStub.returns({})
const mynahUiResult = createMynahUi(messager, tabFactory)
const mynahUiResult = createMynahUi(messager, tabFactory, false)
mynahUi = mynahUiResult[0]
inboundChatApi = mynahUiResult[1]
getSelectedTabIdStub = sinon.stub(mynahUi, 'getSelectedTabId')
Expand Down
35 changes: 30 additions & 5 deletions chat-client/src/client/mynahUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,20 @@ import {
LinkClickParams,
SourceLinkClickParams,
} from '@aws/language-server-runtimes-types'
import { ChatItem, ChatItemType, ChatPrompt, MynahUI, MynahUIDataModel, NotificationType } from '@aws/mynah-ui'
import {
ChatItem,
ChatItemType,
ChatPrompt,
MynahUI,
MynahUIDataModel,
MynahUIProps,
NotificationType,
} from '@aws/mynah-ui'
import { VoteParams } from '../contracts/telemetry'
import { Messager } from './messager'
import { TabFactory } from './tabs/tabFactory'
import { Connector } from '../connectors/connector'
import { connectorFactory } from '../connectors/connectorFactory'

export interface InboundChatApi {
addChatResponse(params: ChatResult, tabId: string, isPartialResult: boolean): void
Expand Down Expand Up @@ -83,10 +93,15 @@ export const handleChatPrompt = (
})
}

export const createMynahUi = (messager: Messager, tabFactory: TabFactory): [MynahUI, InboundChatApi] => {
export const createMynahUi = (
messager: Messager,
tabFactory: TabFactory,
supportFeaturesThroughConnectors: boolean,
connectorsPostMessage?: (msg: any) => void
): [MynahUI, InboundChatApi, Connector | undefined] => {
const initialTabId = TabFactory.generateUniqueId()

const mynahUi = new MynahUI({
let mynahUiProps: MynahUIProps = {
onCodeInsertToCursorPosition(
tabId,
messageId,
Expand Down Expand Up @@ -250,7 +265,17 @@ export const createMynahUi = (messager: Messager, tabFactory: TabFactory): [Myna
maxTabs: 10,
texts: uiComponentsTexts,
},
})
}

const mynahUiRef = { mynahUI: undefined as MynahUI | undefined }
let featuresConnector: Connector | undefined
if (supportFeaturesThroughConnectors) {
// NOTE: 00. Extend MynahUI with connector-specific handlers
;[featuresConnector, mynahUiProps] = connectorFactory(mynahUiProps, mynahUiRef, connectorsPostMessage!)
}

const mynahUi = new MynahUI(mynahUiProps)
mynahUiRef.mynahUI = mynahUi

const getTabStore = (tabId = mynahUi.getSelectedTabId()) => {
return tabId ? mynahUi.getAllTabs()[tabId]?.store : undefined
Expand Down Expand Up @@ -385,7 +410,7 @@ ${params.message}`,
showError: showError,
}

return [mynahUi, api]
return [mynahUi, api, featuresConnector]
}

export const DEFAULT_HELP_PROMPT = 'What can Amazon Q help me with?'
Expand Down
18 changes: 12 additions & 6 deletions chat-client/src/client/tabs/tabFactory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChatItemType, MynahUIDataModel } from '@aws/mynah-ui'
import { ChatItemType, MynahUIDataModel, QuickActionCommandGroup } from '@aws/mynah-ui'

export type DefaultTabData = MynahUIDataModel

Expand All @@ -10,11 +10,14 @@ export class TabFactory {
return `000${firstPart.toString(36)}`.slice(-3) + `000${secondPart.toString(36)}`.slice(-3)
}

constructor(private defaultTabData: DefaultTabData) {}
constructor(
private defaultTabData: DefaultTabData,
private quickActionCommands?: QuickActionCommandGroup[]
) {}

public createTab(needWelcomeMessages: boolean): MynahUIDataModel {
const tabData: MynahUIDataModel = {
...this.defaultTabData,
...this.getDefaultTabData(),
chatItems: needWelcomeMessages
? [
{
Expand All @@ -33,12 +36,15 @@ export class TabFactory {
return tabData
}

public updateDefaultTabData(defaultTabData: DefaultTabData) {
this.defaultTabData = { ...this.defaultTabData, ...defaultTabData }
public updateQuickActionCommands(quickActionCommands: QuickActionCommandGroup[]) {
this.quickActionCommands = [...(this.quickActionCommands ?? []), ...quickActionCommands]
}

public getDefaultTabData(): DefaultTabData {
return this.defaultTabData
return {
...this.defaultTabData,
...(this.quickActionCommands ? { quickActionCommands: this.quickActionCommands } : {}),
}
}

private getWelcomeBlock() {
Expand Down
Loading