Skip to content
Closed
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
3 changes: 1 addition & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@
"ENABLE_CUSTOMIZATIONS": "true"
// "HTTPS_PROXY": "http://127.0.0.1:8888",
// "AWS_CA_BUNDLE": "/path/to/cert.pem"
},
"preLaunchTask": "npm: compile"
}
},
{
"name": "CodeWhisperer Server IAM",
Expand Down
43 changes: 28 additions & 15 deletions chat-client/src/client/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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 './connector'

const DEFAULT_TAB_DATA = {
tabTitle: 'Chat',
Expand All @@ -57,7 +58,9 @@ type ChatClientConfig = Pick<MynahUIDataModel, 'quickActionCommands'> & { discla

export const createChat = (
clientApi: { postMessage: (msg: UiMessage | ServerMessage) => void },
config?: ChatClientConfig
config?: ChatClientConfig,
connectorsConfig?: ChatClientConfig, // legacy
connector?: Connector // legacy
) => {
// eslint-disable-next-line semi
let mynahApi: InboundChatApi
Expand All @@ -67,11 +70,20 @@ export const createChat = (
}

const handleMessage = (event: MessageEvent): void => {
console.log('Received message from IDE: ', event.data)

if (event.data === undefined) {
return
}
const message = event.data

// NOTE: Route incoming events to legacy connector
if (message?.sender && connector) {
const connectorEvent = new MessageEvent('message', { data: JSON.stringify(message) })
connector.handleMessageReceive(connectorEvent)
return
}

switch (message?.command) {
case CHAT_REQUEST_METHOD:
mynahApi.addChatResponse(message.params, message.tabId, message.isPartialResult)
Expand All @@ -87,18 +99,13 @@ export const createChat = (
break
case CHAT_OPTIONS: {
const params = (message as ChatOptionsMessage).params
const chatConfig: ChatClientConfig = params?.quickActions?.quickActionsCommandGroups
? {
quickActionCommands: params.quickActions.quickActionsCommandGroups,
disclaimerAcknowledged: config?.disclaimerAcknowledged ?? false,
}
: { disclaimerAcknowledged: config?.disclaimerAcknowledged ?? false }

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 @@ -164,12 +171,18 @@ export const createChat = (
}

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

const [mynahUi, api] = createMynahUi(messager, tabFactory, config?.disclaimerAcknowledged ?? false)
const [mynahUi, api] = createMynahUi(
messager,
tabFactory,
config?.disclaimerAcknowledged ?? false,
connector,
clientApi.postMessage
)

mynahApi = api

Expand Down
8 changes: 8 additions & 0 deletions chat-client/src/client/connector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ChatPrompt, MynahUI } from '@aws/mynah-ui'

export interface Connector {
createIdeConnector(mynahUIRef: { mynahUI: MynahUI | undefined }, ideApiPostMessage: (msg: any) => void): any // TODO: return type
isSupportedTab(tabId: string): boolean
handleMessageReceive(message: MessageEvent): void
handleQuickAction(prompt: ChatPrompt, tabId: string, eventId: string | undefined): void
}
77 changes: 77 additions & 0 deletions chat-client/src/client/connectorAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { MynahUI, MynahUIProps } from '@aws/mynah-ui'
import { Connector } from './connector'

export const connectorAdapter = (
mynahUiProps: MynahUIProps,
mynahUIRef: { mynahUI: MynahUI | undefined },
ideApiPostMessage: (msg: any) => void,
connector: Connector
): MynahUIProps => {
const ideConnector = connector.createIdeConnector(mynahUIRef, ideApiPostMessage)

const connectorMynahUiProps: MynahUIProps = {
...mynahUiProps,
onChatPrompt(tabId, prompt, eventId) {
if (connector.isSupportedTab(tabId)) {
ideConnector.requestAnswer(tabId, {
chatMessage: prompt.prompt ?? '',
})
return
}

if (prompt.command?.trim() === '/transform') {
// NOTE: Just an example, needs to be extended
// getQuickActionHandler().handle(prompt, tabId, eventId)
connector.handleQuickAction(prompt, tabId, eventId)
return
}

mynahUiProps.onChatPrompt?.(tabId, prompt, eventId)
},
onInBodyButtonClicked(tabId, messageId, action, eventId) {
if (connector.isSupportedTab(tabId)) {
ideConnector.onCustomFormAction(tabId, messageId, action, eventId)
return
}

mynahUiProps.onInBodyButtonClicked?.(tabId, messageId, action, eventId)
},
onCustomFormAction(tabId, action, eventId) {
if (connector.isSupportedTab(tabId)) {
ideConnector.onCustomFormAction(tabId, undefined, action)
return
}

mynahUiProps.onCustomFormAction?.(tabId, action, eventId)
},
onTabRemove(tabId) {
if (connector.isSupportedTab(tabId)) {
ideConnector.onTabRemove(tabId)
return
}

mynahUiProps.onTabRemove?.(tabId)
},
onTabChange(tabId) {
if (connector.isSupportedTab(tabId)) {
ideConnector.onTabChange(tabId)
return
}

mynahUiProps.onTabChange?.(tabId)
},
onLinkClick(tabId, messageId, link, mouseEvent) {
if (connector.isSupportedTab(tabId)) {
mouseEvent?.preventDefault()
mouseEvent?.stopPropagation()
mouseEvent?.stopImmediatePropagation()
ideConnector.onResponseBodyLinkClick(tabId, messageId, link)
return
}

mynahUiProps.onLinkClick?.(tabId, messageId, link, mouseEvent)
},
}

return connectorMynahUiProps
}
28 changes: 24 additions & 4 deletions chat-client/src/client/mynahUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,21 @@ 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,
NotificationType,
MynahUIProps,
} from '@aws/mynah-ui'
import { VoteParams } from '../contracts/telemetry'
import { Messager } from './messager'
import { TabFactory } from './tabs/tabFactory'
import { disclaimerAcknowledgeButtonId, disclaimerCard } from './texts/disclaimer'
import { connectorAdapter } from './connectorAdapter'
import { Connector } from './connector'

export interface InboundChatApi {
addChatResponse(params: ChatResult, tabId: string, isPartialResult: boolean): void
Expand Down Expand Up @@ -87,12 +97,14 @@ export const handleChatPrompt = (
export const createMynahUi = (
messager: Messager,
tabFactory: TabFactory,
disclaimerAcknowledged: boolean
disclaimerAcknowledged: boolean,
connector?: Connector,
connectorsPostMessage?: (msg: any) => void
): [MynahUI, InboundChatApi] => {
const initialTabId = TabFactory.generateUniqueId()
let disclaimerCardActive = !disclaimerAcknowledged

const mynahUi = new MynahUI({
let mynahUiProps: MynahUIProps = {
onCodeInsertToCursorPosition(
tabId,
messageId,
Expand Down Expand Up @@ -271,7 +283,15 @@ export const createMynahUi = (
maxTabs: 10,
texts: uiComponentsTexts,
},
})
}

const mynahUiRef = { mynahUI: undefined as MynahUI | undefined }
if (connector && connectorsPostMessage) {
mynahUiProps = connectorAdapter(mynahUiProps, mynahUiRef, connectorsPostMessage, connector)
}

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

const getTabStore = (tabId = mynahUi.getSelectedTabId()) => {
return tabId ? mynahUi.getAllTabs()[tabId]?.store : undefined
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'
import { disclaimerCard } from '../texts/disclaimer'

export type DefaultTabData = MynahUIDataModel
Expand All @@ -11,11 +11,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, disclaimerCardActive: boolean): MynahUIDataModel {
const tabData: MynahUIDataModel = {
...this.defaultTabData,
...this.getDefaultTabData(),
chatItems: needWelcomeMessages
? [
{
Expand All @@ -35,12 +38,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
1 change: 1 addition & 0 deletions chat-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { createChat } from './client/chat'
export { createConnectorAdapter } from './ui/main'
Loading
Loading