Skip to content

Commit 5b8e83c

Browse files
authored
feat(chat-client): history list and conversation actions (#929)
* feat(chat-client): history list and conversation actions * chore: support conversation click in test vscode client * chore: remove tabId from open detailed list params * chore: removed ts-ignore * fix: removed tabId from controller * chore: fix prettier * chore: removed code used for manual testing * chore: apply formatting * chore: remove noon handlers
1 parent 7258f07 commit 5b8e83c

File tree

8 files changed

+245
-4
lines changed

8 files changed

+245
-4
lines changed

chat-client/.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ node_modules/
44
out/
55
build/
66
**/bin/
7-
**/obj/
7+
**/obj/
8+
CHANGELOG.md

chat-client/src/client/chat.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ import {
3232
import {
3333
CHAT_REQUEST_METHOD,
3434
CONTEXT_COMMAND_NOTIFICATION_METHOD,
35+
CONVERSATION_CLICK_REQUEST_METHOD,
3536
CREATE_PROMPT_NOTIFICATION_METHOD,
3637
ChatParams,
3738
ContextCommandParams,
39+
ConversationClickParams,
40+
ConversationClickResult,
3841
CreatePromptParams,
3942
FEEDBACK_NOTIFICATION_METHOD,
4043
FILE_CLICK_NOTIFICATION_METHOD,
@@ -45,7 +48,10 @@ import {
4548
INFO_LINK_CLICK_NOTIFICATION_METHOD,
4649
InfoLinkClickParams,
4750
LINK_CLICK_NOTIFICATION_METHOD,
51+
LIST_CONVERSATIONS_REQUEST_METHOD,
4852
LinkClickParams,
53+
ListConversationsParams,
54+
ListConversationsResult,
4955
OPEN_TAB_REQUEST_METHOD,
5056
OpenTabParams,
5157
OpenTabResult,
@@ -134,6 +140,12 @@ export const createChat = (
134140
case CONTEXT_COMMAND_NOTIFICATION_METHOD:
135141
mynahApi.sendContextCommands(message.params as ContextCommandParams)
136142
break
143+
case LIST_CONVERSATIONS_REQUEST_METHOD:
144+
mynahApi.listConversations(message.params as ListConversationsResult)
145+
break
146+
case CONVERSATION_CLICK_REQUEST_METHOD:
147+
mynahApi.conversationClicked(message.params as ConversationClickResult)
148+
break
137149
case CHAT_OPTIONS: {
138150
const params = (message as ChatOptionsMessage).params
139151
if (params?.quickActions?.quickActionsCommandGroups) {
@@ -237,6 +249,12 @@ export const createChat = (
237249
fileClick: (params: FileClickParams) => {
238250
sendMessageToClient({ command: FILE_CLICK_NOTIFICATION_METHOD, params: params })
239251
},
252+
listConversations: (params: ListConversationsParams) => {
253+
sendMessageToClient({ command: LIST_CONVERSATIONS_REQUEST_METHOD, params })
254+
},
255+
conversationClick: (params: ConversationClickParams) => {
256+
sendMessageToClient({ command: CONVERSATION_CLICK_REQUEST_METHOD, params })
257+
},
240258
}
241259

242260
const messager = new Messager(chatApi)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { ConversationAction, ConversationItemGroup, ListConversationsResult } from '@aws/language-server-runtimes-types'
2+
import { ChatItemButton, DetailedList, DetailedListItem, MynahUI, TextBasedFormItem } from '@aws/mynah-ui'
3+
import { toMynahIcon } from '../utils'
4+
import { Messager } from '../messager'
5+
6+
export const ChatHistory = {
7+
TabBarButtonId: 'history_sheet',
8+
} as const
9+
10+
interface MynahDetailedList {
11+
update: (data: DetailedList) => void
12+
close: () => void
13+
changeTarget: (direction: 'up' | 'down', snapOnLastAndFirst?: boolean) => void
14+
getTargetElementId: () => string | undefined
15+
}
16+
17+
export class ChatHistoryList {
18+
historyDetailedList: MynahDetailedList | undefined
19+
20+
constructor(
21+
private mynahUi: MynahUI,
22+
private messager: Messager
23+
) {}
24+
25+
show(params: ListConversationsResult) {
26+
const detailedList = {
27+
header: params.header,
28+
filterOptions: params.filterOptions?.map(filter => ({
29+
...filter,
30+
icon: toMynahIcon(filter.icon),
31+
})),
32+
list: this.toConversationGroups(params.list),
33+
}
34+
// set auto focus on the 1st filter option item
35+
if (detailedList.filterOptions && detailedList.filterOptions.length > 0) {
36+
// we currently support only text-based items
37+
;(detailedList.filterOptions[0] as TextBasedFormItem).autoFocus = true
38+
}
39+
40+
if (this.historyDetailedList) {
41+
this.historyDetailedList.update(detailedList)
42+
} else {
43+
this.historyDetailedList = this.mynahUi.openDetailedList({
44+
tabId: '', // TODO: remove after MynahUI is changed to remove the property
45+
detailedList: detailedList,
46+
events: {
47+
onFilterValueChange: this.onFilterValueChange,
48+
onKeyPress: this.onKeyPress,
49+
onItemSelect: this.onItemSelect,
50+
onActionClick: this.onActionClick,
51+
onClose: this.onClose,
52+
},
53+
})
54+
}
55+
}
56+
57+
close() {
58+
this.historyDetailedList?.close()
59+
}
60+
61+
private onFilterValueChange = (filterValues: Record<string, any>) => {
62+
this.messager.onListConversations(filterValues)
63+
}
64+
65+
private onItemSelect = (item: DetailedListItem) => {
66+
if (!item.id) {
67+
throw new Error('Conversation id is not defined')
68+
}
69+
this.messager.onConversationClick(item.id)
70+
}
71+
72+
private onActionClick = (action: ChatItemButton) => {
73+
const conversationAction = this.getConversationAction(action.text)
74+
this.messager.onConversationClick(action.id, conversationAction)
75+
}
76+
77+
private onClose = () => {
78+
this.historyDetailedList = undefined
79+
}
80+
81+
private onKeyPress = (e: KeyboardEvent) => {
82+
if (e.key === 'Escape') {
83+
this.close()
84+
} else if (e.key === 'Enter') {
85+
const targetElementId = this.historyDetailedList?.getTargetElementId()
86+
if (targetElementId) {
87+
this.onItemSelect({
88+
id: targetElementId,
89+
})
90+
}
91+
} else if (e.key === 'ArrowUp') {
92+
this.historyDetailedList?.changeTarget('up')
93+
} else if (e.key === 'ArrowDown') {
94+
this.historyDetailedList?.changeTarget('down')
95+
}
96+
}
97+
98+
private toConversationGroups = (groups: ConversationItemGroup[]) => {
99+
return groups.map(group => ({
100+
groupName: group.groupName,
101+
icon: toMynahIcon(group.icon),
102+
children: group.items?.map(item => ({
103+
...item,
104+
icon: toMynahIcon(item.icon),
105+
actions: item.actions?.map(action => ({
106+
...action,
107+
icon: toMynahIcon(action.icon),
108+
})),
109+
})),
110+
}))
111+
}
112+
113+
private getConversationAction = (actionText: string | undefined): ConversationAction => {
114+
switch (actionText) {
115+
case 'Export':
116+
return 'export'
117+
case 'Delete':
118+
return 'delete'
119+
default:
120+
throw new Error(`Unsupported action: ${actionText}`)
121+
}
122+
}
123+
}

chat-client/src/client/messager.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ import {
2222
} from '@aws/chat-client-ui-types'
2323
import {
2424
ChatParams,
25+
ConversationAction,
26+
ConversationClickParams,
2527
CreatePromptParams,
2628
FeedbackParams,
2729
FileClickParams,
30+
FilterValue,
2831
FollowUpClickParams,
2932
InfoLinkClickParams,
3033
LinkClickParams,
34+
ListConversationsParams,
3135
OpenTabResult,
3236
QuickActionParams,
3337
SourceLinkClickParams,
@@ -78,6 +82,8 @@ export interface OutboundChatApi {
7882
onOpenTab(result: OpenTabResult | ErrorResult): void
7983
createPrompt(params: CreatePromptParams): void
8084
fileClick(params: FileClickParams): void
85+
listConversations(params: ListConversationsParams): void
86+
conversationClick(params: ConversationClickParams): void
8187
}
8288

8389
export class Messager {
@@ -185,4 +191,12 @@ export class Messager {
185191
this.chatApi.telemetry({ ...params, name: FILE_CLICK_TELEMETRY_EVENT })
186192
this.chatApi.fileClick(params)
187193
}
194+
195+
onListConversations = (filter?: Record<string, FilterValue>): void => {
196+
this.chatApi.listConversations({ filter })
197+
}
198+
199+
onConversationClick = (conversationId: string, action?: ConversationAction): void => {
200+
this.chatApi.conversationClick({ id: conversationId, action })
201+
}
188202
}

chat-client/src/client/mynahUi.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { assert } from 'sinon'
44
import { createMynahUi, InboundChatApi, handleChatPrompt, DEFAULT_HELP_PROMPT } from './mynahUi'
55
import { Messager, OutboundChatApi } from './messager'
66
import { TabFactory } from './tabs/tabFactory'
7-
import { ChatItemType, MynahUI, MynahUIProps } from '@aws/mynah-ui'
8-
import { ChatEventHandler, ChatClientAdapter } from '../contracts/chatClientAdapter'
9-
import { withAdapter } from './withAdapter'
7+
import { ChatItemType, MynahUI } from '@aws/mynah-ui'
8+
import { ChatClientAdapter } from '../contracts/chatClientAdapter'
109

1110
describe('MynahUI', () => {
1211
let messager: Messager
@@ -45,6 +44,8 @@ describe('MynahUI', () => {
4544
onOpenTab: sinon.stub(),
4645
createPrompt: sinon.stub(),
4746
fileClick: sinon.stub(),
47+
listConversations: sinon.stub(),
48+
conversationClick: sinon.stub(),
4849
}
4950

5051
messager = new Messager(outboundChatApi)

chat-client/src/client/mynahUi.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import {
1616
ChatResult,
1717
ContextCommand,
1818
ContextCommandParams,
19+
ConversationClickResult,
1920
FeedbackParams,
2021
FollowUpClickParams,
2122
InfoLinkClickParams,
2223
LinkClickParams,
24+
ListConversationsResult,
2325
OpenTabParams,
2426
SourceLinkClickParams,
2527
} from '@aws/language-server-runtimes-types'
@@ -32,6 +34,7 @@ import {
3234
NotificationType,
3335
MynahUIProps,
3436
QuickActionCommand,
37+
MynahIcons,
3538
} from '@aws/mynah-ui'
3639
import { VoteParams } from '../contracts/telemetry'
3740
import { Messager } from './messager'
@@ -40,6 +43,7 @@ import { disclaimerAcknowledgeButtonId, disclaimerCard } from './texts/disclaime
4043
import { ChatClientAdapter, ChatEventHandler } from '../contracts/chatClientAdapter'
4144
import { withAdapter } from './withAdapter'
4245
import { toMynahIcon } from './utils'
46+
import { ChatHistory, ChatHistoryList } from './features/history'
4347

4448
export interface InboundChatApi {
4549
addChatResponse(params: ChatResult, tabId: string, isPartialResult: boolean): void
@@ -48,6 +52,8 @@ export interface InboundChatApi {
4852
showError(params: ErrorParams): void
4953
openTab(params: OpenTabParams): void
5054
sendContextCommands(params: ContextCommandParams): void
55+
listConversations(params: ListConversationsResult): void
56+
conversationClicked(params: ConversationClickResult): void
5157
}
5258

5359
type ContextCommandGroups = MynahUIDataModel['contextCommands']
@@ -328,6 +334,13 @@ export const createMynahUi = (
328334
}
329335
return false
330336
},
337+
onTabBarButtonClick: (tabId: string, buttonId: string) => {
338+
if (buttonId === ChatHistory.TabBarButtonId) {
339+
messager.onListConversations()
340+
return
341+
}
342+
throw new Error(`Unhandled tab bar button id: ${buttonId}`)
343+
},
331344
}
332345

333346
const mynahUiProps: MynahUIProps = {
@@ -343,6 +356,14 @@ export const createMynahUi = (
343356
config: {
344357
maxTabs: 10,
345358
texts: uiComponentsTexts,
359+
// TODO: load dynamically from ChatOptions
360+
tabBarButtons: [
361+
{
362+
id: ChatHistory.TabBarButtonId,
363+
icon: MynahIcons.COMMENT,
364+
description: 'View chat history',
365+
},
366+
],
346367
},
347368
}
348369

@@ -562,13 +583,37 @@ ${params.message}`,
562583
})
563584
}
564585

586+
let chatHistoryList = new ChatHistoryList(mynahUi, messager)
587+
const listConversations = (params: ListConversationsResult) => {
588+
chatHistoryList.show(params)
589+
}
590+
591+
const conversationClicked = (params: ConversationClickResult) => {
592+
if (!params.success) {
593+
// TODO: any logging, error for this?
594+
return
595+
}
596+
597+
// close history list if conversation item was successfully opened
598+
if (!params.action) {
599+
chatHistoryList.close()
600+
return
601+
}
602+
// request update conversations list if conversation item was successfully deleted
603+
if (params.action === 'delete') {
604+
messager.onListConversations()
605+
}
606+
}
607+
565608
const api = {
566609
addChatResponse: addChatResponse,
567610
sendToPrompt: sendToPrompt,
568611
sendGenericCommand: sendGenericCommand,
569612
showError: showError,
570613
openTab: openTab,
571614
sendContextCommands: sendContextCommands,
615+
listConversations: listConversations,
616+
conversationClicked: conversationClicked,
572617
}
573618

574619
return [mynahUi, api]

chat-client/src/contracts/serverContracts.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import {
2525
CREATE_PROMPT_NOTIFICATION_METHOD,
2626
FileClickParams,
2727
FILE_CLICK_NOTIFICATION_METHOD,
28+
LIST_CONVERSATIONS_REQUEST_METHOD,
29+
ListConversationsParams,
30+
CONVERSATION_CLICK_REQUEST_METHOD,
31+
ConversationClickParams,
2832
} from '@aws/language-server-runtimes-types'
2933

3034
export const TELEMETRY = 'telemetry/event'
@@ -45,6 +49,8 @@ export type ServerMessageCommand =
4549
| typeof OPEN_TAB_REQUEST_METHOD
4650
| typeof CREATE_PROMPT_NOTIFICATION_METHOD
4751
| typeof FILE_CLICK_NOTIFICATION_METHOD
52+
| typeof LIST_CONVERSATIONS_REQUEST_METHOD
53+
| typeof CONVERSATION_CLICK_REQUEST_METHOD
4854

4955
export interface Message {
5056
command: ServerMessageCommand
@@ -73,3 +79,5 @@ export type ServerMessageParams =
7379
| OpenTabResult
7480
| CreatePromptParams
7581
| FileClickParams
82+
| ListConversationsParams
83+
| ConversationClickParams

0 commit comments

Comments
 (0)