Skip to content
Merged
Show file tree
Hide file tree
Changes from 91 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
84dc8f4
created new component to track modified files along with tests
sacrodge Sep 10, 2025
9cf60fe
refactored mynah-ui code as it had some duplication of code
sacrodge Sep 11, 2025
10e5b09
fixed formatting issues
sacrodge Sep 11, 2025
0f81966
working version for demo. Still no functionalities added like onClick…
sacrodge Sep 11, 2025
ca53a64
working version for demo. Still no functionalities added like onClick…
sacrodge Sep 11, 2025
3fa96d1
feat: add onClick and undo functionalities
sacrodge Sep 13, 2025
cdc71c2
feat: add onClick and undo functionalities
sacrodge Sep 13, 2025
eb725c9
feat: add onClick and undo functionalities
sacrodge Sep 13, 2025
d2096a1
msg: working-version : Added onFileClick functionality to open the fi…
sacrodge Sep 15, 2025
b752e8d
msg: working-version : Added onFileClick functionality to open the fi…
sacrodge Sep 15, 2025
50890e4
msg: working-version : Added onFileClick functionality to open the fi…
sacrodge Sep 15, 2025
10adc33
msg: working-version : Added onFileClick functionality to open the fi…
sacrodge Sep 15, 2025
38c0200
msg: working-version : Added onFileClick functionality to open the fi…
sacrodge Sep 15, 2025
68cb763
msg: refactoring code to use existing functionality
sacrodge Sep 16, 2025
5650932
msg: refactoring code to use existing functionality
sacrodge Sep 16, 2025
3ad8e8d
msg: arrow aligns with titleText
sacrodge Sep 16, 2025
0e8a803
msg: not working, imported file functionality from chat-item-card
sacrodge Sep 17, 2025
0144697
msg: not working, imported file functionality from chat-item-card
sacrodge Sep 17, 2025
d2659ad
msg: working-mynahUI, Lists files with undo buttons. refactored code
sacrodge Sep 17, 2025
fe41190
msg: working-mynahUI, Lists files with undo buttons. refactored code
sacrodge Sep 17, 2025
63695b8
msg: working-mynahUI, Lists files with undo buttons. refactored code
sacrodge Sep 17, 2025
1f1137f
msg: working-mynahUI, Lists files with undo buttons. refactored code
sacrodge Sep 17, 2025
37a3337
msg: working-mynahUI, Lists files with undo buttons. refactored code
sacrodge Sep 17, 2025
298d4f5
msg: working-mynahUI, Lists files with undo buttons. refactored code
sacrodge Sep 17, 2025
e667f83
msg: working, arrow now alogns with titleText
sacrodge Sep 17, 2025
516842e
msg: working, with no edits to language-servers
sacrodge Sep 17, 2025
8ca1a61
msg: working, with no edits to language-servers
sacrodge Sep 17, 2025
ca1c746
msg: UI works with files displayed as pills. not-tested with LS.
sacrodge Sep 17, 2025
a39316c
msg: UI works with files displayed as pills. not-tested with LS.
sacrodge Sep 17, 2025
2cf6ce6
msg: not-working, styled pills for better looks
sacrodge Sep 18, 2025
ff02f3a
msg: working
sacrodge Sep 18, 2025
d8b515f
msg: working
sacrodge Sep 18, 2025
0cf483d
msg: working-fully, but missing functionalities
sacrodge Sep 19, 2025
5827791
msg: working-fully, but missing functionalities
sacrodge Sep 19, 2025
1c69d1e
msg: working-fully, but missing functionalities
sacrodge Sep 19, 2025
2410e64
msg: working model;
sacrodge Sep 22, 2025
a31f1c8
msg: working model;
sacrodge Sep 22, 2025
865e71e
msg: working model;
sacrodge Sep 22, 2025
9945b21
msg: working;
sacrodge Sep 23, 2025
dc36e82
msg: working;
sacrodge Sep 23, 2025
c144998
msg: Working;
sacrodge Sep 23, 2025
59930b5
msg: Working;
sacrodge Sep 23, 2025
aef2e28
msg: working;
sacrodge Sep 23, 2025
608c329
msg: working;
sacrodge Sep 23, 2025
82b5f57
msg: working;
sacrodge Sep 23, 2025
4d9087a
msg: partially-working;
sacrodge Sep 24, 2025
91627ab
msg: partially-working;
sacrodge Sep 24, 2025
c6c18e4
msg: working:
sacrodge Sep 25, 2025
1ee0c80
msg: working;
sacrodge Sep 25, 2025
7ed1a55
build: working;
sacrodge Sep 25, 2025
a1eb653
msg: working;Added
sacrodge Sep 26, 2025
bae7b41
msg: partially-working;
sacrodge Sep 28, 2025
b059e5b
msg: working:completely
sacrodge Sep 29, 2025
ae378ad
msg: WORKING;COMPLETELY;
sacrodge Sep 29, 2025
d1622b6
msg:working-partially;
sacrodge Sep 30, 2025
b27b664
msg:working-partially;
sacrodge Sep 30, 2025
9e48653
msg: working-partially
sacrodge Sep 30, 2025
0a9c0bd
msg:working-partially
sacrodge Sep 30, 2025
59d40c6
msg:working-partially
sacrodge Sep 30, 2025
ea33df3
msg:working-completely;
sacrodge Sep 30, 2025
1fd9b6d
msg:working; removed unnecessary styling
sacrodge Oct 1, 2025
ae513bd
msg:working; removed unnecessary styling
sacrodge Oct 1, 2025
5e50abe
msg: Working;
sacrodge Oct 2, 2025
e1a040a
msg:working but not tested
sacrodge Oct 2, 2025
55a1081
msg:working-partially; Reverted unnecessary data structures
sacrodge Oct 7, 2025
a6e1b38
msg: working-partially with LS;
sacrodge Oct 7, 2025
b4c9240
msg: working-partially;
sacrodge Oct 7, 2025
635e0d8
msg: working-partially;
sacrodge Oct 7, 2025
3e11fc5
msg: working-partially;
sacrodge Oct 7, 2025
8d4e9d5
msg: working-partially;
sacrodge Oct 8, 2025
0c482da
msg: working-partially;
sacrodge Oct 8, 2025
023480d
msg: working-partially;
sacrodge Oct 8, 2025
cd11065
msg: working-partially;
sacrodge Oct 13, 2025
cd411f2
msg: working-completely;
sacrodge Oct 13, 2025
dc3f6a2
msg: working-completely;
sacrodge Oct 14, 2025
5535430
msg: working-partially;
sacrodge Oct 15, 2025
5aa50cd
msg: added comprehensive tests
sacrodge Oct 16, 2025
ce1482c
msg: E2E tests were failing because of the new component changing the…
sacrodge Oct 16, 2025
5b8b76a
msg: working-completely;
sacrodge Oct 16, 2025
62451f9
msg: working-completely;
sacrodge Oct 16, 2025
25a27b6
msg: working-completely:
sacrodge Oct 16, 2025
1e156fc
fixed following the PR comments
sacrodge Oct 21, 2025
b75c9fe
removed ! from the end of titletext, reflected the same in tests
sacrodge Oct 21, 2025
7180ca0
removed unnecessary logging in the component
sacrodge Oct 22, 2025
350c1a5
removed messageId prefix. Instead adding new properties to the model
sacrodge Oct 23, 2025
8da0558
msg: upon undo files are removed.
sacrodge Oct 24, 2025
ef3bf0b
added null checking
sacrodge Oct 28, 2025
8f2cdcd
switched from per file to using chatItems directly as easier to handl…
sacrodge Oct 29, 2025
270db71
consolidated rendering logic to remove duplication. multiple undoall …
sacrodge Oct 29, 2025
5f642fa
removing unnecessary rendering of the undoall button
sacrodge Oct 29, 2025
5069b92
added light border to the component and handling mutliple undoall but…
sacrodge Oct 29, 2025
d1d20a8
feat(amazonq): component now scrollable. added necessary tests
sacrodge Oct 30, 2025
74c3832
fix: clean naming modifiedFilesTracker
sacrodge Oct 30, 2025
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
2 changes: 1 addition & 1 deletion example/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1260,8 +1260,8 @@ here to see if it gets cut off properly as expected, with an ellipsis through cs
},
onChatPrompt: (tabId: string, prompt: ChatPrompt) => {




Log(`New prompt on tab: <b>${tabId}</b><br/>
prompt: <b>${prompt.prompt !== undefined && prompt.prompt !== '' ? prompt.prompt : '{command only}'}</b><br/>
command: <b>${prompt.command ?? '{none}'}</b><br/>
Expand Down
174 changes: 174 additions & 0 deletions src/components/__test__/modified-files-tracker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { ModifiedFilesTracker } from '../modified-files-tracker';
import { ChatItem, ChatItemType } from '../../static';
import { MynahUIGlobalEvents } from '../../helper/events';

// Mock global events
jest.mock('../../helper/events', () => ({
MynahUIGlobalEvents: {
getInstance: jest.fn(() => ({
dispatch: jest.fn()
}))
}
}));

describe('ModifiedFilesTracker', () => {
let mockDispatch: jest.Mock;

beforeEach(() => {
mockDispatch = jest.fn();
(MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue({
dispatch: mockDispatch
});
});

afterEach(() => {
jest.clearAllMocks();
});

it('should render basic modified files tracker', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });
expect(tracker.render).toBeDefined();
expect(tracker.render.classList.contains('hidden')).toBe(true);
});

describe('addChatItem', () => {
it('should add chat item with file list', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });
const chatItem: ChatItem = {
type: ChatItemType.ANSWER,
messageId: 'msg1',
header: {
fileList: {
filePaths: [ 'file1.ts' ],
deletedFiles: [],
actions: {},
details: {}
}
}
};

tracker.addChatItem(chatItem);
expect(tracker.render.classList.contains('hidden')).toBe(false);
});

it('should add chat item with buttons only', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });
const chatItem: ChatItem = {
type: ChatItemType.ANSWER,
messageId: 'msg2',
buttons: [ { id: 'undo-all', text: 'Undo All' } ]
};

tracker.addChatItem(chatItem);
expect(tracker.render.classList.contains('hidden')).toBe(true);
});

it('should not add chat item without messageId', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });
const chatItem: ChatItem = {
type: ChatItemType.ANSWER,
buttons: [ { id: 'undo-all', text: 'Undo All' } ]
};

tracker.addChatItem(chatItem);
expect(tracker.render.classList.contains('hidden')).toBe(true);
});
});

describe('undo-all button logic', () => {
it('should render undo-all button when files were rendered previously', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });

// Add file list first
const fileItem: ChatItem = {
type: ChatItemType.ANSWER,
messageId: 'file-msg',
header: {
fileList: {
filePaths: [ 'file1.ts' ],
deletedFiles: [],
actions: {},
details: {}
}
}
};

// Add undo-all button
const undoAllItem: ChatItem = {
type: ChatItemType.ANSWER,
messageId: 'undo-msg',
buttons: [ { id: 'undo-all', text: 'Undo All' } ]
};

tracker.addChatItem(fileItem);
tracker.addChatItem(undoAllItem);

const undoContainer = tracker.render.querySelector('.mynah-modified-files-undo-all-container');
expect(undoContainer).toBeTruthy();
});

it('should not render undo-all button when no files were rendered', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });

// Add only undo-all button without files
const undoAllItem: ChatItem = {
type: ChatItemType.ANSWER,
messageId: 'undo-msg',
buttons: [ { id: 'undo-all', text: 'Undo All' } ]
};

tracker.addChatItem(undoAllItem);

const undoContainer = tracker.render.querySelector('.mynah-modified-files-undo-all-container');
expect(undoContainer).toBeFalsy();
});
});

describe('removeChatItem', () => {
it('should remove chat item by messageId', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });
const chatItem: ChatItem = {
type: ChatItemType.ANSWER,
messageId: 'msg1',
header: {
fileList: {
filePaths: [ 'file1.ts' ],
deletedFiles: [],
actions: {},
details: {}
}
}
};

tracker.addChatItem(chatItem);
expect(tracker.render.classList.contains('hidden')).toBe(false);

tracker.removeChatItem('msg1');
expect(tracker.render.classList.contains('hidden')).toBe(true);
});
});

describe('clear', () => {
it('should clear all chat items and hide tracker', () => {
const tracker = new ModifiedFilesTracker({ tabId: 'test-tab' });
const chatItem: ChatItem = {
type: ChatItemType.ANSWER,
messageId: 'msg1',
header: {
fileList: {
filePaths: [ 'file1.ts' ],
deletedFiles: [],
actions: {},
details: {}
}
}
};

tracker.addChatItem(chatItem);
expect(tracker.render.classList.contains('hidden')).toBe(false);

tracker.clear();
expect(tracker.render.classList.contains('hidden')).toBe(true);
});
});
});
40 changes: 36 additions & 4 deletions src/components/chat-item/chat-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { StyleLoader } from '../../helper/style-loader';
import { Icon } from '../icon';
import { cancelEvent, MynahUIGlobalEvents } from '../../helper/events';
import { TopBarButtonOverlayProps } from './prompt-input/prompt-top-bar/top-bar-button';
import { ModifiedFilesTracker } from '../modified-files-tracker';

export const CONTAINER_GAP = 12;
export interface ChatWrapperProps {
Expand Down Expand Up @@ -58,6 +59,7 @@ export class ChatWrapper {
private readonly dragBlurOverlay: HTMLElement;
private dragOverlayVisibility: boolean = true;
private imageContextFeatureEnabled: boolean = false;
private readonly modifiedFilesTracker: ModifiedFilesTracker;

constructor (props: ChatWrapperProps) {
StyleLoader.getInstance().load('components/chat/_chat-wrapper.scss');
Expand Down Expand Up @@ -92,8 +94,14 @@ export class ChatWrapper {
this.imageContextFeatureEnabled = contextCommands.some(group =>
group.commands.some((cmd: QuickActionCommand) => cmd.command.toLowerCase() === 'image')
);

this.modifiedFilesTracker = new ModifiedFilesTracker({
tabId: this.props.tabId
});

MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'chatItems', (chatItems: ChatItem[]) => {
const chatItemToInsert: ChatItem = chatItems[chatItems.length - 1];

if (Object.keys(this.allRenderedChatItems).length === chatItems.length) {
const lastItem = this.chatItemsContainer.children.item(Array.from(this.chatItemsContainer.children).length - 1);
if (lastItem != null && chatItemToInsert != null) {
Expand All @@ -115,6 +123,8 @@ export class ChatWrapper {
this.chatItemsContainer.clear(true);
this.chatItemsContainer.insertChild('beforeend', this.getNewConversationGroupElement());
this.allRenderedChatItems = {};
// clear modifiedFilesTracker component as well
this.modifiedFilesTracker.clear();
}
});

Expand Down Expand Up @@ -310,6 +320,7 @@ export class ChatWrapper {
}
}).render,
this.promptStickyCard,
this.modifiedFilesTracker.render,
this.promptInputElement,
this.footerSpacer,
this.promptInfo,
Expand Down Expand Up @@ -377,14 +388,27 @@ export class ChatWrapper {
};

private readonly insertChatItem = (chatItem: ChatItem): void => {
// Normal flow on initially opening ui requires the currentMessageId;
this.removeEmptyCardsAndFollowups();

const currentMessageId: string = (chatItem.messageId != null && chatItem.messageId !== '') ? chatItem.messageId : `TEMP_${generateUID()}`;

// Create complete chatItem with proper messageId
const completeChatItem = {
...chatItem,
messageId: currentMessageId
};

// Check if forModifiedFilesTracker property is set in the chatItem
if (chatItem.forModifiedFilesTracker !== undefined) {
// Forward the complete chatItem with messageId to ModifiedFilesTracker
this.modifiedFilesTracker.addChatItem(completeChatItem);
}

// Normal flow for all other chat items
const chatItemCard = new ChatItemCard({
tabId: this.props.tabId,
chatItem: {
...chatItem,
messageId: currentMessageId
}
chatItem: completeChatItem
});

// When a new card appears, we're cleaning the last streaming card vars, since it is not the last anymore
Expand All @@ -409,6 +433,8 @@ export class ChatWrapper {
this.allRenderedChatItems[currentMessageId] = chatItemCard;

if (chatItem.type === ChatItemType.PROMPT || chatItem.type === ChatItemType.SYSTEM_PROMPT) {
// Clear modified files tracker on new prompt
this.modifiedFilesTracker.clear();
// Make sure we align to top when there is a new prompt.
// Only if it is a PROMPT!
// Check css application
Expand Down Expand Up @@ -493,6 +519,12 @@ export class ChatWrapper {
if (this.allRenderedChatItems[messageId]?.render !== undefined) {
this.allRenderedChatItems[messageId].updateCardStack(updateWith);

if (updateWith.forModifiedFilesTracker !== undefined) {
if (updateWith.forModifiedFilesTracker?.removeFile !== undefined && updateWith.forModifiedFilesTracker?.removeFile) {
this.modifiedFilesTracker.removeChatItem(messageId);
}
}

// If the last streaming chat answer is the same with the messageId
if (this.lastStreamingChatItemMessageId === messageId) {
this.checkLastAnswerStreamChange(updateWith);
Expand Down
9 changes: 7 additions & 2 deletions src/components/collapsible-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class CollapsibleContent {
private readonly props: Required<CollapsibleContentProps>;
private readonly uid: string;
private icon: ExtendedHTMLElement;
private readonly titleTextElement: ExtendedHTMLElement;
constructor (props: CollapsibleContentProps) {
StyleLoader.getInstance().load('components/_collapsible-content.scss');
this.uid = generateUID();
Expand Down Expand Up @@ -71,11 +72,11 @@ export class CollapsibleContent {
classNames: [ 'mynah-collapsible-content-label-title-wrapper' ],
children: [
this.icon,
{
this.titleTextElement = DomBuilder.getInstance().build({
type: 'span',
classNames: [ 'mynah-collapsible-content-label-title-text' ],
children: [ this.props.title ]
}
})
]
},
{
Expand All @@ -88,4 +89,8 @@ export class CollapsibleContent {
],
});
}

public updateTitle (newTitle: string): void {
this.titleTextElement.update({ children: [ newTitle ] });
}
}
Loading