Skip to content

Commit f52f98c

Browse files
committed
PROGRESS MAY 21
1 parent 9a2f6a1 commit f52f98c

19 files changed

+729
-39
lines changed

src/components/chat-item/chat-item-form-items.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export class ChatItemFormItemsWrapper {
132132
icon: chatItemOption.icon,
133133
description,
134134
value: value as 'true' | 'false',
135+
indeterminate: chatItemOption.indeterminate,
135136
optional: chatItemOption.mandatory !== true,
136137
...(this.getHandlers(chatItemOption))
137138
});

src/components/chat-item/chat-prompt-input.ts

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { convertDetailedListItemToQuickActionCommand, convertQuickActionCommandG
2121
import { DetailedListWrapper } from '../detailed-list/detailed-list';
2222
import { PromptOptions } from './prompt-input/prompt-options';
2323
import { PromptInputStopButton } from './prompt-input/prompt-input-stop-button';
24-
import { Button } from '../button';
25-
import { Icon, MynahIcons } from '../icon';
24+
import { MynahIcons } from '../icon';
25+
import { PromptTopBar } from './prompt-input/prompt-top-bar/prompt-top-bar';
2626

2727
// 96 extra is added as a threshold to allow for attachments
2828
// We ignore this for the textual character limit
@@ -51,13 +51,14 @@ export class ChatPromptInput {
5151
private readonly props: ChatPromptInputProps;
5252
private readonly attachmentWrapper: ExtendedHTMLElement;
5353
private readonly promptTextInput: PromptTextInput;
54-
private readonly contextSelectorButton: Button;
54+
// private readonly contextSelectorButton: Button;
5555
private readonly promptTextInputCommand: ChatPromptInputCommand;
5656
private readonly sendButton: PromptInputSendButton;
5757
private readonly stopButton: PromptInputStopButton;
5858
private readonly progressIndicator: PromptInputProgress;
5959
private readonly promptAttachment: PromptAttachment;
6060
private readonly promptOptions: PromptOptions;
61+
private readonly promptTopBar: PromptTopBar;
6162
private readonly chatPrompt: ExtendedHTMLElement;
6263
private quickPickItemsSelectorContainer: DetailedListWrapper;
6364
private promptTextInputLabel: ExtendedHTMLElement;
@@ -152,6 +153,25 @@ export class ChatPromptInput {
152153
}
153154
});
154155

156+
this.promptTopBar = new PromptTopBar({
157+
tabId: this.props.tabId,
158+
title: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptTopBarTitle'),
159+
placeholder: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptTopBarPlaceholder'),
160+
actionPillConfig: { command: 'Rules', id: 'Rules', label: 'Rules', icon: MynahIcons.CHECK_LIST },
161+
actionPillItems: [ {
162+
groupName: { type: 'checkbox', id: '.amazonq/rules', label: '.amazonq/rules' },
163+
items: [ { type: 'checkbox', id: 'README.md', label: 'README.md' },
164+
{ type: 'checkbox', id: 'java-expert.md', label: 'java-expert.md' },
165+
{ type: 'checkbox', id: 'all-caps.md', label: 'all-caps.md' }
166+
]
167+
}, {
168+
groupName: { type: 'checkbox', id: '.amazonq/coding', label: '.amazonq/coding' },
169+
items: [ { type: 'checkbox', id: 'coding-rule.md', label: 'coding-rule.md' } ]
170+
} ],
171+
contextItems: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptTopBarContextItems'),
172+
onContextSelectorButtonClick: this.onContextSelectorButtonClick
173+
});
174+
155175
this.attachmentWrapper = DomBuilder.getInstance().build({
156176
type: 'div',
157177
testId: testIds.prompt.attachmentWrapper,
@@ -161,32 +181,26 @@ export class ChatPromptInput {
161181
]
162182
});
163183

164-
this.contextSelectorButton = new Button({
165-
icon: new Icon({ icon: MynahIcons.AT }).render,
166-
status: 'clear',
167-
disabled: ((MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? []).length === 0,
168-
classNames: ((MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? []).length === 0 ? [ 'hidden' ] : [],
169-
primary: false,
170-
onClick: () => {
171-
this.searchTerm = '';
172-
this.quickPickType = 'context';
173-
this.quickPickItemGroups = (MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? [];
174-
this.quickPickTriggerIndex = this.promptTextInput.getCursorPos();
175-
this.filteredQuickPickItemGroups = [ ...this.quickPickItemGroups ];
176-
this.promptTextInput.insertEndSpace();
177-
this.openQuickPick();
178-
},
179-
});
180-
181-
MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'contextCommands', (contextCommands) => {
182-
if (contextCommands?.length > 0) {
183-
this.contextSelectorButton.setEnabled(true);
184-
this.contextSelectorButton.render.removeClass('hidden');
185-
} else {
186-
this.contextSelectorButton.setEnabled(false);
187-
this.contextSelectorButton.render.addClass('hidden');
188-
}
189-
});
184+
// this.contextSelectorButton = new Button({
185+
// icon: new Icon({ icon: MynahIcons.AT }).render,
186+
// status: 'clear',
187+
// disabled: ((MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? []).length === 0,
188+
// classNames: ((MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? []).length === 0 ? [ 'hidden' ] : [],
189+
// primary: false,
190+
// onClick: () => {
191+
// this.onContextSelectorButtonClick();
192+
// },
193+
// });
194+
195+
// MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'contextCommands', (contextCommands) => {
196+
// if (contextCommands?.length > 0) {
197+
// this.contextSelectorButton.setEnabled(true);
198+
// this.contextSelectorButton.render.removeClass('hidden');
199+
// } else {
200+
// this.contextSelectorButton.setEnabled(false);
201+
// this.contextSelectorButton.render.addClass('hidden');
202+
// }
203+
// });
190204
this.chatPrompt = DomBuilder.getInstance().build({
191205
type: 'div',
192206
classNames: [ 'mynah-chat-prompt' ],
@@ -197,13 +211,14 @@ export class ChatPromptInput {
197211
type: 'div',
198212
classNames: [ 'mynah-chat-prompt-input-wrapper' ],
199213
children: [
214+
this.promptTopBar.render,
200215
this.promptTextInput.render,
201216
{
202217
type: 'div',
203218
classNames: [ 'mynah-chat-prompt-button-wrapper' ],
204219
children: [
205220
this.promptOptions.render,
206-
this.contextSelectorButton.render,
221+
// this.contextSelectorButton.render,
207222
this.stopButton.render,
208223
this.sendButton.render,
209224
]
@@ -229,6 +244,12 @@ export class ChatPromptInput {
229244
MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptInputButtons', (newButtons: ChatItemButton[]) => {
230245
this.promptOptions.update(undefined, newButtons);
231246
});
247+
MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptTopBarContextItems', (newCommands: QuickActionCommand[]) => {
248+
this.promptTopBar.update({ contextItems: newCommands });
249+
});
250+
MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptTopBarTitle', (newTitle: string) => {
251+
this.promptTopBar.update({ title: newTitle });
252+
});
232253

233254
MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptInputLabel', (promptInputLabel: string) => {
234255
const newDetails = this.getPromptInputTextLabel(promptInputLabel);
@@ -291,6 +312,16 @@ export class ChatPromptInput {
291312
});
292313
}
293314

315+
private readonly onContextSelectorButtonClick = (): void => {
316+
this.searchTerm = '';
317+
this.quickPickType = 'context';
318+
this.quickPickItemGroups = (MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? [];
319+
this.quickPickTriggerIndex = this.promptTextInput.getCursorPos();
320+
this.filteredQuickPickItemGroups = [ ...this.quickPickItemGroups ];
321+
this.promptTextInput.insertEndSpace();
322+
this.openQuickPick();
323+
};
324+
294325
private readonly updateAvailableCharactersIndicator = (): void => {
295326
const characterAmount = MAX_USER_INPUT() - Math.max(0, (this.promptTextInput.promptTextInputMaxLength - this.promptTextInput.getTextInputValue().trim().length));
296327
const charTextElm = DomBuilder.getInstance().build({
@@ -408,7 +439,7 @@ export class ChatPromptInput {
408439
}
409440
}
410441
} else {
411-
const blockedKeys = [ KeyMap.ENTER, KeyMap.ESCAPE, KeyMap.SPACE, KeyMap.TAB, KeyMap.AT, KeyMap.BACK_SLASH, KeyMap.SLASH ] as string[];
442+
const blockedKeys = [ KeyMap.ENTER, KeyMap.ESCAPE, KeyMap.SPACE, KeyMap.TAB, KeyMap.AT, KeyMap.BACK_SLASH, KeyMap.SLASH, KeyMap.ALT ] as string[];
412443
if (blockedKeys.includes(e.key)) {
413444
e.preventDefault();
414445
if (e.key === KeyMap.ESCAPE) {
@@ -423,7 +454,7 @@ export class ChatPromptInput {
423454
const commandToSend = convertDetailedListItemToQuickActionCommand(targetDetailedListItem);
424455
if (this.quickPickType === 'context') {
425456
if (commandToSend.command !== '') {
426-
this.handleContextCommandSelection(commandToSend);
457+
this.handleContextCommandSelection(commandToSend, e.altKey);
427458
} else {
428459
// Otherwise pass the given text by user
429460
const command = this.promptTextInput.getTextInputValue().substring(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos());
@@ -602,7 +633,7 @@ export class ChatPromptInput {
602633
}
603634
};
604635

605-
private readonly handleContextCommandSelection = (dirtyContextCommand: QuickActionCommand): void => {
636+
private readonly handleContextCommandSelection = (dirtyContextCommand: QuickActionCommand, pinned?: boolean): void => {
606637
const contextCommand: QuickActionCommand = {
607638
...dirtyContextCommand,
608639
command: dirtyContextCommand.command.replace(this.markerRemovalRegex, '')
@@ -621,9 +652,14 @@ export class ChatPromptInput {
621652
tabId: this.props.tabId,
622653
promptInputCallback: (insert: boolean) => {
623654
if (insert) {
624-
this.promptTextInput.insertContextItem({
625-
...contextCommand,
626-
}, this.quickPickTriggerIndex);
655+
if (pinned === true) {
656+
this.promptTopBar.addContextPill(contextCommand);
657+
this.promptTextInput.deleteTextRange(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos());
658+
} else {
659+
this.promptTextInput.insertContextItem({
660+
...contextCommand,
661+
}, this.quickPickTriggerIndex);
662+
}
627663
} else {
628664
this.promptTextInput.deleteTextRange(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos());
629665
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../../../helper/dom';
2+
import { generateUID } from '../../../../helper/guid';
3+
import testIds from '../../../../helper/test-ids';
4+
import { QuickActionCommand, CheckboxFormItemGroup } from '../../../../static';
5+
import { Icon } from '../../../icon';
6+
import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../../../overlay';
7+
import { CheckboxGroupWrapper } from './checkbox-list-wrapper';
8+
9+
export interface ActionPillProps {
10+
actionPillConfig?: QuickActionCommand;
11+
actionPillItems?: CheckboxFormItemGroup[];
12+
}
13+
14+
// used for rules list
15+
export class ActionPill {
16+
render: ExtendedHTMLElement;
17+
props: ActionPillProps;
18+
overlay: Overlay;
19+
checklistSelectorContainer: CheckboxGroupWrapper;
20+
checkedItemState: Map<string, boolean>;
21+
22+
constructor (props: ActionPillProps) {
23+
this.props = props;
24+
this.checkedItemState = new Map<string, boolean>();
25+
const temporaryId = generateUID();
26+
27+
this.render = DomBuilder.getInstance().build({
28+
type: 'span',
29+
children: this.getActionPillChildren(),
30+
classNames: [ 'context', 'pinned' ],
31+
attributes: {
32+
'context-tmp-id': temporaryId,
33+
contenteditable: 'false'
34+
},
35+
events: {
36+
click: (e) => {
37+
this.showOverlay(e);
38+
}
39+
}
40+
});
41+
}
42+
43+
showOverlay (e: Event): void {
44+
this.overlay = new Overlay({
45+
testId: testIds.prompt.tobBarActionOverlay,
46+
background: true,
47+
closeOnOutsideClick: false, // false for easy debugging. switch back to true for prod
48+
referenceElement: (e.currentTarget ?? e.target) as HTMLElement,
49+
dimOutside: false,
50+
removeOtherOverlays: true,
51+
verticalDirection: OverlayVerticalDirection.TO_TOP,
52+
horizontalDirection: OverlayHorizontalDirection.END_TO_LEFT,
53+
children: [ this.getCheckListItemGroups() ]
54+
});
55+
}
56+
57+
getActionPillChildren (): Array<string | HTMLElement | DomBuilderObject> {
58+
return (this.props.actionPillConfig != null)
59+
? [
60+
...(this.props.actionPillConfig.icon != null ? [ new Icon({ icon: this.props.actionPillConfig.icon }).render ] : [ ]),
61+
{ type: 'span', classNames: [ 'at-char' ], innerHTML: '@' },
62+
`${this.props.actionPillConfig.command.replace(/^@?(.*)$/, '$1')}`
63+
]
64+
: [ '' ];
65+
}
66+
67+
handleItemSelected (command: QuickActionCommand): void {
68+
console.log('command clicked', command);
69+
}
70+
71+
private readonly getCheckListItemGroups = (): ExtendedHTMLElement => {
72+
// if (this.checklistSelectorContainer == null) {
73+
this.checklistSelectorContainer = new CheckboxGroupWrapper({ checkboxGroups: this.props.actionPillItems ?? [] });
74+
// } else {
75+
// this.checklistSelectorContainer.update({
76+
// checkboxGroups: this.props.actionPillItems ?? []
77+
// });
78+
// }
79+
return DomBuilder.getInstance().build({
80+
type: 'div',
81+
classNames: [ 'mynah-chat-prompt-quick-picks-overlay-wrapper' ],
82+
children: [
83+
this.checklistSelectorContainer.render
84+
]
85+
});
86+
};
87+
}

0 commit comments

Comments
 (0)