Skip to content

Commit 20c6aaa

Browse files
Merge master into feature/stepfunctions-workflow
2 parents a53bc53 + 22d5a8a commit 20c6aaa

File tree

5 files changed

+201
-91
lines changed

5 files changed

+201
-91
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "/doc: Usage in multiple chat tabs may cause unexpected behavior."
4+
}

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

Lines changed: 72 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
} from '../../../shared/utilities/workspaceUtils'
4444
import { getPathsFromZipFilePath, SvgFileExtension } from '../../../amazonq/util/files'
4545
import { FollowUpTypes } from '../../../amazonq/commons/types'
46-
import { DocGenerationTask } from '../docGenerationTask'
46+
import { DocGenerationTask, DocGenerationTasks } from '../docGenerationTask'
4747
import { DevPhase } from '../../types'
4848

4949
export interface ChatControllerEventEmitters {
@@ -67,9 +67,7 @@ export class DocController {
6767
private readonly messenger: DocMessenger
6868
private readonly sessionStorage: BaseChatSessionStorage<Session>
6969
private authController: AuthController
70-
private folderPath = ''
71-
private mode: Mode = Mode.NONE
72-
public docGenerationTask: DocGenerationTask
70+
private docGenerationTasks: DocGenerationTasks
7371

7472
public constructor(
7573
private readonly chatControllerMessageListeners: ChatControllerEventEmitters,
@@ -80,7 +78,7 @@ export class DocController {
8078
this.messenger = messenger
8179
this.sessionStorage = sessionStorage
8280
this.authController = new AuthController()
83-
this.docGenerationTask = new DocGenerationTask()
81+
this.docGenerationTasks = new DocGenerationTasks()
8482

8583
this.chatControllerMessageListeners.processHumanChatMessage.event((data) => {
8684
this.processUserChatMessage(data).catch((e) => {
@@ -157,23 +155,23 @@ export class DocController {
157155
} else {
158156
let displayPath = ''
159157
const relativePath = getWorkspaceRelativePath(uri.fsPath)
160-
158+
const docGenerationTask = this.docGenerationTasks.getTask(data.tabID)
161159
if (relativePath) {
162160
// Display path should always include workspace folder name
163161
displayPath = path.join(relativePath.workspaceFolder.name, relativePath.relativePath)
164162
// Only include workspace folder name in API call if multi-root workspace
165-
this.folderPath = isMultiRootWorkspace() ? displayPath : relativePath.relativePath
163+
docGenerationTask.folderPath = isMultiRootWorkspace() ? displayPath : relativePath.relativePath
166164

167165
if (!relativePath.relativePath) {
168-
this.docGenerationTask.folderLevel = 'ENTIRE_WORKSPACE'
166+
docGenerationTask.folderLevel = 'ENTIRE_WORKSPACE'
169167
} else {
170-
this.docGenerationTask.folderLevel = 'SUB_FOLDER'
168+
docGenerationTask.folderLevel = 'SUB_FOLDER'
171169
}
172170
}
173171

174172
this.messenger.sendFolderConfirmationMessage(
175173
data.tabID,
176-
this.mode === Mode.CREATE
174+
docGenerationTask.mode === Mode.CREATE
177175
? i18n('AWS.amazonq.doc.answer.createReadme')
178176
: i18n('AWS.amazonq.doc.answer.updateReadme'),
179177
displayPath,
@@ -218,6 +216,7 @@ export class DocController {
218216
private initializeFollowUps(): void {
219217
this.chatControllerMessageListeners.followUpClicked.event(async (data) => {
220218
const session: Session = await this.sessionStorage.getSession(data.tabID)
219+
const docGenerationTask = this.docGenerationTasks.getTask(data.tabID)
221220

222221
const workspaceFolders = vscode.workspace.workspaceFolders
223222
if (workspaceFolders === undefined || workspaceFolders.length === 0) {
@@ -245,7 +244,7 @@ export class DocController {
245244

246245
switch (data.followUp.type) {
247246
case FollowUpTypes.Retry:
248-
if (this.mode === Mode.EDIT) {
247+
if (docGenerationTask.mode === Mode.EDIT) {
249248
this.enableUserInput(data?.tabID)
250249
} else {
251250
await this.tabOpened(data)
@@ -262,19 +261,19 @@ export class DocController {
262261
case FollowUpTypes.CloseSession:
263262
return this.closeSession(data)
264263
case FollowUpTypes.CreateDocumentation:
265-
this.docGenerationTask.interactionType = 'GENERATE_README'
266-
this.mode = Mode.CREATE
264+
docGenerationTask.interactionType = 'GENERATE_README'
265+
docGenerationTask.mode = Mode.CREATE
267266
sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.createReadme'))
268267
break
269268
case FollowUpTypes.ChooseFolder:
270269
await this.folderSelector(data)
271270
break
272271
case FollowUpTypes.SynchronizeDocumentation:
273-
this.mode = Mode.SYNC
272+
docGenerationTask.mode = Mode.SYNC
274273
sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.updateReadme'))
275274
break
276275
case FollowUpTypes.UpdateDocumentation:
277-
this.docGenerationTask.interactionType = 'UPDATE_README'
276+
docGenerationTask.interactionType = 'UPDATE_README'
278277
this.messenger.sendAnswer({
279278
type: 'answer',
280279
tabID: data?.tabID,
@@ -283,21 +282,21 @@ export class DocController {
283282
})
284283
break
285284
case FollowUpTypes.EditDocumentation:
286-
this.docGenerationTask.interactionType = 'EDIT_README'
287-
this.mode = Mode.EDIT
285+
docGenerationTask.interactionType = 'EDIT_README'
286+
docGenerationTask.mode = Mode.EDIT
288287
sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.updateReadme'))
289288
break
290289
case FollowUpTypes.MakeChanges:
291-
this.mode = Mode.EDIT
290+
docGenerationTask.mode = Mode.EDIT
292291
this.enableUserInput(data.tabID)
293292
break
294293
case FollowUpTypes.AcceptChanges:
295-
this.docGenerationTask.userDecision = 'ACCEPT'
294+
docGenerationTask.userDecision = 'ACCEPT'
296295
await this.sendDocAcceptanceEvent(data)
297296
await this.insertCode(data)
298297
return
299298
case FollowUpTypes.RejectChanges:
300-
this.docGenerationTask.userDecision = 'REJECT'
299+
docGenerationTask.userDecision = 'REJECT'
301300
await this.sendDocAcceptanceEvent(data)
302301
this.messenger.sendAnswer({
303302
type: 'answer',
@@ -309,26 +308,27 @@ export class DocController {
309308
break
310309
case FollowUpTypes.ProceedFolderSelection:
311310
// If a user did not change the folder in a multi-root workspace, default to the first workspace folder
312-
if (this.folderPath === '' && isMultiRootWorkspace()) {
313-
this.folderPath = workspaceFolderName
311+
if (docGenerationTask.folderPath === '' && isMultiRootWorkspace()) {
312+
docGenerationTask.folderPath = workspaceFolderName
314313
}
315-
if (this.mode === Mode.EDIT) {
314+
if (docGenerationTask.mode === Mode.EDIT) {
316315
this.enableUserInput(data.tabID)
317316
} else {
318-
await this.generateDocumentation({
319-
message: {
317+
await this.generateDocumentation(
318+
{
320319
...data,
321320
message:
322-
this.mode === Mode.CREATE
321+
docGenerationTask.mode === Mode.CREATE
323322
? 'Create documentation for a specific folder'
324323
: 'Sync documentation',
325324
},
326325
session,
327-
})
326+
docGenerationTask
327+
)
328328
}
329329
break
330330
case FollowUpTypes.CancelFolderSelection:
331-
this.docGenerationTask.folderLevel = 'ENTIRE_WORKSPACE'
331+
docGenerationTask.reset()
332332
return this.tabOpened(data)
333333
}
334334
})
@@ -383,7 +383,8 @@ export class DocController {
383383

384384
private async newTask(message: any) {
385385
// Old session for the tab is ending, delete it so we can create a new one for the message id
386-
this.docGenerationTask = new DocGenerationTask()
386+
387+
this.docGenerationTasks.deleteTask(message.tabID)
387388
this.sessionStorage.deleteSession(message.tabID)
388389

389390
// Re-run the opening flow, where we check auth + create a session
@@ -399,18 +400,22 @@ export class DocController {
399400
})
400401
this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.sessionClosed'))
401402
this.messenger.sendChatInputEnabled(message.tabID, false)
402-
403-
this.docGenerationTask.reset()
403+
this.docGenerationTasks.deleteTask(message.tabID)
404404
}
405405

406-
private processErrorChatMessage = (err: any, message: any, session: Session | undefined) => {
406+
private processErrorChatMessage = (
407+
err: any,
408+
message: any,
409+
session: Session | undefined,
410+
docGenerationTask: DocGenerationTask
411+
) => {
407412
const errorMessage = createUserFacingErrorMessage(`${err.cause?.message ?? err.message}`)
408413
// eslint-disable-next-line unicorn/no-null
409414
this.messenger.sendUpdatePromptProgress(message.tabID, null)
410415
if (err.constructor.name === MonthlyConversationLimitError.name) {
411416
this.messenger.sendMonthlyLimitError(message.tabID)
412417
} else {
413-
const enableUserInput = this.mode === Mode.EDIT && err.remainingIterations > 0
418+
const enableUserInput = docGenerationTask.mode === Mode.EDIT && err.remainingIterations > 0
414419

415420
this.messenger.sendErrorMessage(
416421
errorMessage,
@@ -423,11 +428,11 @@ export class DocController {
423428
}
424429
}
425430

426-
private async generateDocumentation({ message, session }: { message: any; session: any }) {
431+
private async generateDocumentation(message: any, session: any, docGenerationTask: DocGenerationTask) {
427432
try {
428-
await this.onDocsGeneration(session, message.message, message.tabID)
433+
await this.onDocsGeneration(session, message.message, message.tabID, docGenerationTask)
429434
} catch (err: any) {
430-
this.processErrorChatMessage(err, message, session)
435+
this.processErrorChatMessage(err, message, session, docGenerationTask)
431436
}
432437
}
433438

@@ -448,6 +453,7 @@ export class DocController {
448453
}
449454

450455
const session: Session = await this.sessionStorage.getSession(message.tabID)
456+
const docGenerationTask = this.docGenerationTasks.getTask(message.tabID)
451457

452458
try {
453459
getLogger().debug(`${featureName}: Processing message: ${message.message}`)
@@ -459,9 +465,9 @@ export class DocController {
459465
return
460466
}
461467

462-
await this.generateDocumentation({ message, session })
468+
await this.generateDocumentation(message, session, docGenerationTask)
463469
} catch (err: any) {
464-
this.processErrorChatMessage(err, message, session)
470+
this.processErrorChatMessage(err, message, session, docGenerationTask)
465471
}
466472
}
467473

@@ -483,17 +489,18 @@ export class DocController {
483489
let session: Session | undefined
484490
try {
485491
session = await this.sessionStorage.getSession(message.tabID)
492+
const docGenerationTask = this.docGenerationTasks.getTask(message.tabID)
486493
getLogger().debug(`${featureName}: Session created with id: ${session.tabID}`)
487-
this.folderPath = ''
488-
this.mode = Mode.NONE
494+
docGenerationTask.folderPath = ''
495+
docGenerationTask.mode = Mode.NONE
489496

490497
const authState = await AuthUtil.instance.getChatAuthState()
491498
if (authState.amazonQ !== 'connected') {
492499
void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID)
493500
session.isAuthenticating = true
494501
return
495502
}
496-
this.docGenerationTask.numberOfNavigations += 1
503+
docGenerationTask.numberOfNavigations += 1
497504
this.messenger.sendAnswer({
498505
type: 'answer',
499506
tabID: message.tabID,
@@ -536,13 +543,18 @@ export class DocController {
536543
await vscode.commands.executeCommand('markdown.showPreview')
537544
}
538545

539-
private async onDocsGeneration(session: Session, message: string, tabID: string) {
540-
this.messenger.sendDocProgress(tabID, DocGenerationStep.UPLOAD_TO_S3, 0, this.mode)
546+
private async onDocsGeneration(
547+
session: Session,
548+
message: string,
549+
tabID: string,
550+
docGenerationTask: DocGenerationTask
551+
) {
552+
this.messenger.sendDocProgress(tabID, DocGenerationStep.UPLOAD_TO_S3, 0, docGenerationTask.mode)
541553

542554
await session.preloader(message)
543555

544556
try {
545-
await session.send(message, this.mode, this.folderPath)
557+
await session.send(message, docGenerationTask.mode, docGenerationTask.folderPath)
546558
const filePaths = session.state.filePaths ?? []
547559
const deletedFiles = session.state.deletedFiles ?? []
548560

@@ -585,7 +597,7 @@ export class DocController {
585597
this.messenger.sendAnswer({
586598
type: 'answer',
587599
tabID: tabID,
588-
message: `${this.mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated')} ${remainingIterations > 0 ? i18n('AWS.amazonq.doc.answer.codeResult') : i18n('AWS.amazonq.doc.answer.acceptOrReject')}`,
600+
message: `${docGenerationTask.mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated')} ${remainingIterations > 0 ? i18n('AWS.amazonq.doc.answer.codeResult') : i18n('AWS.amazonq.doc.answer.acceptOrReject')}`,
589601
disableChatInput: true,
590602
})
591603

@@ -601,13 +613,14 @@ export class DocController {
601613
})
602614
}
603615
if (session?.state.phase === DevPhase.CODEGEN) {
616+
const docGenerationTask = this.docGenerationTasks.getTask(tabID)
604617
const { totalGeneratedChars, totalGeneratedLines, totalGeneratedFiles } =
605-
await session.countGeneratedContent(this.docGenerationTask.interactionType)
606-
this.docGenerationTask.conversationId = session.conversationId
607-
this.docGenerationTask.numberOfGeneratedChars = totalGeneratedChars
608-
this.docGenerationTask.numberOfGeneratedLines = totalGeneratedLines
609-
this.docGenerationTask.numberOfGeneratedFiles = totalGeneratedFiles
610-
const docGenerationEvent = this.docGenerationTask.docGenerationEventBase()
618+
await session.countGeneratedContent(docGenerationTask.interactionType)
619+
docGenerationTask.conversationId = session.conversationId
620+
docGenerationTask.numberOfGeneratedChars = totalGeneratedChars
621+
docGenerationTask.numberOfGeneratedLines = totalGeneratedLines
622+
docGenerationTask.numberOfGeneratedFiles = totalGeneratedFiles
623+
const docGenerationEvent = docGenerationTask.docGenerationEventBase()
611624

612625
await session.sendDocTelemetryEvent(docGenerationEvent, 'generation')
613626
}
@@ -635,6 +648,7 @@ export class DocController {
635648

636649
private tabClosed(message: any) {
637650
this.sessionStorage.deleteSession(message.tabID)
651+
this.docGenerationTasks.deleteTask(message.tabID)
638652
}
639653

640654
private async insertCode(message: any) {
@@ -670,14 +684,15 @@ export class DocController {
670684
}
671685
private async sendDocAcceptanceEvent(message: any) {
672686
const session = await this.sessionStorage.getSession(message.tabID)
673-
this.docGenerationTask.conversationId = session.conversationId
687+
const docGenerationTask = this.docGenerationTasks.getTask(message.tabID)
688+
docGenerationTask.conversationId = session.conversationId
674689
const { totalAddedChars, totalAddedLines, totalAddedFiles } = await session.countAddedContent(
675-
this.docGenerationTask.interactionType
690+
docGenerationTask.interactionType
676691
)
677-
this.docGenerationTask.numberOfAddedChars = totalAddedChars
678-
this.docGenerationTask.numberOfAddedLines = totalAddedLines
679-
this.docGenerationTask.numberOfAddedFiles = totalAddedFiles
680-
const docAcceptanceEvent = this.docGenerationTask.docAcceptanceEventBase()
692+
docGenerationTask.numberOfAddedChars = totalAddedChars
693+
docGenerationTask.numberOfAddedLines = totalAddedLines
694+
docGenerationTask.numberOfAddedFiles = totalAddedFiles
695+
const docAcceptanceEvent = docGenerationTask.docAcceptanceEventBase()
681696

682697
await session.sendDocTelemetryEvent(docAcceptanceEvent, 'acceptance')
683698
}

packages/core/src/amazonqDoc/controllers/docGenerationTask.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,26 @@ import {
1010
DocV2GenerationEvent,
1111
} from '../../codewhisperer/client/codewhispereruserclient'
1212
import { getLogger } from '../../shared/logger/logger'
13+
import { Mode } from '../constants'
14+
15+
export class DocGenerationTasks {
16+
private tasks: Map<string, DocGenerationTask> = new Map()
17+
18+
public getTask(tabId: string): DocGenerationTask {
19+
if (!this.tasks.has(tabId)) {
20+
this.tasks.set(tabId, new DocGenerationTask())
21+
}
22+
return this.tasks.get(tabId)!
23+
}
24+
25+
public deleteTask(tabId: string): void {
26+
this.tasks.delete(tabId)
27+
}
28+
}
1329

1430
export class DocGenerationTask {
31+
public mode: Mode = Mode.NONE
32+
public folderPath = ''
1533
// Telemetry fields
1634
public conversationId?: string
1735
public numberOfAddedChars?: number
@@ -25,10 +43,6 @@ export class DocGenerationTask {
2543
public numberOfNavigations = 0
2644
public folderLevel: DocFolderLevel = 'ENTIRE_WORKSPACE'
2745

28-
constructor(conversationId?: string) {
29-
this.conversationId = conversationId
30-
}
31-
3246
public docGenerationEventBase() {
3347
const undefinedProps = Object.entries(this)
3448
.filter(([key, value]) => value === undefined)

0 commit comments

Comments
 (0)