From 43cd8ca7d71f6af1d8c2245a264c2c2c999f00e7 Mon Sep 17 00:00:00 2001 From: Kevin Ding Date: Mon, 24 Mar 2025 11:07:11 -0400 Subject: [PATCH] chore: migrate types constants and errors files for doc --- .../chat/agents/docGeneration/constants.ts | 162 ++++++++++++++++++ .../chat/agents/docGeneration/errors.ts | 64 +++++++ .../chat/agents/shared/i18n.ts | 3 + .../chat/agents/shared/types.ts | 63 +++++++ 4 files changed, 292 insertions(+) create mode 100644 server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/constants.ts create mode 100644 server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/errors.ts create mode 100644 server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/i18n.ts create mode 100644 server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/types.ts diff --git a/server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/constants.ts b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/constants.ts new file mode 100644 index 0000000000..4a857dac27 --- /dev/null +++ b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/constants.ts @@ -0,0 +1,162 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MynahIcons, Status } from '@aws/mynah-ui' +import { FollowUpTypes, NewFileInfo } from '../shared/types' +import { i18n } from '../shared/i18n' + +// For uniquely identifiying which chat messages should be routed to Doc +export const docChat = 'docChat' + +export const docScheme = 'aws-doc' + +export const featureName = 'Amazon Q Doc Generation' + +export function getFileSummaryPercentage(input: string): number { + // Split the input string by newline characters + const lines = input.split('\n') + + // Find the line containing "summarized:" + const summaryLine = lines.find(line => line.includes('summarized:')) + + // If the line is not found, return null + if (!summaryLine) { + return -1 + } + + // Extract the numbers from the summary line + const [summarized, total] = summaryLine.split(':')[1].trim().split(' of ').map(Number) + + // Calculate the percentage + const percentage = (summarized / total) * 100 + + return percentage +} + +const checkIcons = { + wait: '☐', + current: '☐', + done: '☑', +} + +const getIconForStep = (targetStep: number, currentStep: number) => { + return currentStep === targetStep + ? checkIcons.current + : currentStep > targetStep + ? checkIcons.done + : checkIcons.wait +} + +export enum DocGenerationStep { + UPLOAD_TO_S3, + SUMMARIZING_FILES, + GENERATING_ARTIFACTS, +} + +export const docGenerationProgressMessage = (currentStep: DocGenerationStep, mode: Mode) => ` +${mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.creating') : i18n('AWS.amazonq.doc.answer.updating')} + +${getIconForStep(DocGenerationStep.UPLOAD_TO_S3, currentStep)} ${i18n('AWS.amazonq.doc.answer.scanning')} + +${getIconForStep(DocGenerationStep.SUMMARIZING_FILES, currentStep)} ${i18n('AWS.amazonq.doc.answer.summarizing')} + +${getIconForStep(DocGenerationStep.GENERATING_ARTIFACTS, currentStep)} ${i18n('AWS.amazonq.doc.answer.generating')} + + +` + +export const docGenerationSuccessMessage = (mode: Mode) => + mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated') + +export const docRejectConfirmation = 'Your changes have been discarded.' + +export const FolderSelectorFollowUps = [ + { + icon: 'ok' as MynahIcons, + pillText: 'Yes', + prompt: 'Yes', + status: 'success' as Status, + type: FollowUpTypes.ProceedFolderSelection, + }, + { + icon: 'refresh' as MynahIcons, + pillText: 'Change folder', + prompt: 'Change folder', + status: 'info' as Status, + type: FollowUpTypes.ChooseFolder, + }, + { + icon: 'cancel' as MynahIcons, + pillText: 'Cancel', + prompt: 'Cancel', + status: 'error' as Status, + type: FollowUpTypes.CancelFolderSelection, + }, +] + +export const CodeChangeFollowUps = [ + { + pillText: i18n('AWS.amazonq.doc.pillText.accept'), + prompt: i18n('AWS.amazonq.doc.pillText.accept'), + type: FollowUpTypes.AcceptChanges, + icon: 'ok' as MynahIcons, + status: 'success' as Status, + }, + { + pillText: i18n('AWS.amazonq.doc.pillText.makeChanges'), + prompt: i18n('AWS.amazonq.doc.pillText.makeChanges'), + type: FollowUpTypes.MakeChanges, + icon: 'refresh' as MynahIcons, + status: 'info' as Status, + }, + { + pillText: i18n('AWS.amazonq.doc.pillText.reject'), + prompt: i18n('AWS.amazonq.doc.pillText.reject'), + type: FollowUpTypes.RejectChanges, + icon: 'cancel' as MynahIcons, + status: 'error' as Status, + }, +] + +export const NewSessionFollowUps = [ + { + pillText: i18n('AWS.amazonq.doc.pillText.newTask'), + type: FollowUpTypes.NewTask, + status: 'info' as Status, + }, + { + pillText: i18n('AWS.amazonq.doc.pillText.closeSession'), + type: FollowUpTypes.CloseSession, + status: 'info' as Status, + }, +] + +export const SynchronizeDocumentation = { + pillText: i18n('AWS.amazonq.doc.pillText.update'), + prompt: i18n('AWS.amazonq.doc.pillText.update'), + type: FollowUpTypes.SynchronizeDocumentation, +} + +export const EditDocumentation = { + pillText: i18n('AWS.amazonq.doc.pillText.makeChange'), + prompt: i18n('AWS.amazonq.doc.pillText.makeChange'), + type: FollowUpTypes.EditDocumentation, +} + +export enum Mode { + NONE = 'None', + CREATE = 'Create', + SYNC = 'Sync', + EDIT = 'Edit', +} + +/** + * + * @param paths file paths + * @returns the path to a README.md, or undefined if none exist + */ +export const findReadmePath = (paths?: NewFileInfo[]) => { + return paths?.find(path => /readme\.md$/i.test(path.relativePath)) +} diff --git a/server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/errors.ts b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/errors.ts new file mode 100644 index 0000000000..528d661de1 --- /dev/null +++ b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/docGeneration/errors.ts @@ -0,0 +1,64 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//TODO: extend the toolkit error once ready +// import { ClientError, ContentLengthError as CommonContentLengthError } from '../shared/errors' +import { i18n } from '../shared/i18n' + +export class DocClientError { + remainingIterations?: number + constructor(message: string, code: string, remainingIterations?: number) { + // super(message, { code }) + this.remainingIterations = remainingIterations + } +} + +export class ReadmeTooLargeError extends DocClientError { + constructor() { + super(i18n('AWS.amazonq.doc.error.readmeTooLarge'), ReadmeTooLargeError.name) + } +} + +export class ReadmeUpdateTooLargeError extends DocClientError { + constructor(remainingIterations: number) { + super(i18n('AWS.amazonq.doc.error.readmeUpdateTooLarge'), ReadmeUpdateTooLargeError.name, remainingIterations) + } +} + +export class WorkspaceEmptyError extends DocClientError { + constructor() { + super(i18n('AWS.amazonq.doc.error.workspaceEmpty'), WorkspaceEmptyError.name) + } +} + +export class NoChangeRequiredException extends DocClientError { + constructor() { + super(i18n('AWS.amazonq.doc.error.noChangeRequiredException'), NoChangeRequiredException.name) + } +} + +export class PromptRefusalException extends DocClientError { + constructor(remainingIterations: number) { + super(i18n('AWS.amazonq.doc.error.promptRefusal'), PromptRefusalException.name, remainingIterations) + } +} + +// TODO: move the common content length error to shared folder +// export class ContentLengthError extends CommonContentLengthError { +// constructor() { +// super(i18n('AWS.amazonq.doc.error.contentLengthError'), { code: ContentLengthError.name }) +// } +// } +export class PromptTooVagueError extends DocClientError { + constructor(remainingIterations: number) { + super(i18n('AWS.amazonq.doc.error.promptTooVague'), PromptTooVagueError.name, remainingIterations) + } +} + +export class PromptUnrelatedError extends DocClientError { + constructor(remainingIterations: number) { + super(i18n('AWS.amazonq.doc.error.promptUnrelated'), PromptUnrelatedError.name, remainingIterations) + } +} diff --git a/server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/i18n.ts b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/i18n.ts new file mode 100644 index 0000000000..3d4cfc37b6 --- /dev/null +++ b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/i18n.ts @@ -0,0 +1,3 @@ +export function i18n(text: string): string { + return text +} diff --git a/server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/types.ts b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/types.ts new file mode 100644 index 0000000000..8b4b3e8c43 --- /dev/null +++ b/server/aws-lsp-codewhisperer/src/language-server/chat/agents/shared/types.ts @@ -0,0 +1,63 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { WorkspaceFolder } from '@aws/language-server-runtimes/protocol' + +//TODO: May not be needed follow up with flare +export enum FollowUpTypes { + // UnitTestGeneration + ViewDiff = 'ViewDiff', + AcceptCode = 'AcceptCode', + RejectCode = 'RejectCode', + BuildAndExecute = 'BuildAndExecute', + ModifyCommands = 'ModifyCommands', + SkipBuildAndFinish = 'SkipBuildAndFinish', + InstallDependenciesAndContinue = 'InstallDependenciesAndContinue', + ContinueBuildAndExecute = 'ContinueBuildAndExecute', + ViewCodeDiffAfterIteration = 'ViewCodeDiffAfterIteration', + // FeatureDev + GenerateCode = 'GenerateCode', + InsertCode = 'InsertCode', + ProvideFeedbackAndRegenerateCode = 'ProvideFeedbackAndRegenerateCode', + Retry = 'Retry', + ModifyDefaultSourceFolder = 'ModifyDefaultSourceFolder', + DevExamples = 'DevExamples', + NewTask = 'NewTask', + CloseSession = 'CloseSession', + SendFeedback = 'SendFeedback', + AcceptAutoBuild = 'AcceptAutoBuild', + DenyAutoBuild = 'DenyAutoBuild', + GenerateDevFile = 'GenerateDevFile', + // Doc + CreateDocumentation = 'CreateDocumentation', + ChooseFolder = 'ChooseFolder', + UpdateDocumentation = 'UpdateDocumentation', + SynchronizeDocumentation = 'SynchronizeDocumentation', + EditDocumentation = 'EditDocumentation', + AcceptChanges = 'AcceptChanges', + RejectChanges = 'RejectChanges', + MakeChanges = 'MakeChanges', + ProceedFolderSelection = 'ProceedFolderSelection', + CancelFolderSelection = 'CancelFolderSelection', +} + +export type DiffTreeFileInfo = { + zipFilePath: string + relativePath: string + rejected: boolean + changeApplied: boolean +} + +export type NewFileZipContents = { zipFilePath: string; fileContent: string } + +export type DeletedFileInfo = DiffTreeFileInfo & { + workspaceFolder: WorkspaceFolder +} + +export type NewFileInfo = DiffTreeFileInfo & + NewFileZipContents & { + virtualMemoryUri: string + workspaceFolder: WorkspaceFolder + }