Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d59e57e
poc
zixlin7 Aug 14, 2024
e053541
initial e2e poc
zixlin7 Aug 22, 2024
6776bb6
update to prompt and popup
zixlin7 Sep 20, 2024
a30e825
bugbash
zixlin7 Sep 30, 2024
f60db9b
bugfix and hint popup ux
zixlin7 Oct 11, 2024
3a80178
more bug fixes
zixlin7 Oct 11, 2024
425a375
more fixes
zixlin7 Oct 14, 2024
08f2797
move listeners
zixlin7 Oct 14, 2024
fbe4f62
cleanup
zixlin7 Oct 15, 2024
8795b69
fnf
zixlin7 Oct 15, 2024
06a6f0d
more fnf
zixlin7 Oct 16, 2024
69ce914
update to latest prompt
zixlin7 Oct 16, 2024
15081a2
cleanup
zixlin7 Oct 16, 2024
add9899
better popup position
zixlin7 Oct 16, 2024
fa53173
improve hint location
zixlin7 Oct 17, 2024
751f2bf
cleanup
zixlin7 Oct 17, 2024
f09a5fd
detekt
zixlin7 Oct 17, 2024
611bf64
rebase
zixlin7 Oct 17, 2024
1c9a928
fix after rebase
zixlin7 Oct 17, 2024
22a1dd9
small fix
zixlin7 Oct 17, 2024
52ec61d
remove gutter icon and improve popup location
zixlin7 Oct 18, 2024
e1cb930
fix undo and donot close popup when there is diff
zixlin7 Oct 18, 2024
6e44d76
fix error message and popup location, disable client prompt
zixlin7 Oct 18, 2024
7775724
small fixes
zixlin7 Oct 21, 2024
324ff94
Merge branch 'staging' into inlineChat
zixlin7 Oct 21, 2024
b8839bd
refactor
zixlin7 Oct 22, 2024
cc03925
cleanup
zixlin7 Oct 22, 2024
2988b00
cleanup logs
zixlin7 Oct 22, 2024
49fc2b2
Merge branch 'staging' into inlineChat
zixlin7 Oct 22, 2024
897e3a4
fix build
zixlin7 Oct 23, 2024
cdfdef2
detekt and pr feedback
zixlin7 Oct 23, 2024
cb2a169
refactor new ux
zixlin7 Oct 24, 2024
ad0a3af
fix request and prompt
zixlin7 Oct 25, 2024
562ebb5
Merge branch 'main' into inlineChat
zixlin7 Oct 25, 2024
377385a
fix detekt
zixlin7 Oct 25, 2024
dba54c2
fixes
zixlin7 Oct 27, 2024
7068e26
add to right click context menu
zixlin7 Oct 27, 2024
7f1e9f4
fix error handling
zixlin7 Oct 27, 2024
208bc85
Merge branch 'main' into inlineChat
zixlin7 Oct 28, 2024
1170e9f
telemetry change
zixlin7 Oct 28, 2024
fc0916c
Merge branch 'main' into inlineChat
zixlin7 Oct 28, 2024
cfdc0be
add change log
zixlin7 Oct 28, 2024
f7c7e2c
pr feedback
zixlin7 Oct 28, 2024
3e9781b
add read action to language extractor
zixlin7 Oct 28, 2024
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: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ assertJ = "3.26.3"
awsSdk = "2.26.25"
commonmark = "0.22.0"
detekt = "1.23.7"
diff-util = "4.12"
intellijExt = "1.1.8"
# match with <root>/settings.gradle.kts
intellijGradle = "2.1.0"
Expand Down Expand Up @@ -71,6 +72,7 @@ commons-collections = { module = "org.apache.commons:commons-collections4", vers
commons-io = { module = "commons-io:commons-io", version.ref = "apache-commons-io" }
detekt-api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" }
detekt-formattingRules = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
diff-util = { module = "io.github.java-diff-utils:java-diff-utils", version.ref = "diff-util" }
detekt-test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version.ref = "detekt" }
gradlePlugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" }
gradlePlugin-ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "intellijExt" }
Expand Down
1 change: 1 addition & 0 deletions plugins/amazonq/chat/jetbrains-community/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies {
implementation(project(":plugin-amazonq:shared:jetbrains-community"))
// everything references codewhisperer, which is not ideal
implementation(project(":plugin-amazonq:codewhisperer:jetbrains-community"))
implementation(libs.diff.util)

compileOnly(project(":plugin-core:jetbrains-community"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@
class="software.aws.toolkits.jetbrains.services.cwc.commands.SendToPromptAction">
<keyboard-shortcut keymap="$default" first-keystroke="meta alt S" />
</action>

<action id="aws.toolkit.jetbrains.core.services.cwc.inline.openChat"
class="software.aws.toolkits.jetbrains.services.cwc.inline.OpenChatInputAction">
<keyboard-shortcut keymap="Mac OS X" first-keystroke="meta I"/>
<keyboard-shortcut keymap="Mac OS X 10.5+" first-keystroke="meta I"/>
<keyboard-shortcut keymap="$default" first-keystroke="control I"/>
</action>
</group>
</actions>
</idea-plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.project.ProjectContextCo
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatController
import java.lang.management.ManagementFactory
import java.time.Duration
import java.util.concurrent.atomic.AtomicBoolean
Expand All @@ -31,6 +32,7 @@ class AmazonQStartupActivity : ProjectActivity {

// initialize html contents in BGT so users don't have to wait when they open the tool window
AmazonQToolWindow.getInstance(project)
InlineChatController.getInstance(project)

if (CodeWhispererExplorerActionManager.getInstance().getIsFirstRestartAfterQInstall()) {
runInEdt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum class TriggerType {
ContextMenu,
Hotkeys,
CodeScanButton,
Inline,
}

data class ChatRequestData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.Recommend
import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.Reference
import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.SuggestedFollowUp
import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.Suggestion
import software.aws.toolkits.jetbrains.services.cwc.clients.chat.model.TriggerType
import software.aws.toolkits.jetbrains.services.cwc.editor.context.ActiveFileContext

class ChatSessionV1(
Expand Down Expand Up @@ -200,6 +201,7 @@ class ChatSessionV1(
val userInputMessageContextBuilder = UserInputMessageContext.builder()
userInputMessageContextBuilder.editorState(activeFileContext.toEditorState(relevantTextDocuments, useRelevantDocuments))
val userInputMessageContext = userInputMessageContextBuilder.build()
val chatTriggerType = if (triggerType == TriggerType.Inline) ChatTriggerType.INLINE_CHAT else ChatTriggerType.MANUAL

val userInput = UserInputMessage.builder()
.content(message.take(ChatConstants.CUSTOMER_MESSAGE_SIZE_LIMIT))
Expand All @@ -209,7 +211,7 @@ class ChatSessionV1(
val conversationState = ConversationState.builder()
.conversationId(conversationId)
.currentMessage(ChatMessage.fromUserInputMessage(userInput))
.chatTriggerType(ChatTriggerType.MANUAL)
.chatTriggerType(chatTriggerType)
.customizationArn(customization?.arn)
.build()
return GenerateAssistantResponseRequest.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class ChatController private constructor(
) : InboundAppMessagesHandler {

private val messagePublisher: MessagePublisher = context.messagesFromAppToUi
private val telemetryHelper = TelemetryHelper(context, chatSessionStorage)
private val telemetryHelper = TelemetryHelper(context.project, chatSessionStorage)
constructor(
context: AmazonQAppInitContext,
) : this(
Expand Down Expand Up @@ -217,7 +217,7 @@ class ChatController private constructor(

editor.document.insertString(offset, message.code)

ReferenceLogController.addReferenceLog(message.code, message.codeReference, editor, context.project)
ReferenceLogController.addReferenceLog(message.code, message.codeReference, editor, context.project, null)

CodeWhispererUserModificationTracker.getInstance(context.project).enqueue(
InsertedCodeModificationEntry(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Reference
import software.amazon.awssdk.services.codewhispererruntime.model.Span
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.CodeReferenceGenerated
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition
import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager
import software.aws.toolkits.jetbrains.services.cwc.messages.CodeReference

object ReferenceLogController {
fun addReferenceLog(originalCode: String, codeReferences: List<CodeReference>?, editor: Editor, project: Project) {
fun addReferenceLog(originalCode: String, codeReferences: List<CodeReference>?, editor: Editor, project: Project, inlineChatStartPosition: CaretPosition?) {
codeReferences?.let { references ->
val cwReferences = references.map { reference ->
Reference.builder()
Expand All @@ -36,7 +37,7 @@ object ReferenceLogController {
originalCode,
cwReferences,
editor,
CodeWhispererEditorUtil.getCaretPosition(editor),
inlineChatStartPosition ?: CodeWhispererEditorUtil.getCaretPosition(editor),
null,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import software.amazon.awssdk.awscore.exception.AwsServiceException
import software.amazon.awssdk.services.codewhispererstreaming.model.CodeWhispererStreamingException
Expand Down Expand Up @@ -55,6 +56,7 @@ class ChatPromptHandler(private val telemetryHelper: TelemetryHelper) {
data: ChatRequestData,
sessionInfo: ChatSessionInfo,
shouldAddIndexInProgressMessage: Boolean,
isInlineChat: Boolean = false,
) = flow {
val session = sessionInfo.session
session.chat(data)
Expand Down Expand Up @@ -135,14 +137,19 @@ class ChatPromptHandler(private val telemetryHelper: TelemetryHelper) {
)
}
}
.onEach { responseEvent ->
if (isInlineChat) processChatEvent(tabId, triggerId, data, responseEvent, shouldAddIndexInProgressMessage)?.let { emit(it) }
}
.collect { responseEvent ->
processChatEvent(
tabId,
triggerId,
data,
responseEvent,
shouldAddIndexInProgressMessage
)?.let { emit(it) }
if (!isInlineChat) {
processChatEvent(
tabId,
triggerId,
data,
responseEvent,
shouldAddIndexInProgressMessage
)?.let { emit(it) }
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@

package software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry

import com.intellij.openapi.project.Project
import org.jetbrains.annotations.VisibleForTesting
import software.amazon.awssdk.services.codewhispererruntime.model.ChatInteractWithMessageEvent
import software.amazon.awssdk.services.codewhispererruntime.model.ChatMessageInteractionType
import software.amazon.awssdk.services.codewhispererruntime.model.InlineChatUserDecision
import software.amazon.awssdk.services.codewhispererstreaming.model.UserIntent
import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.info
import software.aws.toolkits.core.utils.warn
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererCustomization
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
Expand All @@ -39,14 +40,14 @@ import java.time.Duration
import java.time.Instant
import software.amazon.awssdk.services.codewhispererruntime.model.UserIntent as CWClientUserIntent

class TelemetryHelper(private val context: AmazonQAppInitContext, private val sessionStorage: ChatSessionStorage) {
class TelemetryHelper(private val project: Project, private val sessionStorage: ChatSessionStorage) {
private val responseStreamStartTime: MutableMap<String, Instant> = mutableMapOf()
private val responseStreamTotalTime: MutableMap<String, Int> = mutableMapOf()
private val responseStreamTimeForChunks: MutableMap<String, MutableList<Instant>> = mutableMapOf()
private val responseHasProjectContext: MutableMap<String, Boolean> = mutableMapOf()

private val customization: CodeWhispererCustomization?
get() = CodeWhispererModelConfigurator.getInstance().activeCustomization(context.project)
get() = CodeWhispererModelConfigurator.getInstance().activeCustomization(project)

fun getConversationId(tabId: String): String? = sessionStorage.getSession(tabId)?.session?.conversationId

Expand All @@ -63,7 +64,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
}

private fun getTelemetryTriggerType(triggerType: TriggerType): CwsprChatTriggerInteraction = when (triggerType) {
TriggerType.Click, TriggerType.CodeScanButton -> CwsprChatTriggerInteraction.Click
TriggerType.Click, TriggerType.CodeScanButton, TriggerType.Inline -> CwsprChatTriggerInteraction.Click
TriggerType.ContextMenu, TriggerType.Hotkeys -> CwsprChatTriggerInteraction.ContextMenu
}

Expand All @@ -89,7 +90,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
cwsprChatUserIntent = data.userIntent?.let { getTelemetryUserIntent(it) },
cwsprChatHasCodeSnippet = data.activeFileContext.focusAreaContext?.codeSelection?.isNotEmpty() ?: false,
cwsprChatProgrammingLanguage = data.activeFileContext.fileContext?.fileLanguage,
credentialStartUrl = getStartUrl(context.project),
credentialStartUrl = getStartUrl(project),
cwsprChatHasProjectContext = getIsProjectContextEnabled() && data.useRelevantDocuments && data.relevantTextDocuments.isNotEmpty()
)
}
Expand All @@ -116,15 +117,15 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
cwsprChatRequestLength = data.message.length.toLong(),
cwsprChatResponseLength = responseLength.toLong(),
cwsprChatConversationType = CwsprChatConversationType.Chat,
credentialStartUrl = getStartUrl(context.project),
credentialStartUrl = getStartUrl(project),
codewhispererCustomizationArn = data.customization?.arn,
cwsprChatHasProjectContext = getMessageHasProjectContext(response.messageId)
)

val programmingLanguage = data.activeFileContext.fileContext?.fileLanguage
val validProgrammingLanguage = if (ChatSessionV1.validLanguages.contains(programmingLanguage)) programmingLanguage else null

CodeWhispererClientAdaptor.getInstance(context.project).sendChatAddMessageTelemetry(
CodeWhispererClientAdaptor.getInstance(project).sendChatAddMessageTelemetry(
getConversationId(response.tabId).orEmpty(),
response.messageId,
CWClientUserIntent.fromValue(data.userIntent?.name),
Expand All @@ -139,11 +140,29 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
numberOfCodeBlocks,
getMessageHasProjectContext(response.messageId),
data.customization
).also {
logger.debug {
"Successfully sendTelemetryEvent for ChatAddMessage with requestId=${it.responseMetadata().requestId()}"
}
}
)
}

fun recordInlineChatTelemetry(
requestId: String,
inputLength: Int?,
numSelectedLines: Int?,
codeIntent: Boolean?,
userDecision: InlineChatUserDecision?,
responseStartLatency: Double?,
responseEndLatency: Double?,
numSuggestionAddChars: Int?,
numSuggestionAddLines: Int?,
numSuggestionDelChars: Int?,
numSuggestionDelLines: Int?,
charactersAdded: Int?,
charactersRemoved: Int?,
) {
CodeWhispererClientAdaptor.getInstance(project).sendInlineChatTelemetry(
requestId, inputLength, numSelectedLines, codeIntent, userDecision,
responseStartLatency, responseEndLatency, numSuggestionAddChars, numSuggestionAddLines, numSuggestionDelChars, numSuggestionDelLines,
charactersAdded, charactersRemoved
)
}

fun recordMessageResponseError(data: ChatRequestData, tabId: String, responseCode: Int) {
Expand All @@ -158,7 +177,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
cwsprChatResponseCode = responseCode.toLong(),
cwsprChatRequestLength = data.message.length.toLong(),
cwsprChatConversationType = CwsprChatConversationType.Chat,
credentialStartUrl = getStartUrl(context.project)
credentialStartUrl = getStartUrl(project)
)
}

Expand All @@ -174,7 +193,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
"downvote" -> CwsprChatInteractionType.Downvote
else -> CwsprChatInteractionType.Unknown
},
credentialStartUrl = getStartUrl(context.project),
credentialStartUrl = getStartUrl(project),
cwsprChatHasProjectContext = getMessageHasProjectContext(message.messageId)
)
ChatInteractWithMessageEvent.builder().apply {
Expand All @@ -196,7 +215,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
cwsprChatConversationId = getConversationId(message.tabId).orEmpty(),
cwsprChatMessageId = message.messageId.orEmpty(),
cwsprChatInteractionType = CwsprChatInteractionType.ClickFollowUp,
credentialStartUrl = getStartUrl(context.project),
credentialStartUrl = getStartUrl(project),
cwsprChatHasProjectContext = getMessageHasProjectContext(message.messageId.orEmpty())
)
ChatInteractWithMessageEvent.builder().apply {
Expand All @@ -216,7 +235,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
cwsprChatAcceptedCharactersLength = message.code.length.toLong(),
cwsprChatInteractionTarget = message.insertionTargetType,
cwsprChatHasReference = null,
credentialStartUrl = getStartUrl(context.project),
credentialStartUrl = getStartUrl(project),
cwsprChatCodeBlockIndex = message.codeBlockIndex?.toLong(),
cwsprChatTotalCodeBlocks = message.totalCodeBlocks?.toLong(),
cwsprChatHasProjectContext = getMessageHasProjectContext(message.messageId),
Expand All @@ -242,7 +261,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
cwsprChatAcceptedNumberOfLines = message.code.lines().size.toLong(),
cwsprChatInteractionTarget = message.insertionTargetType,
cwsprChatHasReference = null,
credentialStartUrl = getStartUrl(context.project),
credentialStartUrl = getStartUrl(project),
cwsprChatCodeBlockIndex = message.codeBlockIndex?.toLong(),
cwsprChatTotalCodeBlocks = message.totalCodeBlocks?.toLong(),
cwsprChatHasProjectContext = getMessageHasProjectContext(message.messageId),
Expand Down Expand Up @@ -274,7 +293,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
cwsprChatInteractionType = linkInteractionType,
cwsprChatInteractionTarget = message.link,
cwsprChatHasReference = null,
credentialStartUrl = getStartUrl(context.project),
credentialStartUrl = getStartUrl(project),
cwsprChatHasProjectContext = getMessageHasProjectContext(message.messageId)
)
ChatInteractWithMessageEvent.builder().apply {
Expand Down Expand Up @@ -308,7 +327,7 @@ class TelemetryHelper(private val context: AmazonQAppInitContext, private val se
}

event?.let {
val steResponse = CodeWhispererClientAdaptor.getInstance(context.project).sendChatInteractWithMessageTelemetry(it)
val steResponse = CodeWhispererClientAdaptor.getInstance(project).sendChatInteractWithMessageTelemetry(it)
logger.debug {
"Successfully sendTelemetryEvent for ChatInteractWithMessage with requestId=${steResponse.responseMetadata().requestId()}"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ActiveFileContextExtractor(
}

companion object {
fun create(fqnWebviewAdapter: FqnWebviewAdapter, project: Project) = ActiveFileContextExtractor(
fun create(fqnWebviewAdapter: FqnWebviewAdapter?, project: Project) = ActiveFileContextExtractor(
fileContextExtractor = FileContextExtractor(fqnWebviewAdapter, project),
focusAreaContextExtractor = FocusAreaContextExtractor(fqnWebviewAdapter, project),
)
Expand Down
Loading
Loading