Skip to content

Commit 278fc48

Browse files
committed
feat: paid tier
detect Free Tier, show "Upgrade" button
1 parent 01df5dd commit 278fc48

File tree

14 files changed

+351
-33
lines changed

14 files changed

+351
-33
lines changed

chat-client/src/client/chat.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,10 @@ export const createChat = (
379379
promptInputOptionChange: (params: PromptInputOptionChangeParams) => {
380380
sendMessageToClient({ command: PROMPT_INPUT_OPTION_CHANGE_METHOD, params })
381381
},
382+
promptInputButtonClick: params => {
383+
// TODO
384+
sendMessageToClient({ command: BUTTON_CLICK_REQUEST_METHOD, params })
385+
},
382386
stopChatResponse: (tabId: string) => {
383387
sendMessageToClient({ command: STOP_CHAT_RESPONSE, params: { tabId } })
384388
},

chat-client/src/client/messager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export interface OutboundChatApi {
9292
tabBarAction(params: TabBarActionParams): void
9393
onGetSerializedChat(requestId: string, result: GetSerializedChatResult | ErrorResult): void
9494
promptInputOptionChange(params: PromptInputOptionChangeParams): void
95+
promptInputButtonClick(params: ButtonClickParams): void
9596
stopChatResponse(tabId: string): void
9697
sendButtonClickEvent(params: ButtonClickParams): void
9798
onOpenSettings(settingKey: string): void
@@ -229,6 +230,10 @@ export class Messager {
229230
this.chatApi.promptInputOptionChange(params)
230231
}
231232

233+
onPromptInputButtonClick = (params: ButtonClickParams): void => {
234+
this.chatApi.promptInputButtonClick(params)
235+
}
236+
232237
onStopChatResponse = (tabId: string): void => {
233238
this.chatApi.stopChatResponse(tabId)
234239
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ describe('MynahUI', () => {
5656
tabBarAction: sinon.stub(),
5757
onGetSerializedChat: sinon.stub(),
5858
promptInputOptionChange: sinon.stub(),
59+
promptInputButtonClick: sinon.stub(),
5960
stopChatResponse: sinon.stub(),
6061
sendButtonClickEvent: sinon.stub(),
6162
onOpenSettings: sinon.stub(),

chat-client/src/client/mynahUi.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ export const createMynahUi = (
171171
): [MynahUI, InboundChatApi] => {
172172
let disclaimerCardActive = !disclaimerAcknowledged
173173
let programmingModeCardActive = !pairProgrammingCardAcknowledged
174+
const isFreeTierLimitReached = true
175+
let paidTierCardActive = isFreeTierLimitReached
174176
let contextCommandGroups: ContextCommandGroups | undefined
175177

176178
let chatEventHandlers: ChatEventHandler = {
@@ -264,7 +266,12 @@ export const createMynahUi = (
264266
// We check if tabMetadata.openTabKey exists - if it does and is set to true, we skip showing welcome messages
265267
// since this indicates we're loading a previous chat session rather than starting a new one.
266268
if (!tabStore?.tabMetadata || !tabStore.tabMetadata.openTabKey) {
267-
defaultTabConfig.chatItems = tabFactory.getChatItems(true, programmingModeCardActive, [])
269+
defaultTabConfig.chatItems = tabFactory.getChatItems(
270+
true,
271+
programmingModeCardActive,
272+
paidTierCardActive,
273+
[]
274+
)
268275
}
269276
mynahUi.updateStore(tabId, defaultTabConfig)
270277
messager.onTabAdd(tabId)
@@ -465,6 +472,14 @@ export const createMynahUi = (
465472
}
466473
messager.onPromptInputOptionChange({ tabId, optionsValues })
467474
},
475+
onPromptInputButtonClick: (tabId, buttonId, eventId) => {
476+
const payload: ButtonClickParams = {
477+
tabId,
478+
messageId: 'not-a-message',
479+
buttonId: buttonId,
480+
}
481+
messager.onPromptInputButtonClick(payload)
482+
},
468483
onMessageDismiss: (tabId, messageId) => {
469484
if (messageId === programmerModeCard.messageId) {
470485
programmingModeCardActive = false
@@ -473,7 +488,7 @@ export const createMynahUi = (
473488
// Update the tab defaults to hide the programmer mode card for new tabs
474489
mynahUi.updateTabDefaults({
475490
store: {
476-
chatItems: tabFactory.getChatItems(true, false),
491+
chatItems: tabFactory.getChatItems(true, false, false),
477492
},
478493
})
479494
}
@@ -494,7 +509,7 @@ export const createMynahUi = (
494509
},
495510
},
496511
defaults: {
497-
store: tabFactory.createTab(false),
512+
store: tabFactory.createTab(false, paidTierCardActive),
498513
},
499514
config: {
500515
maxTabs: 10,
@@ -534,7 +549,7 @@ export const createMynahUi = (
534549
// This distinction helps maintain consistent tab behavior between fresh conversations and restored sessions.
535550
const createTabId = (openTab?: boolean) => {
536551
const tabId = mynahUi.updateStore('', {
537-
...tabFactory.createTab(disclaimerCardActive),
552+
...tabFactory.createTab(disclaimerCardActive, paidTierCardActive),
538553
tabMetadata: { openTabKey: openTab ? true : false },
539554
})
540555
if (tabId === undefined) {
@@ -998,7 +1013,12 @@ ${params.message}`,
9981013
const tabId = createTabId(true)
9991014
if (tabId) {
10001015
mynahUi.updateStore(tabId, {
1001-
chatItems: tabFactory.getChatItems(messages ? false : true, programmingModeCardActive, messages),
1016+
chatItems: tabFactory.getChatItems(
1017+
messages ? false : true,
1018+
programmingModeCardActive,
1019+
paidTierCardActive,
1020+
messages
1021+
),
10021022
})
10031023
messager.onOpenTab(requestId, { tabId })
10041024
} else {

chat-client/src/client/tabs/tabFactory.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,31 @@ import {
55
MynahUIDataModel,
66
QuickActionCommandGroup,
77
TabBarMainAction,
8+
ChatItemButton,
89
} from '@aws/mynah-ui'
910
import { disclaimerCard } from '../texts/disclaimer'
1011
import { ChatMessage } from '@aws/language-server-runtimes-types'
1112
import { ChatHistory } from '../features/history'
1213
import { pairProgrammingPromptInput, programmerModeCard } from '../texts/pairProgramming'
14+
import { paidTierCard } from '../texts/paidTier'
1315

1416
export type DefaultTabData = MynahUIDataModel
1517

1618
export const ExportTabBarButtonId = 'export'
1719

20+
const upgradeToProButton: ChatItemButton = {
21+
flash: 'once',
22+
fillState: 'hover',
23+
position: 'outside',
24+
id: 'upgrade-q',
25+
// https://github.com/aws/mynah-ui/blob/main/src/components/icon/icons/q.svg
26+
// https://github.com/aws/mynah-ui/blob/main/src/components/icon/icons/rocket.svg
27+
// icon: MynahIcons.Q,
28+
description: 'Upgrade to Amazon Q Pro',
29+
text: 'Upgrade Q',
30+
status: 'info',
31+
}
32+
1833
export class TabFactory {
1934
private history: boolean = false
2035
private export: boolean = false
@@ -36,10 +51,11 @@ export class TabFactory {
3651
this.initialTabId = TabFactory.generateUniqueId()
3752
}
3853

39-
public createTab(disclaimerCardActive: boolean): MynahUIDataModel {
54+
public createTab(disclaimerCardActive: boolean, showUpgradeButton: boolean): MynahUIDataModel {
4055
const tabData: MynahUIDataModel = {
4156
...this.getDefaultTabData(),
4257
...(disclaimerCardActive ? { promptInputStickyCard: disclaimerCard } : {}),
58+
promptInputButtons: showUpgradeButton ? [upgradeToProButton] : [],
4359
promptInputOptions: this.agenticMode ? [pairProgrammingPromptInput] : [],
4460
cancelButtonWhenLoading: this.agenticMode, // supported for agentic chat only
4561
}
@@ -49,13 +65,15 @@ export class TabFactory {
4965
public getChatItems(
5066
needWelcomeMessages: boolean,
5167
pairProgrammingCardActive: boolean,
68+
paidTierCardActive: boolean,
5269
chatMessages?: ChatMessage[]
5370
): ChatItem[] {
5471
return [
5572
...(this.bannerMessage ? [this.getBannerMessage() as ChatItem] : []),
5673
...(needWelcomeMessages
5774
? [
5875
...(this.agenticMode && pairProgrammingCardActive ? [programmerModeCard] : []),
76+
...(paidTierCardActive ? [paidTierCard] : []),
5977
{
6078
type: ChatItemType.ANSWER,
6179
body: `Hi, I'm Amazon Q. I can answer your software development questions.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ChatItem, ChatItemFormItem, ChatItemType } from '@aws/mynah-ui'
2+
3+
export const paidTierCard: ChatItem = {
4+
type: ChatItemType.ANSWER,
5+
title: 'FREE TIER LIMIT REACHED',
6+
header: {
7+
icon: 'q',
8+
iconStatus: 'primary',
9+
body: 'Upgrade to Amazon Q Pro',
10+
},
11+
messageId: 'paidTierCardId',
12+
fullWidth: true,
13+
canBeDismissed: false,
14+
body: 'You have reached the free tier limit. Upgraded to Amazon Q Pro.\n\n[Learn More...](https://aws.amazon.com/q/pricing/)',
15+
}
16+
17+
export const paidTierPromptInput: ChatItemFormItem = {
18+
type: 'switch',
19+
id: 'paid-tier',
20+
tooltip: 'Upgrade to Amazon Q Pro',
21+
value: 'true',
22+
icon: 'magic',
23+
}
24+
25+
export const paidTierStep0: ChatItem = {
26+
type: ChatItemType.DIRECTIVE,
27+
body: 'You have upgraded to Amazon Q Pro',
28+
}
29+
30+
export const paidTierStep1: ChatItem = {
31+
type: ChatItemType.DIRECTIVE,
32+
body: 'You have upgraded to Amazon Q Pro',
33+
}

chat-client/src/client/withAdapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const withAdapter = (
5757
onChatPromptProgressActionButtonClicked: addDefaultRouting('onChatPromptProgressActionButtonClicked'),
5858
onTabbedContentTabChange: addDefaultRouting('onTabbedContentTabChange'),
5959
onPromptInputOptionChange: addDefaultRouting('onPromptInputOptionChange'),
60+
onPromptInputButtonClick: addDefaultRouting('onPromptInputButtonClick'),
6061
onMessageDismiss: addDefaultRouting('onMessageDismiss'),
6162

6263
/**

chat-client/src/contracts/chatClientAdapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface ChatEventHandler
3636
| 'onResetStore'
3737
| 'onReady'
3838
| 'onPromptInputOptionChange'
39+
| 'onPromptInputButtonClick'
3940
| 'onMessageDismiss'
4041
> {}
4142

server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ export class AgenticChatController implements ChatHandlers {
251251
this.#stoppedToolUses.add(params.messageId)
252252
await this.#renderStoppedShellCommand(params.tabId, params.messageId)
253253
return { success: true }
254+
} else if (params.buttonId === 'upgrade-q') {
255+
this.#features.logging.warn('clicked upgrade-to-pro-button')
256+
return { success: true }
254257
} else {
255258
return {
256259
success: false,

server/aws-lsp-codewhisperer/src/language-server/agenticChat/qAgenticChatServer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/A
1313
import { AmazonQWorkspaceConfig } from '../../shared/amazonQServiceManager/configurationUtils'
1414
import { TabBarController } from './tabBarController'
1515
import { AmazonQServiceInitializationError } from '../../shared/amazonQServiceManager/errors'
16-
import { safeGet } from '../../shared/utils'
16+
import { safeGet, getSsoConnectionType } from '../../shared/utils'
1717

1818
export const QAgenticChatServer =
1919
// prettier-ignore
@@ -51,6 +51,8 @@ export const QAgenticChatServer =
5151
chatController.updateConfiguration(updatedConfig)
5252
}
5353

54+
const isFreeTierUser = getSsoConnectionType(credentialsProvider)
55+
5456
lsp.onInitialized(async () => {
5557
// Get initialized service manager and inject it to chatSessionManagementService to pass it down
5658
amazonQServiceManager = AmazonQTokenServiceManager.getInstance()

0 commit comments

Comments
 (0)