Skip to content

Commit f6a4e20

Browse files
laileni-awsctlai95
andauthored
fix(chat): UX fixes for agentic chat (#7098)
## Problem - IDE requests permission twice when accessing files outside workspace ![image](https://github.com/user-attachments/assets/a74fd330-91ca-417c-98e9-11b0cab2d4b6) - Users can see insert at cursor and copy buttons for bash commands. - UX stying: No borders for - Read and List directory files/folder outside of the workspace - Code diff view UX - Execute bash command UX - `Insert at cursor` option is available if pair programming mode is enabled. ## Solution - Fixed issue issue of showing reading files twice. ![image](https://github.com/user-attachments/assets/a57e4004-bf18-4d09-978c-8bbc1dda98da) - Fixed issue of showing insert at cursor and copy buttons for bash commands. - Added borders for mentioned UX. - User can see only `Copy` option (remove `Insert at cursor` option) if pair programming mode is enabled. ![image](https://github.com/user-attachments/assets/548a949e-61f4-4c53-b736-3d0c9c12e5c5) ![image](https://github.com/user-attachments/assets/5622a673-673e-4eb0-ad6a-26cd7a815dcf) - If user turn off pair programming mode, IDE shows both `Insert at cursor` and `Copy` options. ![image](https://github.com/user-attachments/assets/de8d8622-e080-489d-92ee-6d4aac8c2b5d) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Co-authored-by: Tai Lai <[email protected]>
1 parent a465656 commit f6a4e20

File tree

3 files changed

+39
-12
lines changed

3 files changed

+39
-12
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ export const createMynahUI = (
178178
let messageController: MessageController
179179

180180
function getCodeBlockActions(messageData: any) {
181+
if (!messageData.codeBlockActions) {
182+
return {}
183+
}
181184
// Show ViewDiff and AcceptDiff for allowedCommands in CWC
182185
const isEnabled = featureConfigs.get('ViewDiffInChat')?.variation === 'TREATMENT'
183186
const tab = tabsStorage.getTab(messageData?.tabID || '')
@@ -204,6 +207,15 @@ export const createMynahUI = (
204207
},
205208
}
206209
}
210+
if (tab?.type === 'cwc' && messageData.codeBlockActions['insert-to-cursor'] === null) {
211+
const actions: Record<string, undefined> = {
212+
'insert-to-cursor': undefined,
213+
}
214+
if (messageData.codeBlockActions['copy'] === null) {
215+
actions.copy = undefined
216+
}
217+
return actions
218+
}
207219
// Show only "Copy" option for codeblocks in Q Test Tab
208220
if (tab?.type === 'testgen') {
209221
return {

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
SelectTabMessage,
2222
ChatItemHeader,
2323
ToolMessage,
24+
ChatMessageType,
2425
} from '../../../view/connector/connector'
2526
import { EditorContextCommandType } from '../../../commands/registerCommands'
2627
import { ChatResponseStream as qdevChatResponseStream } from '@amzn/amazon-q-developer-streaming-client'
@@ -53,6 +54,7 @@ import {
5354
MynahIconsType,
5455
DetailedList,
5556
MynahUIDataModel,
57+
CodeBlockActions,
5658
} from '@aws/mynah-ui'
5759
import { Database } from '../../../../shared/db/chatDb/chatDb'
5860
import { TabType } from '../../../../amazonq/webview/ui/storages/tabsStorage'
@@ -71,6 +73,7 @@ import { ConversationTracker } from '../../../storages/conversationTracker'
7173
import { waitTimeout, Timeout } from '../../../../shared/utilities/timeoutUtils'
7274
import { FsReadParams } from '../../../tools/fsRead'
7375
import { ListDirectoryParams } from '../../../tools/listDirectory'
76+
import { fs } from '../../../../shared/fs/fs'
7477

7578
export type StaticTextResponseType = 'quick-action-help' | 'onboarding-help' | 'transform' | 'help'
7679

@@ -193,12 +196,16 @@ export class Messenger {
193196
return codeBlocks.length
194197
}
195198

196-
public handleFileReadOrListOperation = (session: ChatSession, toolUse: ToolUse, tool: Tool) => {
199+
public handleFileReadOrListOperation = async (session: ChatSession, toolUse: ToolUse, tool: Tool) => {
197200
const messageIdToUpdate =
198201
tool.type === ToolType.FsRead ? session.messageIdToUpdate : session.messageIdToUpdateListDirectory
199202
const messageId = messageIdToUpdate ?? toolUse?.toolUseId ?? ''
200203
const operationType = tool.type === ToolType.FsRead ? 'read' : 'listDir'
201204
const input = toolUse.input as unknown as FsReadParams | ListDirectoryParams
205+
const fileExists = await fs.exists(input.path)
206+
if (!fileExists) {
207+
return messageIdToUpdate
208+
}
202209
const existingPaths = session.getFilePathsByMessageId(messageId)
203210

204211
// Check if path already exists in the list
@@ -359,7 +366,7 @@ export class Messenger {
359366
changeList = await tool.tool.getDiffChanges()
360367
}
361368
if (isReadOrList) {
362-
messageIdToUpdate = this.handleFileReadOrListOperation(session, toolUse, tool)
369+
messageIdToUpdate = await this.handleFileReadOrListOperation(session, toolUse, tool)
363370
}
364371
const validation = ToolUtils.requiresAcceptance(tool)
365372
const chatStream = new ChatStream(
@@ -471,7 +478,11 @@ export class Messenger {
471478
if (this.isTriggerCancelled(triggerID)) {
472479
return
473480
}
474-
481+
let codeBlockActions: CodeBlockActions | null = {}
482+
if (session.pairProgrammingModeOn) {
483+
// eslint-disable-next-line unicorn/no-null
484+
codeBlockActions = { 'insert-to-cursor': null }
485+
}
475486
this.dispatcher.sendChatMessage(
476487
new ChatMessage(
477488
{
@@ -486,6 +497,7 @@ export class Messenger {
486497
userIntent: triggerPayload.userIntent,
487498
codeBlockLanguage: codeBlockLanguage,
488499
contextList: undefined,
500+
codeBlockActions,
489501
},
490502
tabID
491503
)
@@ -768,8 +780,10 @@ export class Messenger {
768780
const buttons: ChatItemButton[] = []
769781
let header: ChatItemHeader | undefined = undefined
770782
let messageID: string = toolUse?.toolUseId ?? ''
783+
let messageType: ChatMessageType = 'answer-part'
771784
if (toolUse?.name === ToolType.ExecuteBash && message.startsWith('```shell')) {
772785
if (validation.requiresAcceptance) {
786+
messageType = 'answer'
773787
const buttons: ChatItemButton[] = [
774788
{
775789
id: 'reject-shell-command',
@@ -796,6 +810,7 @@ export class Messenger {
796810
if (this.isTriggerCancelled(triggerID)) {
797811
return
798812
}
813+
messageType = 'answer'
799814
const input = toolUse.input as unknown as FsWriteParams
800815
const fileName = path.basename(input.path)
801816
const changes = getDiffLinesFromChanges(changeList)
@@ -829,6 +844,7 @@ export class Messenger {
829844
* requiredAcceptance = false, we use messageID = toolID and we keep on updating this messageID
830845
* requiredAcceptance = true, IDE sends messageID != toolID, some default value, as this overlaps with previous message. */
831846
messageID = 'toolUse'
847+
messageType = 'answer'
832848
const buttons: ChatItemButton[] = [
833849
{
834850
id: 'confirm-tool-use',
@@ -858,8 +874,8 @@ export class Messenger {
858874
this.dispatcher.sendChatMessage(
859875
new ChatMessage(
860876
{
861-
message: message,
862-
messageType: toolUse?.name === ToolType.FsWrite ? 'answer' : 'answer-part',
877+
message,
878+
messageType,
863879
followUps: undefined,
864880
followUpsHeader: undefined,
865881
relatedSuggestions: undefined,

packages/core/src/codewhispererChat/tools/fsRead.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,20 @@ export class FsRead {
5252
if (requiresAcceptance) {
5353
const fileName = path.basename(this.fsPath)
5454
const fileUri = vscode.Uri.file(this.fsPath)
55-
updates.write(`Reading file: [${fileName}](${fileUri}), `)
56-
5755
const [start, end] = this.readRange ?? []
58-
56+
let readMessage = ''
5957
if (start && end) {
60-
updates.write(`from line ${start} to ${end}`)
58+
readMessage += `from line ${start} to ${end}`
6159
} else if (start) {
6260
if (start > 0) {
63-
updates.write(`from line ${start} to end of file`)
61+
readMessage += `from line ${start} to end of file`
6462
} else {
65-
updates.write(`${start} line from the end of file to end of file`)
63+
readMessage += `${start} line from the end of file to end of file`
6664
}
6765
} else {
68-
updates.write('all lines')
66+
readMessage += 'all lines'
6967
}
68+
updates.write(`Reading file: [${fileName}](${fileUri}), ${readMessage}`)
7069
} else {
7170
updates.write('')
7271
}

0 commit comments

Comments
 (0)