Skip to content

Commit 8d6467b

Browse files
committed
Modifying Agentic chat UX for view code diff and accept/reject files
1 parent 3fb3d0b commit 8d6467b

File tree

10 files changed

+193
-11
lines changed

10 files changed

+193
-11
lines changed

packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { ChatItemButton, ChatItemFormItem, ChatItemType, MynahUIDataModel, QuickActionCommand } from '@aws/mynah-ui'
6+
import {
7+
ChatItem,
8+
ChatItemButton,
9+
ChatItemFormItem,
10+
ChatItemType,
11+
MynahUIDataModel,
12+
QuickActionCommand,
13+
} from '@aws/mynah-ui'
714
import { TabType } from '../storages/tabsStorage'
815
import { CWCChatItem } from '../connector'
916
import { BaseConnector, BaseConnectorProps } from './baseConnector'
@@ -18,12 +25,14 @@ export interface ConnectorProps extends BaseConnectorProps {
1825
title?: string,
1926
description?: string
2027
) => void
28+
onChatAnswerUpdated?: (tabID: string, message: ChatItem) => void
2129
}
2230

2331
export class Connector extends BaseConnector {
2432
private readonly onCWCContextCommandMessage
2533
private readonly onContextCommandDataReceived
2634
private readonly onShowCustomForm
35+
private readonly onChatAnswerUpdated
2736

2837
override getTabType(): TabType {
2938
return 'cwc'
@@ -34,6 +43,7 @@ export class Connector extends BaseConnector {
3443
this.onCWCContextCommandMessage = props.onCWCContextCommandMessage
3544
this.onContextCommandDataReceived = props.onContextCommandDataReceived
3645
this.onShowCustomForm = props.onShowCustomForm
46+
this.onChatAnswerUpdated = props.onChatAnswerUpdated
3747
}
3848

3949
onSourceLinkClick = (tabID: string, messageId: string, link: string): void => {
@@ -91,16 +101,14 @@ export class Connector extends BaseConnector {
91101
messageId: messageData.messageID ?? messageData.triggerID,
92102
body: messageData.message,
93103
followUp: followUps,
94-
canBeVoted: true,
104+
canBeVoted: messageData.canBeVoted ?? false,
95105
codeReference: messageData.codeReference,
96106
userIntent: messageData.userIntent,
97107
codeBlockLanguage: messageData.codeBlockLanguage,
98108
contextList: messageData.contextList,
99-
}
100-
101-
// If it is not there we will not set it
102-
if (messageData.messageType === 'answer-part' || messageData.messageType === 'answer') {
103-
answer.canBeVoted = true
109+
title: messageData.title,
110+
buttons: messageData.buttons ?? undefined,
111+
fileList: messageData.fileList ?? undefined,
104112
}
105113

106114
if (messageData.relatedSuggestions !== undefined) {
@@ -137,6 +145,8 @@ export class Connector extends BaseConnector {
137145
options: messageData.followUps,
138146
}
139147
: undefined,
148+
buttons: messageData.buttons ?? undefined,
149+
canBeVoted: messageData.canBeVoted ?? false,
140150
}
141151
this.onChatAnswerReceived(messageData.tabID, answer, messageData)
142152

@@ -204,7 +214,7 @@ export class Connector extends BaseConnector {
204214
}
205215

206216
if (messageData.type === 'customFormActionMessage') {
207-
this.onCustomFormAction(messageData.tabID, messageData.action)
217+
this.onCustomFormAction(messageData.tabID, messageData.messageId, messageData.action)
208218
return
209219
}
210220
// For other message types, call the base class handleMessageReceive
@@ -235,6 +245,7 @@ export class Connector extends BaseConnector {
235245

236246
onCustomFormAction(
237247
tabId: string,
248+
messageId: string,
238249
action: {
239250
id: string
240251
text?: string | undefined
@@ -248,14 +259,53 @@ export class Connector extends BaseConnector {
248259
this.sendMessageToExtension({
249260
command: 'form-action-click',
250261
action: action,
262+
formSelectedValues: action.formItemValues,
251263
tabType: this.getTabType(),
252264
tabID: tabId,
253265
})
266+
267+
if (!this.onChatAnswerUpdated || !['accept-code-diff', 'reject-code-diff'].includes(action.id)) {
268+
return
269+
}
270+
const answer: ChatItem = {
271+
type: ChatItemType.ANSWER,
272+
messageId: messageId,
273+
buttons: [],
274+
}
275+
switch (action.id) {
276+
case 'accept-code-diff':
277+
answer.buttons = [
278+
{
279+
keepCardAfterClick: true,
280+
text: 'Accepted code',
281+
id: 'accepted-code-diff',
282+
status: 'success',
283+
position: 'outside',
284+
disabled: true,
285+
},
286+
]
287+
break
288+
case 'reject-code-diff':
289+
answer.buttons = [
290+
{
291+
keepCardAfterClick: true,
292+
text: 'Rejected code',
293+
id: 'rejected-code-diff',
294+
status: 'error',
295+
position: 'outside',
296+
disabled: true,
297+
},
298+
]
299+
break
300+
default:
301+
break
302+
}
303+
this.onChatAnswerUpdated(tabId, answer)
254304
}
255305

256306
onFileClick = (tabID: string, filePath: string, messageId?: string) => {
257307
this.sendMessageToExtension({
258-
command: 'file-click',
308+
command: messageId === '' ? 'file-click' : 'open-diff',
259309
tabID,
260310
messageId,
261311
filePath,

packages/core/src/amazonq/webview/ui/connector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface CWCChatItem extends ChatItem {
6363
userIntent?: UserIntent
6464
codeBlockLanguage?: string
6565
contextList?: Context[]
66+
title?: string
6667
}
6768

6869
export interface Context {
@@ -711,7 +712,7 @@ export class Connector {
711712
tabType: 'cwc',
712713
})
713714
} else {
714-
this.cwChatConnector.onCustomFormAction(tabId, action)
715+
this.cwChatConnector.onCustomFormAction(tabId, messageId ?? '', action)
715716
}
716717
break
717718
case 'agentWalkthrough': {

packages/core/src/amazonq/webview/ui/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ export const createMynahUI = (
352352
...(item.followUp !== undefined ? { followUp: item.followUp } : {}),
353353
...(item.fileList !== undefined ? { fileList: item.fileList } : {}),
354354
...(item.header !== undefined ? { header: item.header } : { header: undefined }),
355+
...(item.buttons !== undefined ? { buttons: item.buttons } : { buttons: undefined }),
355356
})
356357
if (
357358
item.messageId !== undefined &&
@@ -374,7 +375,7 @@ export const createMynahUI = (
374375
fileList: {
375376
fileTreeTitle: '',
376377
filePaths: item.contextList.map((file) => file.relativeFilePath),
377-
rootFolderTitle: 'Context',
378+
rootFolderTitle: item.title,
378379
flatList: true,
379380
collapsed: true,
380381
hideFileCount: true,

packages/core/src/codewhispererChat/app.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
AcceptDiff,
2929
QuickCommandGroupActionClick,
3030
FileClick,
31+
OpenDiff,
3132
} from './controllers/chat/model'
3233
import { EditorContextCommand, registerCommands } from './commands/registerCommands'
3334
import { ContextSelectedMessage, CustomFormActionMessage } from './view/connector/connector'
@@ -41,6 +42,7 @@ export function init(appContext: AmazonQAppInitContext) {
4142
processInsertCodeAtCursorPosition: new EventEmitter<InsertCodeAtCursorPosition>(),
4243
processAcceptDiff: new EventEmitter<AcceptDiff>(),
4344
processViewDiff: new EventEmitter<ViewDiff>(),
45+
processOpenDiff: new EventEmitter<OpenDiff>(),
4446
processCopyCodeToClipboard: new EventEmitter<CopyCodeToClipboard>(),
4547
processContextMenuCommand: new EventEmitter<EditorContextCommand>(),
4648
processTriggerTabIDReceived: new EventEmitter<TriggerTabIDReceived>(),
@@ -76,6 +78,7 @@ export function init(appContext: AmazonQAppInitContext) {
7678
),
7779
processAcceptDiff: new MessageListener<AcceptDiff>(cwChatControllerEventEmitters.processAcceptDiff),
7880
processViewDiff: new MessageListener<ViewDiff>(cwChatControllerEventEmitters.processViewDiff),
81+
processOpenDiff: new MessageListener<OpenDiff>(cwChatControllerEventEmitters.processOpenDiff),
7982
processCopyCodeToClipboard: new MessageListener<CopyCodeToClipboard>(
8083
cwChatControllerEventEmitters.processCopyCodeToClipboard
8184
),
@@ -137,6 +140,7 @@ export function init(appContext: AmazonQAppInitContext) {
137140
),
138141
processAcceptDiff: new MessagePublisher<AcceptDiff>(cwChatControllerEventEmitters.processAcceptDiff),
139142
processViewDiff: new MessagePublisher<ViewDiff>(cwChatControllerEventEmitters.processViewDiff),
143+
processOpenDiff: new MessagePublisher<OpenDiff>(cwChatControllerEventEmitters.processOpenDiff),
140144
processCopyCodeToClipboard: new MessagePublisher<CopyCodeToClipboard>(
141145
cwChatControllerEventEmitters.processCopyCodeToClipboard
142146
),

packages/core/src/codewhispererChat/clients/chat/v0/chat.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { UserWrittenCodeTracker } from '../../../../codewhisperer/tracker/userWr
1313

1414
export class ChatSession {
1515
private sessionId?: string
16+
private _listOfReadFiles: string[] = []
17+
private _filePath: string | undefined
18+
private _tempFilePath: string | undefined
1619

1720
contexts: Map<string, { first: number; second: number }[]> = new Map()
1821
// TODO: doesn't handle the edge case when two files share the same relativePath string but from different root
@@ -35,6 +38,28 @@ export class ChatSession {
3538
public setSessionID(id?: string) {
3639
this.sessionId = id
3740
}
41+
public get listOfReadFiles(): string[] {
42+
return this._listOfReadFiles
43+
}
44+
public get getFilePath(): string | undefined {
45+
return this._filePath
46+
}
47+
public get getTempFilePath(): string | undefined {
48+
return this._tempFilePath
49+
}
50+
public setFilePath(filePath: string | undefined) {
51+
this._filePath = filePath
52+
}
53+
public setTempFilePath(tempFilePath: string | undefined) {
54+
this._tempFilePath = tempFilePath
55+
}
56+
public pushToListOfReadFiles(filePath?: string) {
57+
if (filePath) {
58+
this._listOfReadFiles.push(filePath)
59+
} else {
60+
this._listOfReadFiles = []
61+
}
62+
}
3863
async chatIam(chatRequest: SendMessageRequest): Promise<SendMessageCommandOutput> {
3964
const client = await createQDeveloperStreamingClient()
4065

packages/core/src/codewhispererChat/controllers/chat/controller.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
DocumentReference,
3333
FileClick,
3434
RelevantTextDocumentAddition,
35+
OpenDiff,
3536
} from './model'
3637
import {
3738
AppToWebViewMessageDispatcher,
@@ -81,6 +82,7 @@ import {
8182
} from '../../constants'
8283
import { ChatSession } from '../../clients/chat/v0/chat'
8384
import { ChatHistoryManager } from '../../storages/chatHistory'
85+
import { amazonQTabSuffix } from '../../../shared/constants'
8486

8587
export interface ChatControllerMessagePublishers {
8688
readonly processPromptChatMessage: MessagePublisher<PromptMessage>
@@ -90,6 +92,7 @@ export interface ChatControllerMessagePublishers {
9092
readonly processInsertCodeAtCursorPosition: MessagePublisher<InsertCodeAtCursorPosition>
9193
readonly processAcceptDiff: MessagePublisher<AcceptDiff>
9294
readonly processViewDiff: MessagePublisher<ViewDiff>
95+
readonly processOpenDiff: MessagePublisher<OpenDiff>
9396
readonly processCopyCodeToClipboard: MessagePublisher<CopyCodeToClipboard>
9497
readonly processContextMenuCommand: MessagePublisher<EditorContextCommand>
9598
readonly processTriggerTabIDReceived: MessagePublisher<TriggerTabIDReceived>
@@ -115,6 +118,7 @@ export interface ChatControllerMessageListeners {
115118
readonly processInsertCodeAtCursorPosition: MessageListener<InsertCodeAtCursorPosition>
116119
readonly processAcceptDiff: MessageListener<AcceptDiff>
117120
readonly processViewDiff: MessageListener<ViewDiff>
121+
readonly processOpenDiff: MessageListener<OpenDiff>
118122
readonly processCopyCodeToClipboard: MessageListener<CopyCodeToClipboard>
119123
readonly processContextMenuCommand: MessageListener<EditorContextCommand>
120124
readonly processTriggerTabIDReceived: MessageListener<TriggerTabIDReceived>
@@ -213,6 +217,10 @@ export class ChatController {
213217
return this.processViewDiff(data)
214218
})
215219

220+
this.chatControllerMessageListeners.processOpenDiff.onMessage((data) => {
221+
return this.processOpenDiff(data)
222+
})
223+
216224
this.chatControllerMessageListeners.processCopyCodeToClipboard.onMessage((data) => {
217225
return this.processCopyCodeToClipboard(data)
218226
})
@@ -388,6 +396,45 @@ export class ChatController {
388396
})
389397
}
390398

399+
private async processOpenDiff(message: OpenDiff) {
400+
const session = this.sessionStorage.getSession(message.tabID)
401+
const filePath = session.getFilePath ?? message.filePath
402+
const fileExists = await fs.existsFile(filePath)
403+
// Check if fileExists=false, If yes, return instead of showing broken diff experience.
404+
if (!session.getTempFilePath) {
405+
return
406+
}
407+
const leftUri = fileExists ? vscode.Uri.file(filePath) : vscode.Uri.from({ scheme: 'untitled' })
408+
const rightUri = vscode.Uri.file(session.getTempFilePath ?? filePath)
409+
const fileName = path.basename(filePath)
410+
await vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, `${fileName} ${amazonQTabSuffix}`)
411+
}
412+
413+
private async processAcceptCodeDiff(message: CustomFormActionMessage) {
414+
const session = this.sessionStorage.getSession(message.tabID ?? '')
415+
const filePath = session.getFilePath ?? ''
416+
const fileExists = await fs.existsFile(filePath)
417+
const tempFilePath = session.getTempFilePath
418+
const tempFileExists = await fs.existsFile(tempFilePath ?? '')
419+
if (fileExists && tempFileExists) {
420+
const fileContent = await fs.readFileText(filePath)
421+
const tempFileContent = await fs.readFileText(tempFilePath ?? '')
422+
if (fileContent !== tempFileContent) {
423+
await fs.writeFile(filePath, tempFileContent)
424+
}
425+
await fs.delete(tempFilePath ?? '')
426+
await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath))
427+
} else if (!fileExists && tempFileExists) {
428+
const fileContent = await fs.readFileText(tempFilePath ?? '')
429+
await fs.writeFile(filePath, fileContent)
430+
await fs.delete(tempFilePath ?? '')
431+
await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath))
432+
}
433+
// Reset the filePaths to undefined
434+
session.setFilePath(undefined)
435+
session.setTempFilePath(undefined)
436+
}
437+
391438
private async processCopyCodeToClipboard(message: CopyCodeToClipboard) {
392439
this.telemetryHelper.recordInteractWithMessage(message)
393440
}
@@ -577,6 +624,12 @@ export class ChatController {
577624
const newFileDoc = await vscode.workspace.openTextDocument(newFilePath)
578625
await vscode.window.showTextDocument(newFileDoc)
579626
telemetry.ui_click.emit({ elementId: 'amazonq_createSavedPrompt' })
627+
} else if (message.action.id === 'accept-code-diff') {
628+
await this.processAcceptCodeDiff(message)
629+
} else if (message.action.id === 'reject-code-diff') {
630+
// Reset the filePaths to undefined
631+
this.sessionStorage.getSession(message.tabID ?? '').setFilePath(undefined)
632+
this.sessionStorage.getSession(message.tabID ?? '').setTempFilePath(undefined)
580633
}
581634
}
582635

@@ -835,6 +888,8 @@ export class ChatController {
835888
}
836889

837890
private async processPromptMessageAsNewThread(message: PromptMessage) {
891+
const session = this.sessionStorage.getSession(message.tabID)
892+
session.pushToListOfReadFiles(undefined)
838893
this.editorContextExtractor
839894
.extractContextForTrigger('ChatMessage')
840895
.then((context) => {

packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ export class Messenger {
8585
userIntent: undefined,
8686
codeBlockLanguage: undefined,
8787
contextList: mergedRelevantDocuments,
88+
title: 'Context',
89+
buttons: undefined,
90+
fileList: undefined,
91+
canBeVoted: false,
8892
},
8993
tabID
9094
)
@@ -436,6 +440,7 @@ export class Messenger {
436440
userIntent: undefined,
437441
codeBlockLanguage: undefined,
438442
contextList: undefined,
443+
title: undefined,
439444
},
440445
tabID
441446
)

packages/core/src/codewhispererChat/controllers/chat/model.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ export interface ViewDiff {
8989
totalCodeBlocks?: number
9090
}
9191

92+
export interface OpenDiff {
93+
command: string | undefined
94+
tabID: string
95+
messageId: string
96+
filePath: string
97+
referenceTrackerInformation?: CodeReference[]
98+
}
99+
92100
export type ChatPromptCommandType =
93101
| 'help'
94102
| 'clear'

0 commit comments

Comments
 (0)