From ea7a1edbd6b6e46ab0aaa7784b10a45f6f416ae8 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:28:18 +0800 Subject: [PATCH 01/13] feat: Optimize webview communication - Refactored JavaScript communication to use more efficient methods. - Introduced sendToWebView method for improved payload handling. - Updated action handling to align with new communication structure. --- .../ai/devchat/core/BaseActionHandler.kt | 2 +- src/main/kotlin/ai/devchat/plugin/Browser.kt | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/ai/devchat/core/BaseActionHandler.kt b/src/main/kotlin/ai/devchat/core/BaseActionHandler.kt index eabc072..eb872a9 100644 --- a/src/main/kotlin/ai/devchat/core/BaseActionHandler.kt +++ b/src/main/kotlin/ai/devchat/core/BaseActionHandler.kt @@ -43,7 +43,7 @@ abstract class BaseActionHandler( "error" to "" )) response["payload"] = payload ?: JSONObject() - browser!!.executeJS(jsCallback, response) + browser!!.sendToWebView(payload ?: JSONObject()) } override fun executeAction() { diff --git a/src/main/kotlin/ai/devchat/plugin/Browser.kt b/src/main/kotlin/ai/devchat/plugin/Browser.kt index 39030aa..1d3993b 100644 --- a/src/main/kotlin/ai/devchat/plugin/Browser.kt +++ b/src/main/kotlin/ai/devchat/plugin/Browser.kt @@ -35,6 +35,14 @@ class Browser(val project: Project): Disposable { jbCefBrowser.cefBrowser.executeJavaScript(funcCall, "", 0) } + fun sendToWebView(message: Any) { + jbCefBrowser.cefBrowser.executeJavaScript( + "window.postMessage(${JSON.toJSONString(message)});", + jbCefBrowser.cefBrowser.url, + 0 + ) + } + private fun callJava(arg: String): JBCefJSQuery.Response { Log.info("JSON string from JS: $arg") var jsonArg = arg @@ -44,9 +52,10 @@ class Browser(val project: Project): Disposable { // Parse the json parameter val jsonObject = JSON.parseObject(jsonArg) - val action = jsonObject.getString("action") - val metadata = jsonObject.getJSONObject("metadata") - val payload = jsonObject.getJSONObject("payload") + + val action = jsonObject.getString("command") + val metadata = jsonObject + val payload = jsonObject Log.info("Got action: $action") ActionHandlerFactory().createActionHandler(project, action, metadata, payload)?.let { ApplicationManager.getApplication().invokeLater { @@ -73,7 +82,14 @@ class Browser(val project: Project): Disposable { "response => console.log(response)", "(error_code, error_message) => console.log('callJava Failed', error_code, error_message)" )} - }}; + }}; + window.acquireIdeaCodeApi = function() { + return { + postMessage: function(message) { + window.JSJavaBridge.callJava(JSON.stringify(message)); + } + }; + }; """.trimIndent(), jbCefBrowser.cefBrowser.url, 0 ) From 6cf312510507f4d5994e7731ada68561ff391b6b Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:28:49 +0800 Subject: [PATCH 02/13] feat: Optimize webview communication actions - Updated action constants for improved readability and functionality. - Adjusted constants to better reflect the actions they represent. - Removed unnecessary prefixes from action strings for cleaner integration. --- .../kotlin/ai/devchat/core/DevChatActions.kt | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/ai/devchat/core/DevChatActions.kt b/src/main/kotlin/ai/devchat/core/DevChatActions.kt index 0781862..9bbb975 100644 --- a/src/main/kotlin/ai/devchat/core/DevChatActions.kt +++ b/src/main/kotlin/ai/devchat/core/DevChatActions.kt @@ -1,48 +1,49 @@ package ai.devchat.core object DevChatActions { - const val SEND_MESSAGE_REQUEST = "sendMessage/request" + const val SEND_MESSAGE_REQUEST = "sendMessage" const val SEND_MESSAGE_RESPONSE = "sendMessage/response" - const val REGENERATION_REQUEST = "regeneration/request" + const val REGENERATION_REQUEST = "regeneration" const val SEND_USER_MESSAGE_REQUEST = "sendUserMessage/request" - const val SEND_USER_MESSAGE_RESPONSE = "sendUserMessage/response" + const val SEND_USER_MESSAGE_RESPONSE = "chatWithDevChat" const val CODE_DIFF_APPLY_REQUEST = "codeDiffApply/request" - const val CODE_DIFF_APPLY_RESPONSE = "codeDiffApply/response" - const val ADD_CONTEXT_NOTIFY = "addContext/notify" - const val LIST_COMMANDS_REQUEST = "listCommands/request" - const val LIST_COMMANDS_RESPONSE = "listCommands/response" - const val LOAD_CONVERSATIONS_REQUEST = "loadConversations/request" + const val CODE_DIFF_APPLY_RESPONSE = "codeDiffApply" + const val ADD_CONTEXT_NOTIFY = "contextDetailResponse" + const val LIST_COMMANDS_REQUEST = "regCommandList" + const val LIST_COMMANDS_RESPONSE = "regCommandList" + const val NEW_CONVERSATIONS_REQUEST = "setNewTopic" + const val LOAD_CONVERSATIONS_REQUEST = "getTopicDetail" const val LOAD_CONVERSATIONS_RESPONSE = "loadConversations/response" - const val LOAD_HISTORY_MESSAGES_REQUEST = "loadHistoryMessages/request" - const val LOAD_HISTORY_MESSAGES_RESPONSE = "loadHistoryMessages/response" - const val OPEN_LINK_REQUEST = "openLink/request" + const val LOAD_HISTORY_MESSAGES_REQUEST = "historyMessages" + const val LOAD_HISTORY_MESSAGES_RESPONSE = "reloadMessage" + const val OPEN_LINK_REQUEST = "openLink" const val OPEN_LINK_RESPONSE = "openLink/response" - const val LIST_TOPICS_REQUEST = "listTopics/request" - const val LIST_TOPICS_RESPONSE = "listTopics/response" - const val INSERT_CODE_REQUEST = "insertCode/request" + const val LIST_TOPICS_REQUEST = "getTopics" + const val LIST_TOPICS_RESPONSE = "listTopics" + const val INSERT_CODE_REQUEST = "code_apply" const val INSERT_CODE_RESPONSE = "insertCode/response" - const val NEW_SRC_FILE_REQUEST = "newSrcFile/request" + const val NEW_SRC_FILE_REQUEST = "code_new_file" const val NEW_SRC_FILE_RESPONSE = "newSrcFile/response" - const val REPLACE_FILE_CONTENT_REQUEST = "replaceFileContent/request" + const val REPLACE_FILE_CONTENT_REQUEST = "code_file_apply" const val REPLACE_FILE_CONTENT_RESPONSE = "replaceFileContent/response" - const val VIEW_DIFF_REQUEST = "viewDiff/request" + const val VIEW_DIFF_REQUEST = "show_diff" const val VIEW_DIFF_RESPONSE = "viewDiff/response" - const val GET_IDE_SERVICE_PORT_REQUEST = "getIDEServicePort/request" - const val GET_IDE_SERVICE_PORT_RESPONSE = "getIDEServicePort/response" - const val GET_SETTING_REQUEST = "getSetting/request" - const val GET_SETTING_RESPONSE = "getSetting/response" - const val UPDATE_SETTING_REQUEST = "updateSetting/request" - const val UPDATE_SETTING_RESPONSE = "updateSetting/response" - const val GET_SERVER_SETTINGS_REQUEST = "getServerSettings/request" - const val GET_SERVER_SETTINGS_RESPONSE = "getServerSettings/response" - const val UPDATE_SERVER_SETTINGS_REQUEST = "updateServerSettings/request" + const val GET_IDE_SERVICE_PORT_REQUEST = "getIDEServicePort" + const val GET_IDE_SERVICE_PORT_RESPONSE = "getIDEServicePort" + const val GET_SETTING_REQUEST = "readConfig" + const val GET_SETTING_RESPONSE = "readConfig" + const val UPDATE_SETTING_REQUEST = "writeConfig" + const val UPDATE_SETTING_RESPONSE = "updateSetting" + const val GET_SERVER_SETTINGS_REQUEST = "readServerConfigBase" + const val GET_SERVER_SETTINGS_RESPONSE = "readServerConfigBase" + const val UPDATE_SERVER_SETTINGS_REQUEST = "writeServerConfigBase" const val UPDATE_SERVER_SETTINGS_RESPONSE = "updateServerSettings/response" - const val INPUT_REQUEST = "input/request" + const val INPUT_REQUEST = "userInput" const val INPUT_RESPONSE = "input/response" - const val STOP_GENERATION_REQUEST = "stopGeneration/request" + const val STOP_GENERATION_REQUEST = "stopDevChat" const val STOP_GENERATION_RESPONSE = "stopGeneration/request" - const val DELETE_LAST_CONVERSATION_REQUEST = "deleteLastConversation/request" - const val DELETE_LAST_CONVERSATION_RESPONSE = "deleteLastConversation/response" - const val DELETE_TOPIC_REQUEST = "deleteTopic/request" + const val DELETE_LAST_CONVERSATION_REQUEST = "deleteChatMessage" + const val DELETE_LAST_CONVERSATION_RESPONSE = "deletedChatMessage" + const val DELETE_TOPIC_REQUEST = "deleteTopic" const val DELETE_TOPIC_RESPONSE = "deleteTopic/response" } From a27fc039a05e3a5942f4e623271838b614bffe24 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:29:19 +0800 Subject: [PATCH 03/13] feat: Add new conversation request handler - Introduced a new conversation request handler to the action factory. - Updated ActionHandlerFactory to manage new conversations effectively. - This change enhances the communication capabilities of the webview. --- src/main/kotlin/ai/devchat/core/ActionHandlerFactory.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/ai/devchat/core/ActionHandlerFactory.kt b/src/main/kotlin/ai/devchat/core/ActionHandlerFactory.kt index 23eaf2f..d664358 100644 --- a/src/main/kotlin/ai/devchat/core/ActionHandlerFactory.kt +++ b/src/main/kotlin/ai/devchat/core/ActionHandlerFactory.kt @@ -12,6 +12,7 @@ class ActionHandlerFactory { DevChatActions.REGENERATION_REQUEST to SendMessageRequestHandler::class, DevChatActions.LIST_COMMANDS_REQUEST to ListCommandsRequestHandler::class, DevChatActions.LOAD_CONVERSATIONS_REQUEST to LoadConversationRequestHandler::class, + DevChatActions.NEW_CONVERSATIONS_REQUEST to LoadConversationRequestHandler::class, DevChatActions.LOAD_HISTORY_MESSAGES_REQUEST to LoadHistoryMessagesRequestHandler::class, DevChatActions.OPEN_LINK_REQUEST to OpenLinkRequestHandler::class, DevChatActions.LIST_TOPICS_REQUEST to ListTopicsRequestHandler::class, From 8c6a877c11d74893f504ff7292491f1820863b1d Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:30:09 +0800 Subject: [PATCH 04/13] feat: Optimize webview communication handling - Remove unnecessary JS callback execution during initialization. - Add error handling when copying Python resources on Windows. - Improve logging for better debugging of the Python copy process. --- .../ai/devchat/plugin/DevChatToolWindowFactory.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/ai/devchat/plugin/DevChatToolWindowFactory.kt b/src/main/kotlin/ai/devchat/plugin/DevChatToolWindowFactory.kt index 3b0b804..7563767 100644 --- a/src/main/kotlin/ai/devchat/plugin/DevChatToolWindowFactory.kt +++ b/src/main/kotlin/ai/devchat/plugin/DevChatToolWindowFactory.kt @@ -136,12 +136,6 @@ class DevChatToolWindowFactory : ToolWindowFactory, DumbAware, Disposable { DevChatState.instance.lastVersion = devChatVersion Log.info("-----------> DevChatState updated with new version") - // Step 9: Execute JS callback - devChatService.browser?.let { - Log.info("-----------> Executing JS callback onInitializationFinish") - it.executeJS("onInitializationFinish") - } - ApplicationManager.getApplication().invokeLater { Notifier.info("$ASSISTANT_NAME_EN initialization has completed successfully.") } @@ -190,7 +184,11 @@ private suspend fun setupPython(envManager: PythonEnvManager, devChatService: De if (OSInfo.isWindows) { val installDir = Paths.get(PathUtils.workPath, "python-win").toString() Log.info("start to copy python-win files") - PathUtils.copyResourceDirToPath("/tools/python-3.11.6-embed-amd64", installDir, overwrite) + try { + PathUtils.copyResourceDirToPath("/tools/python-3.11.6-embed-amd64", installDir, overwrite) + } catch (e: Exception) { + Log.error("Failed to copy python-win files: ${e.message}") + } Log.info("copy python-win files finished") val pthFile = File(Paths.get(installDir, "python311._pth").toString()) val pthContent = pthFile.readText().replace( From 7a748ca44d97fa69e931827c678612cc7acb44ea Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:30:32 +0800 Subject: [PATCH 05/13] fix: Correct pagination logic for message retrieval - Adjusted page index calculation to ensure it starts at 1 or higher. - Prevents potential negative paging when requesting messages. - Enhanced overall message retrieval reliability. --- src/main/kotlin/ai/devchat/storage/ActiveConversation.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/ai/devchat/storage/ActiveConversation.kt b/src/main/kotlin/ai/devchat/storage/ActiveConversation.kt index c5c0b87..fb83018 100644 --- a/src/main/kotlin/ai/devchat/storage/ActiveConversation.kt +++ b/src/main/kotlin/ai/devchat/storage/ActiveConversation.kt @@ -33,7 +33,11 @@ class ActiveConversation { if (this.messages == null) { return null } - val offset = pageSize * (page - 1) + var pageIndex = page + if (page <= 0) { + pageIndex = 1 + } + val offset = pageSize * (pageIndex - 1) if (offset >= this.messages!!.size) { return null } From fe39718614c3d7e0c46534e059253301a5fee401 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:31:35 +0800 Subject: [PATCH 06/13] feat: Optimize webview communication handlers - Refactor handlers to include command in payloads sent. - Update action methods in various request handlers for consistency. - Improve error handling and payload structures across different classes. --- .../core/handlers/AddContextNotifyHandler.kt | 20 ++++-- .../core/handlers/CodeDiffApplyHandler.kt | 2 +- .../DeleteLastConversationRequestHandler.kt | 4 +- .../GetIDEServicePortRequestHandler.kt | 2 +- .../GetServerSettingsRequestHandler.kt | 2 +- .../core/handlers/GetSettingRequestHandler.kt | 2 +- .../core/handlers/InputRequestHandler.kt | 2 +- .../handlers/ListCommandsRequestHandler.kt | 2 +- .../core/handlers/ListTopicsRequestHandler.kt | 2 +- .../handlers/SendMessageRequestHandler.kt | 71 +++++++++---------- .../core/handlers/SendUserMessageHandler.kt | 2 +- .../UpdateServerSettingsRequestHandler.kt | 2 +- .../handlers/UpdateSettingRequestHandler.kt | 6 +- 13 files changed, 61 insertions(+), 58 deletions(-) diff --git a/src/main/kotlin/ai/devchat/core/handlers/AddContextNotifyHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/AddContextNotifyHandler.kt index df1638f..055fc76 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/AddContextNotifyHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/AddContextNotifyHandler.kt @@ -13,12 +13,20 @@ class AddContextNotifyHandler(project: Project, requestAction: String, metadata: payload ) { override val actionName: String = DevChatActions.ADD_CONTEXT_NOTIFY + override fun action() { - send(payload=mapOf( - "path" to payload?.getString("path"), - "content" to payload?.getString("content"), - "languageId" to payload?.getString("languageId"), - "startLine" to payload?.getInteger("startLine") - )) + val contextObj = mapOf( + "path" to (payload?.getString("path") ?: ""), + "content" to (payload?.getString("content") ?: ""), + "command" to "" + ) + + val newPayload = mapOf( + "command" to actionName, + "file" to (payload?.getString("path") ?: ""), + "result" to JSONObject.toJSONString(contextObj) + ) + + send(payload = newPayload) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/CodeDiffApplyHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/CodeDiffApplyHandler.kt index 11a265b..31166f0 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/CodeDiffApplyHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/CodeDiffApplyHandler.kt @@ -13,5 +13,5 @@ class CodeDiffApplyHandler(project: Project, requestAction: String, metadata: JS payload ) { override val actionName: String = DevChatActions.CODE_DIFF_APPLY_RESPONSE - override fun action() { send() } + override fun action() { send(payload = mapOf("command" to actionName)) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/DeleteLastConversationRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/DeleteLastConversationRequestHandler.kt index d4f1e4a..1b0e749 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/DeleteLastConversationRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/DeleteLastConversationRequestHandler.kt @@ -13,8 +13,8 @@ class DeleteLastConversationRequestHandler(project: Project, requestAction: Stri ) { override val actionName: String = DevChatActions.DELETE_LAST_CONVERSATION_RESPONSE override fun action() { - val promptHash = payload!!.getString("promptHash") + val promptHash = payload!!.getString("hash") client!!.deleteLog(promptHash) - send(payload = mapOf("promptHash" to promptHash)) + send(payload = mapOf("command" to actionName, "hash" to promptHash)) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/GetIDEServicePortRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/GetIDEServicePortRequestHandler.kt index 29d6b5f..60e29ca 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/GetIDEServicePortRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/GetIDEServicePortRequestHandler.kt @@ -14,6 +14,6 @@ class GetIDEServicePortRequestHandler(project: Project, requestAction: String, m ) { override val actionName: String = DevChatActions.GET_IDE_SERVICE_PORT_RESPONSE override fun action() { - send(payload= mapOf("result" to devChatService.ideServicePort)) + send(payload= mapOf("command" to actionName, "result" to devChatService.ideServicePort)) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/GetServerSettingsRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/GetServerSettingsRequestHandler.kt index f26a967..d9f607b 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/GetServerSettingsRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/GetServerSettingsRequestHandler.kt @@ -16,6 +16,6 @@ class GetServerSettingsRequestHandler(project: Project, requestAction: String, m override val actionName: String = DevChatActions.GET_SERVER_SETTINGS_RESPONSE @Suppress("UNCHECKED_CAST") override fun action() { - send(payload= SERVER_CONFIG.get() as? Map) + send(payload= mapOf( "command" to actionName, "value" to SERVER_CONFIG.get() as? Map)) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/GetSettingRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/GetSettingRequestHandler.kt index 25897ee..c93ede8 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/GetSettingRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/GetSettingRequestHandler.kt @@ -16,6 +16,6 @@ class GetSettingRequestHandler(project: Project, requestAction: String, metadata override val actionName: String = DevChatActions.GET_SETTING_RESPONSE @Suppress("UNCHECKED_CAST") override fun action() { - send(payload= CONFIG.get() as? Map) + send(payload= mapOf("command" to actionName, "value" to CONFIG.get() as? Map)) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/InputRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/InputRequestHandler.kt index 2361943..efb4095 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/InputRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/InputRequestHandler.kt @@ -16,7 +16,7 @@ class InputRequestHandler(project: Project, requestAction: String, metadata: JSO override fun action() { runBlocking { - wrapper!!.activeChannel?.send(payload!!.getString("data")) + wrapper!!.activeChannel?.send(payload!!.getString("text")) } send() } diff --git a/src/main/kotlin/ai/devchat/core/handlers/ListCommandsRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/ListCommandsRequestHandler.kt index 0073b0a..ba5127c 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/ListCommandsRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/ListCommandsRequestHandler.kt @@ -41,6 +41,6 @@ class ListCommandsRequestHandler(project: Project, requestAction: String, metada "recommend" to recommendedWorkflows.indexOf(commandName) ) }.orEmpty() - send(payload = mapOf("commands" to indexedCommands)) + send(payload = mapOf("command" to actionName, "result" to indexedCommands)) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/ListTopicsRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/ListTopicsRequestHandler.kt index a514de4..699ba3a 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/ListTopicsRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/ListTopicsRequestHandler.kt @@ -30,6 +30,6 @@ class ListTopicsRequestHandler(project: Project, requestAction: String, metadata "hidden" to it.hidden, ) } - send(payload= mapOf("topics" to topics)) + send(payload= mapOf("command" to actionName, "list" to topics)) } } \ No newline at end of file diff --git a/src/main/kotlin/ai/devchat/core/handlers/SendMessageRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/SendMessageRequestHandler.kt index 5475284..387a0d4 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/SendMessageRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/SendMessageRequestHandler.kt @@ -31,17 +31,15 @@ class SendMessageRequestHandler(project: Project, requestAction: String, metadat lastRequestArgs = Pair(metadata, payload) } - val parent = metadata!!.getString("parent")?.takeUnless { it.isEmpty() } - val model = payload!!.getString("model")?.takeIf { it.isNotEmpty() } ?: defaultModel - val message = payload!!.getString("message") + val parent = metadata?.getString("parent_hash")?.takeUnless { it.isEmpty() } + val model = payload?.getString("model")?.takeIf { it.isNotEmpty() } ?: defaultModel + val message = payload?.getString("text") val (contextTempFilePaths, contextContents) = processContexts( - json.decodeFromString( - payload!!.getJSONArray("contexts").toString() - ) + payload?.getJSONArray("contextInfo") ).unzip() val chatRequest = ChatRequest( - content=message, + content=message!!, modelName = model, apiKey = CONFIG["providers.devchat.api_key"] as String, apiBase = CONFIG["providers.devchat.api_base"] as String, @@ -64,11 +62,11 @@ class SendMessageRequestHandler(project: Project, requestAction: String, metadat override fun except(exception: Exception) { send( - metadata=mapOf( - "currentChunkId" to 0, - "isFinalChunk" to true, - "finishReason" to "error", - "error" to "Exception occurred while executing 'devchat' command." + payload = mapOf( + "command" to "receiveMessage", + "text" to "Exception occurred while executing 'devchat' command. ${exception.message}", + "isError" to true, + "hash" to "" ) ) } @@ -129,12 +127,14 @@ class SendMessageRequestHandler(project: Project, requestAction: String, metadat } private fun errorHandler(e: String) { - send(metadata=mapOf( - "currentChunkId" to 0, - "isFinalChunk" to true, - "finishReason" to "error", - "error" to e - )) + send( + payload = mapOf( + "command" to "receiveMessage", + "text" to e, + "isError" to true, + "hash" to "" + ) + ) } private fun promptCallback(response: ChatResponse) { @@ -142,33 +142,28 @@ class SendMessageRequestHandler(project: Project, requestAction: String, metadat currentChunkId += 1 send( payload = mapOf( - "message" to response.content, + "command" to if (response.promptHash != null) "receiveMessage" else "receiveMessagePartial", + "text" to response.content, + "hash" to response.promptHash, "user" to response.user, - "date" to response.date, - "promptHash" to response.promptHash - ), - metadata = mapOf( - "currentChunkId" to currentChunkId, - "isFinalChunk" to (response.promptHash != null), - "finishReason" to if (response.promptHash != null) "success" else "", - "error" to "" - ), + "date" to response.date + ) ) } } - private fun processContexts(contexts: List>?): List> { + private fun processContexts(contexts: List?): List> { val prefix = "devchat-context-" return contexts?.mapNotNull {context -> - when (context["type"] as? String) { - "code", "command" -> { - val data = json.encodeToString(serializer(), context) - val tempFilePath = PathUtils.createTempFile(data, prefix) - Log.info("Context file path: $tempFilePath") - tempFilePath!! to data - } - else -> null + val context = context as? JSONObject ?: return@mapNotNull null + // context has two fields: file and context + val contextMap = context.getJSONObject("context").entries.associate { (key, value) -> + key to value.toString() } + val data = json.encodeToString(serializer(), contextMap) + val tempFilePath = PathUtils.createTempFile(data, prefix) + Log.info("Context file path: $tempFilePath") + tempFilePath!! to data }.orEmpty() } @@ -176,4 +171,4 @@ class SendMessageRequestHandler(project: Project, requestAction: String, metadat var lastRequestArgs: Pair? = null } -} +} \ No newline at end of file diff --git a/src/main/kotlin/ai/devchat/core/handlers/SendUserMessageHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/SendUserMessageHandler.kt index 5a478b7..c2b74eb 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/SendUserMessageHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/SendUserMessageHandler.kt @@ -15,7 +15,7 @@ class SendUserMessageHandler(project: Project, requestAction: String, metadata: override val actionName: String = DevChatActions.SEND_USER_MESSAGE_RESPONSE override fun action() { send(payload=mapOf( - "command" to payload?.getString("command"), + "command" to actionName, "message" to payload?.getString("message"), )) } diff --git a/src/main/kotlin/ai/devchat/core/handlers/UpdateServerSettingsRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/UpdateServerSettingsRequestHandler.kt index ada60bd..421bece 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/UpdateServerSettingsRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/UpdateServerSettingsRequestHandler.kt @@ -15,7 +15,7 @@ class UpdateServerSettingsRequestHandler(project: Project, requestAction: String override val actionName: String = DevChatActions.UPDATE_SERVER_SETTINGS_RESPONSE override fun action() { - SERVER_CONFIG.replaceAll(payload!!) + SERVER_CONFIG.replaceAll(payload!!.getJSONObject("value")) send() } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/UpdateSettingRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/UpdateSettingRequestHandler.kt index b10786a..7f4dcfa 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/UpdateSettingRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/UpdateSettingRequestHandler.kt @@ -15,7 +15,7 @@ class UpdateSettingRequestHandler(project: Project, requestAction: String, metad override val actionName: String = DevChatActions.UPDATE_SETTING_RESPONSE override fun action() { - CONFIG.replaceAll(payload!!) - send() + CONFIG.replaceAll(payload?.getJSONObject("value")!!) + send(payload = mapOf("command" to actionName)) } -} +} \ No newline at end of file From 51ab3e421cde3630b177283bc7f1ffde058f9fa5 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:31:59 +0800 Subject: [PATCH 07/13] feat: Improve webview communication handlers - Refactor LoadConversationRequestHandler to load messages asynchronously. - Update LoadHistoryMessagesRequestHandler to change page index retrieval. - Adjust payload structure for message reload with updated mappings. --- .../LoadConversationRequestHandler.kt | 35 +++++++++++++++++-- .../LoadHistoryMessagesRequestHandler.kt | 27 ++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/ai/devchat/core/handlers/LoadConversationRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/LoadConversationRequestHandler.kt index 0f9fd34..76544df 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/LoadConversationRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/LoadConversationRequestHandler.kt @@ -2,6 +2,7 @@ package ai.devchat.core.handlers import ai.devchat.core.BaseActionHandler import ai.devchat.core.DevChatActions +import ai.devchat.storage.CONFIG import com.alibaba.fastjson.JSONObject import com.intellij.openapi.project.Project @@ -14,7 +15,7 @@ class LoadConversationRequestHandler(project: Project, requestAction: String, me override val actionName: String = DevChatActions.LOAD_CONVERSATIONS_RESPONSE override fun action() { - val topicHash = metadata!!.getString("topicHash") + val topicHash = metadata?.getString("topicHash") val res = mutableMapOf("reset" to true) when { topicHash.isNullOrEmpty() -> activeConversation!!.reset() @@ -24,7 +25,37 @@ class LoadConversationRequestHandler(project: Project, requestAction: String, me activeConversation!!.reset(topicHash, logs) } } - send(payload=res) + + loadConversation() + } + + fun loadConversation() { + val pageSize = CONFIG["max_log_count"] as Int + val pageIndex = 1 + val messages = activeConversation!!.getMessages(pageIndex, pageSize) + + // 创建新的 payload + val newPayload = mapOf( + "command" to "reloadMessage", + "entries" to (messages?.map { shortLog -> + mapOf( + "hash" to shortLog.hash, + "parent" to shortLog.parent, + "user" to shortLog.user, + "date" to shortLog.date, + "request" to shortLog.request, + "responses" to shortLog.responses, + "context" to shortLog.context, + "request_tokens" to shortLog.requestTokens, + "response_tokens" to shortLog.responseTokens, + "response" to (shortLog.responses?.joinToString("\n") ?: "") + ) + }?.reversed() ?: emptyList()), + "pageIndex" to 0, + "reset" to messages.isNullOrEmpty() + ) + + send(payload = newPayload) } } diff --git a/src/main/kotlin/ai/devchat/core/handlers/LoadHistoryMessagesRequestHandler.kt b/src/main/kotlin/ai/devchat/core/handlers/LoadHistoryMessagesRequestHandler.kt index 02fc3ca..92153cb 100644 --- a/src/main/kotlin/ai/devchat/core/handlers/LoadHistoryMessagesRequestHandler.kt +++ b/src/main/kotlin/ai/devchat/core/handlers/LoadHistoryMessagesRequestHandler.kt @@ -2,6 +2,7 @@ package ai.devchat.core.handlers import ai.devchat.core.BaseActionHandler import ai.devchat.core.DevChatActions +import ai.devchat.core.ShortLog import ai.devchat.storage.CONFIG import com.alibaba.fastjson.JSONObject import com.intellij.openapi.project.Project @@ -16,8 +17,30 @@ class LoadHistoryMessagesRequestHandler(project: Project, requestAction: String, override fun action() { val pageSize = CONFIG["max_log_count"] as Int - val pageIndex = metadata!!.getInteger("pageIndex") ?: 1 + val pageIndex = metadata!!.getInteger("page") ?: 1 val messages = activeConversation!!.getMessages(pageIndex, pageSize) - send(payload = mapOf("messages" to messages)) + + // 创建新的 payload + val newPayload = mapOf( + "command" to "reloadMessage", + "entries" to (messages?.map { shortLog -> + mapOf( + "hash" to shortLog.hash, + "parent" to shortLog.parent, + "user" to shortLog.user, + "date" to shortLog.date, + "request" to shortLog.request, + "responses" to shortLog.responses, + "context" to shortLog.context, + "request_tokens" to shortLog.requestTokens, + "response_tokens" to shortLog.responseTokens, + "response" to (shortLog.responses?.joinToString("\n") ?: "") + ) + }?.reversed() ?: emptyList()), + "pageIndex" to 0, + "reset" to messages.isNullOrEmpty() + ) + + send(payload = newPayload) } } From a3640226d2d880e02d1b0f9ad981e206e0fea5c8 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:32:28 +0800 Subject: [PATCH 08/13] feat: Improve context handling in ContextBuilder - Refactored functions to return lists instead of strings for context. - Updated MAX_CONTEXT_TOKENS to be configurable based on limits. - Enhanced token counting logic for improved performance. --- .../plugin/completion/agent/ContextBuilder.kt | 70 +++++++++++-------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/ai/devchat/plugin/completion/agent/ContextBuilder.kt b/src/main/kotlin/ai/devchat/plugin/completion/agent/ContextBuilder.kt index 29bdeec..c8e249a 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/agent/ContextBuilder.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/agent/ContextBuilder.kt @@ -19,8 +19,7 @@ import com.intellij.psi.PsiRecursiveElementVisitor import java.util.concurrent.ConcurrentHashMap -val MAX_CONTEXT_TOKENS: Int - get() = (CONFIG["complete_context_limit"] as? Int) ?: 6000 +var MAX_CONTEXT_TOKENS: Int = 0 const val LINE_SEPARATOR = '\n' @@ -231,25 +230,28 @@ class ContextBuilder(val file: PsiFile, val offset: Int) { return (newCount <= MAX_CONTEXT_TOKENS).also { if (it) tokenCount = newCount } } - private fun buildCalleeDefinitionsContext(): String { + private fun buildCalleeDefinitionsContext(): List { return runInEdtAndGet { file.findElementAt(offset) ?.findCalleeInParent() + ?.asSequence() ?.flatMap { elements -> elements.filter { it.containingFile.virtualFile.path != filepath } } ?.map { CodeSnippet(it.containingFile.virtualFile.path, it.foldTextOfLevel(1)) } ?.takeWhile(::checkAndUpdateTokenCount) - ?.joinToString(separator = "") {snippet -> + ?.map { snippet -> val commentedContent = snippet.content.lines() .joinToString(LINE_SEPARATOR.toString()) "$commentPrefix Function call definition:\n\n" + - "$commentPrefix ${snippet.filepath}\n\n" + - "$commentPrefix \n$commentedContent\n\n\n\n" - } ?: "" + "$commentPrefix ${snippet.filepath}\n\n" + + "$commentPrefix \n$commentedContent\n\n\n\n" + } + ?.toList() + ?: emptyList() } } - private fun buildSymbolsContext(): String { - return ApplicationManager.getApplication().runReadAction { + private fun buildSymbolsContext(): List { + return ApplicationManager.getApplication().runReadAction> { Log.info("Starting buildSymbolsContext") val element = file.findElementAt(offset) Log.info("Found element at offset: ${element?.text}") @@ -262,7 +264,7 @@ class ContextBuilder(val file: PsiFile, val offset: Int) { Log.info("Found $variablesCount accessible variables") val processedTypes = mutableSetOf() - val result = StringBuilder() + val result = mutableListOf() variablesList.forEach { variable -> val typeElement = variable.typeDeclaration.element @@ -284,7 +286,7 @@ class ContextBuilder(val file: PsiFile, val offset: Int) { val snippet = CodeSnippet( typeFilePath, if (variable.typeDeclaration.isProjectContent) { - typeElement.foldTextOfLevel(2) + typeElement.foldTextOfLevel(1) } else { typeElement.text.lines().first() + "..." } @@ -294,10 +296,10 @@ class ContextBuilder(val file: PsiFile, val offset: Int) { Log.info("Adding context for type: ${typeText}") val commentedContent = snippet.content.lines() .joinToString(LINE_SEPARATOR.toString()) - result.append("$commentPrefix Symbol type definition:\n\n") - .append("$commentPrefix ${variable.symbol.name}\n\n") - .append("$commentPrefix ${snippet.filepath}\n\n") - .append("$commentPrefix \n$commentedContent\n\n\n\n") + result.add("$commentPrefix Symbol type definition:\n\n" + + "$commentPrefix ${variable.symbol.name}\n\n" + + "$commentPrefix ${snippet.filepath}\n\n" + + "$commentPrefix \n$commentedContent\n\n\n\n") } else { Log.info("Skipping type ${variable.symbol.name} due to token limit") return@forEach @@ -313,8 +315,8 @@ class ContextBuilder(val file: PsiFile, val offset: Int) { } } - Log.info("buildSymbolsContext result length: ${result.length}") - result.toString() + Log.info("buildSymbolsContext result size: ${result.size}") + result } } @@ -340,25 +342,32 @@ class ContextBuilder(val file: PsiFile, val offset: Int) { } } - private fun buildRecentFilesContext(): String { + private fun buildRecentFilesContext(): List { val project = file.project return runInEdtAndGet { project.getService(RecentFilesTracker::class.java).getRecentFiles().asSequence() .filter { it.isValid && !it.isDirectory && it.path != filepath } - .map { CodeSnippet(it.path, getPsiFile(project, it).foldTextOfLevel(2)) } + .map { CodeSnippet(it.path, getPsiFile(project, it).foldTextOfLevel(1)) } .filter { it.content.lines().count(String::isBlank) <= 50 } .takeWhile(::checkAndUpdateTokenCount) - .joinToString(separator = "") {snippet -> + .map { snippet -> val commentedContent = snippet.content.lines() .joinToString(LINE_SEPARATOR.toString()) "$commentPrefix Recently open file:\n\n" + - "$commentPrefix ${snippet.filepath}\n\n" + - "$commentedContent\n\n\n\n" + "$commentPrefix ${snippet.filepath}\n\n" + + "$commentedContent\n\n\n\n" } + .toList() } } fun createPrompt(model: String?): String { + MAX_CONTEXT_TOKENS = when (val limit = CONFIG["complete_context_limit"]) { + is Int -> limit + is String -> limit.toIntOrNull() ?: 6000 + else -> 6000 + } + val (prefix, suffix) = buildFileContext() var currentTokenCount = prefix.tokenCount() + suffix.tokenCount() val maxAllowedTokens = (MAX_CONTEXT_TOKENS * 0.9).toInt() @@ -375,12 +384,17 @@ class ContextBuilder(val file: PsiFile, val offset: Int) { ) for (builder in contextBuilders) { - val context = builder() - val contextTokens = context.tokenCount() - if (currentTokenCount + contextTokens <= maxAllowedTokens) { - extraContexts.add(context) - currentTokenCount += contextTokens - } else { + val contextList = builder() + for (context in contextList) { + val contextTokens = context.tokenCount() + if (currentTokenCount + contextTokens <= MAX_CONTEXT_TOKENS) { + extraContexts.add(context) + currentTokenCount += contextTokens + } else { + break + } + } + if (currentTokenCount >= MAX_CONTEXT_TOKENS) { break } } From 3727a274c00638127294308a73b813defa3468aa Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 13 Nov 2024 20:32:53 +0800 Subject: [PATCH 09/13] update submodules --- gui | 2 +- tools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gui b/gui index c011224..dc4ac3b 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit c011224587af65658d5637d5016f6b60024054a4 +Subproject commit dc4ac3bfa6134c02cb604f370f063c560edf361a diff --git a/tools b/tools index b6bccf6..8dd91e1 160000 --- a/tools +++ b/tools @@ -1 +1 @@ -Subproject commit b6bccf6a463c4437398499aa7ff17552e49ecf56 +Subproject commit 8dd91e1c57752be181e6e4c814089850f3f0ebd8 From 83e197137305eeaf6b9ab8efcae1482c49433577 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Thu, 14 Nov 2024 12:44:42 +0800 Subject: [PATCH 10/13] refactor: Optimize WebView communication and completion handling - Added CompletableFuture for handling asynchronous tasks in Browser class - Enhanced CompletionProvider to log completion messages and handle exceptions - Updated InlineCompletionService to include manual trigger flag and logging improvements --- src/main/kotlin/ai/devchat/plugin/Browser.kt | 4 + .../completion/editor/CompletionProvider.kt | 102 ++++++++++++------ .../editor/InlineCompletionService.kt | 16 ++- 3 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/ai/devchat/plugin/Browser.kt b/src/main/kotlin/ai/devchat/plugin/Browser.kt index 1d3993b..baf48a5 100644 --- a/src/main/kotlin/ai/devchat/plugin/Browser.kt +++ b/src/main/kotlin/ai/devchat/plugin/Browser.kt @@ -21,9 +21,12 @@ import org.cef.handler.CefLoadHandlerAdapter import org.cef.network.CefRequest import java.awt.Color import java.nio.charset.StandardCharsets +import java.util.concurrent.CompletableFuture + class Browser(val project: Project): Disposable { val jbCefBrowser = JBCefBrowserBuilder().setOffScreenRendering(false).setEnableOpenDevToolsMenuItem(true).build() + private var lastFuture: CompletableFuture = CompletableFuture.completedFuture(null) init { registerLoadHandler() @@ -41,6 +44,7 @@ class Browser(val project: Project): Disposable { jbCefBrowser.cefBrowser.url, 0 ) + jbCefBrowser.cefBrowser.executeJavaScript("setTimeout(function(){}, 0);", jbCefBrowser.cefBrowser.url, 0) } private fun callJava(arg: String): JBCefJSQuery.Response { diff --git a/src/main/kotlin/ai/devchat/plugin/completion/editor/CompletionProvider.kt b/src/main/kotlin/ai/devchat/plugin/completion/editor/CompletionProvider.kt index 5462285..11cad0e 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/editor/CompletionProvider.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/editor/CompletionProvider.kt @@ -1,16 +1,21 @@ package ai.devchat.plugin.completion.editor +import ai.devchat.plugin.Browser +import ai.devchat.plugin.DevChatService +import ai.devchat.plugin.completion.agent.Agent.LogEventRequest import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor import ai.devchat.plugin.completion.agent.AgentService +import ai.devchat.plugin.completion.agent.Agent import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileDocumentManager @Service @@ -25,45 +30,46 @@ class CompletionProvider { private val currentContext = AtomicReference(null) -fun provideCompletion(editor: Editor, offset: Int, manually: Boolean = false) { - logger.info("start provideCompletion") - val currentSequence = completionSequence.incrementAndGet() - val agentService = service() - val inlineCompletionService = service() - - val oldContext = currentContext.getAndSet(null) - oldContext?.job?.cancel() - inlineCompletionService.dismiss() - - val job = agentService.scope.launch { - try { - logger.info("Trigger completion at $offset") - val result = agentService.provideCompletion(editor, offset, manually) - if (isActive && result != null && completionSequence.get() == currentSequence) { - logger.info("Show completion at $offset: $result") - ApplicationManager.getApplication().invokeLater { - inlineCompletionService.show(editor, offset, result) + fun provideCompletion(editor: Editor, offset: Int, manually: Boolean = false) { + logger.info("start provideCompletion") + val currentSequence = completionSequence.incrementAndGet() + val agentService = service() + val inlineCompletionService = service() + + val oldContext = currentContext.getAndSet(null) + oldContext?.job?.cancel() + inlineCompletionService.dismiss() + + val job = agentService.scope.launch { + try { + logger.info("Trigger completion at $offset") + val result = agentService.provideCompletion(editor, offset, manually) + if (isActive && result != null && completionSequence.get() == currentSequence) { + logger.info("Show completion at $offset: $result") + logCompletionMessage(editor, result) + ApplicationManager.getApplication().invokeLater { + inlineCompletionService.show(editor, offset, result, manually) + } } + } catch (e: CancellationException) { + // 方案1:以较低的日志级别记录 + logger.info("Completion was cancelled: ${e.message}") + // 或者方案2:完全忽略 + // // 不做任何处理 + + null + } catch (e: Exception) { + logger.error("Error in completion job", e) + } finally { + currentContext.set(null) + ongoingCompletionFlow.value = null } - } catch (e: CancellationException) { - // 方案1:以较低的日志级别记录 - logger.info("Completion was cancelled: ${e.message}") - // 或者方案2:完全忽略 - // // 不做任何处理 - - null - } catch (e: Exception) { - logger.error("Error in completion job", e) - } finally { - currentContext.set(null) - ongoingCompletionFlow.value = null } - } - val newContext = CompletionContext(editor, offset, job) - currentContext.set(newContext) - ongoingCompletionFlow.value = newContext -} + val newContext = CompletionContext(editor, offset, job) + currentContext.set(newContext) + ongoingCompletionFlow.value = newContext + } fun clear() { val inlineCompletionService = service() @@ -72,4 +78,30 @@ fun provideCompletion(editor: Editor, offset: Int, manually: Boolean = false) { context?.job?.cancel() ongoingCompletionFlow.value = null } + + fun logCompletionMessage(editor: Editor, completion: Agent.CompletionResponse,) { + var browser = editor.project?.getService(DevChatService::class.java)?.browser + val virtualFile = FileDocumentManager.getInstance().getFile(editor.document) + val choice = completion.choices.first() + val text = choice.text + + val message = mapOf( + "command" to "logMessage", + "id" to completion.id, + "language" to (virtualFile?.extension ?: ""), + "commandName" to "code_completion", + "content" to text, + "model" to completion.model + ) + + // 使用 Browser 类的 sendToWebView 方法发送消息 + if (browser == null) { + logger.warn("Browser instance is null, cannot send log event to webview.") + } else { + browser.sendToWebView(message) + } + + // 记录日志 + logger.info("Code completion log message: $message") + } } \ No newline at end of file diff --git a/src/main/kotlin/ai/devchat/plugin/completion/editor/InlineCompletionService.kt b/src/main/kotlin/ai/devchat/plugin/completion/editor/InlineCompletionService.kt index 1862ff5..865e2fe 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/editor/InlineCompletionService.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/editor/InlineCompletionService.kt @@ -40,13 +40,14 @@ class InlineCompletionService { val markups: List, val id: String, val displayAt: Long, - var ongoing: Boolean = false + var ongoing: Boolean = false, + var manual: Boolean = false ) var shownInlineCompletion: InlineCompletion? = null private set - fun show(editor: Editor, offset: Int, completion: Agent.CompletionResponse) { + fun show(editor: Editor, offset: Int, completion: Agent.CompletionResponse, manual: Boolean) { dismiss() if (completion.choices.isEmpty()) { return @@ -152,12 +153,13 @@ class InlineCompletionService { val cmplId = completion.id.replace("cmpl-", "") val displayAt = System.currentTimeMillis() val id = "view-${cmplId}-at-${displayAt}" - shownInlineCompletion = InlineCompletion(editor, offset, completion, inlays, markups, id, displayAt) + shownInlineCompletion = InlineCompletion(editor, offset, completion, inlays, markups, id, displayAt, manual = manual) val agentService = service() val virtualFile = FileDocumentManager.getInstance().getFile(editor.document) agentService.scope.launch { agentService.postEvent( + project = editor.project!!, Agent.LogEventRequest( type = Agent.LogEventRequest.EventType.VIEW, completionId = completion.id, @@ -168,6 +170,7 @@ class InlineCompletionService { promptBuildingElapse = completion.promptBuildingElapse, llmRequestElapse = completion.llmRequestElapse, model = completion.model, + isManualTrigger = manual, ) ) } @@ -180,7 +183,7 @@ class InlineCompletionService { NEXT_LINE, } - fun accept(type: AcceptType) { + fun accept(editor: Editor, type: AcceptType) { val currentCompletion = shownInlineCompletion ?: return val choice = currentCompletion.completion.choices.first() logger.info("Accept inline completion at ${currentCompletion.offset}: $type: $choice") @@ -227,6 +230,7 @@ class InlineCompletionService { val virtualFile = FileDocumentManager.getInstance().getFile(currentCompletion.editor.document) agentService.scope.launch { agentService.postEvent( + project = editor.project!!, Agent.LogEventRequest( type = Agent.LogEventRequest.EventType.SELECT, completionId = currentCompletion.completion.id, @@ -237,6 +241,7 @@ class InlineCompletionService { promptBuildingElapse = currentCompletion.completion.promptBuildingElapse, llmRequestElapse = currentCompletion.completion.llmRequestElapse, model = currentCompletion.completion.model, + isManualTrigger = currentCompletion.manual, ) ) } @@ -256,7 +261,8 @@ class InlineCompletionService { promptBuildingElapse = 0, llmRequestElapse = 0, model = currentCompletion.completion.model, - ) + ), + manual = currentCompletion.manual ) } } From f46db0ff9e11ceda51fd71117294e912c12f4b97 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Thu, 14 Nov 2024 12:45:05 +0800 Subject: [PATCH 11/13] refactor: Update inline completion service calls - Modified accept method calls to include editor parameter - Ensured consistency in service usage across actions - Improved clarity and maintainability of code --- .../ai/devchat/plugin/completion/actions/AcceptCompletion.kt | 2 +- .../plugin/completion/actions/AcceptCompletionNextLine.kt | 2 +- .../plugin/completion/actions/AcceptCompletionNextWord.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletion.kt b/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletion.kt index d5ebc7e..580d959 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletion.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletion.kt @@ -12,7 +12,7 @@ class AcceptCompletion : EditorAction(object : EditorActionHandler() { val inlineCompletionService = service() override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - inlineCompletionService.accept(InlineCompletionService.AcceptType.FULL_COMPLETION) + inlineCompletionService.accept(editor, InlineCompletionService.AcceptType.FULL_COMPLETION) } override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean { diff --git a/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextLine.kt b/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextLine.kt index fee00b4..348d2c9 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextLine.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextLine.kt @@ -12,7 +12,7 @@ class AcceptCompletionNextLine : EditorAction(object : EditorActionHandler() { val inlineCompletionService = service() override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - inlineCompletionService.accept(InlineCompletionService.AcceptType.NEXT_LINE) + inlineCompletionService.accept(editor, InlineCompletionService.AcceptType.NEXT_LINE) } override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean { diff --git a/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextWord.kt b/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextWord.kt index d29746a..8226bcc 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextWord.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/actions/AcceptCompletionNextWord.kt @@ -12,7 +12,7 @@ class AcceptCompletionNextWord : EditorAction(object : EditorActionHandler() { val inlineCompletionService = service() override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - inlineCompletionService.accept(InlineCompletionService.AcceptType.NEXT_WORD) + inlineCompletionService.accept(editor, InlineCompletionService.AcceptType.NEXT_WORD) } override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean { From 7941c38d77f37d7906cf3186dd6935f5d3b926b0 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Thu, 14 Nov 2024 12:45:30 +0800 Subject: [PATCH 12/13] refactor: Optimize webview communication in AgentService - Refactored `postEvent` method to include `Browser` instance for webview communication - Added `isManualTrigger` field to `Agent` class for manual trigger tracking - Imported additional classes for project and browser integration in `Agent` class --- .../devchat/plugin/completion/agent/Agent.kt | 120 +++++++++--------- .../plugin/completion/agent/AgentService.kt | 7 +- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/main/kotlin/ai/devchat/plugin/completion/agent/Agent.kt b/src/main/kotlin/ai/devchat/plugin/completion/agent/Agent.kt index b098ab7..49454ca 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/agent/Agent.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/agent/Agent.kt @@ -14,7 +14,9 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody - +import com.intellij.openapi.project.Project +import ai.devchat.plugin.DevChatService +import ai.devchat.plugin.Browser val CLOSING_BRACES = setOf("}", "]", ")") const val MAX_CONTINUOUS_INDENT_COUNT = 4 @@ -77,7 +79,8 @@ class Agent(val scope: CoroutineScope) { @SerializedName("prompt_time") val promptBuildingElapse: Long, @SerializedName("llm_time") val llmRequestElapse: Long, @SerializedName("model") val model: String? = null, - @SerializedName("cache_hit") val cacheHit: Boolean = false + @SerializedName("cache_hit") val cacheHit: Boolean = false, + @SerializedName("is_manual_trigger") var isManualTrigger: Boolean = false ) { enum class EventType { @SerializedName("view") VIEW, @@ -287,69 +290,68 @@ private fun requestDevChatAPI(prompt: String): Flow = flow return completion } -suspend fun provideCompletions( - completionRequest: CompletionRequest -): CompletionResponse? = suspendCancellableCoroutine { continuation -> - currentRequest = RequestInfo.fromCompletionRequest(completionRequest) - val model = CONFIG["complete_model"] as? String - var startTime = System.currentTimeMillis() - logger.info("offset: ${completionRequest.position}") - val prompt = ContextBuilder( - completionRequest.file, - completionRequest.position - ).createPrompt(model) - logger.info("Prompt: $prompt") - // output prompt length - logger.info("Prompt length: ${prompt.length}") - val promptBuildingElapse = System.currentTimeMillis() - startTime + suspend fun provideCompletions( + completionRequest: CompletionRequest + ): CompletionResponse? = suspendCancellableCoroutine { continuation -> + currentRequest = RequestInfo.fromCompletionRequest(completionRequest) + val model = CONFIG["complete_model"] as? String + var startTime = System.currentTimeMillis() + logger.info("offset: ${completionRequest.position}") + val prompt = ContextBuilder( + completionRequest.file, + completionRequest.position + ).createPrompt(model) + logger.info("Prompt: $prompt") + // output prompt length + logger.info("Prompt length: ${prompt.length}") + val promptBuildingElapse = System.currentTimeMillis() - startTime - scope.launch { - startTime = System.currentTimeMillis() - val chunks = request(prompt) - .let(::toLines) - .let(::stopAtFirstBrace) - .let(::stopAtDuplicateLine) - .let(::stopAtBlockEnds) - val completion = aggregate(chunks) - val llmRequestElapse = System.currentTimeMillis() - startTime - val offset = completionRequest.position - val replaceRange = CompletionResponse.Choice.Range(start = offset, end = offset) - val text = completion.text - val choice = CompletionResponse.Choice(index = 0, text = text, replaceRange = replaceRange) - val response = CompletionResponse(completion.id, model, listOf(choice), promptBuildingElapse, llmRequestElapse) + scope.launch { + startTime = System.currentTimeMillis() + val chunks = request(prompt) + .let(::toLines) + .let(::stopAtFirstBrace) + .let(::stopAtDuplicateLine) + .let(::stopAtBlockEnds) + val completion = aggregate(chunks) + val llmRequestElapse = System.currentTimeMillis() - startTime + val offset = completionRequest.position + val replaceRange = CompletionResponse.Choice.Range(start = offset, end = offset) + val text = completion.text + val choice = CompletionResponse.Choice(index = 0, text = text, replaceRange = replaceRange) + val response = CompletionResponse(completion.id, model, listOf(choice), promptBuildingElapse, llmRequestElapse) - // 添加日志输出 - logger.info("Code completion response: $response") - logger.info("Final completion text: ${completion.text}") + // 添加日志输出 + logger.info("Code completion response: $response") + logger.info("Final completion text: ${completion.text}") - continuation.resumeWith(Result.success(response)) - prevCompletion = completion.text - } + continuation.resumeWith(Result.success(response)) + prevCompletion = completion.text + } - continuation.invokeOnCancellation { - logger.warn("Agent request cancelled") + continuation.invokeOnCancellation { + logger.warn("Agent request cancelled") + } } -} - suspend fun postEvent(logEventRequest: LogEventRequest): Unit = suspendCancellableCoroutine { - val devChatEndpoint = CONFIG["providers.devchat.api_base"] as? String - val devChatAPIKey = CONFIG["providers.devchat.api_key"] as? String - val requestBuilder = Request.Builder() - .url("$devChatEndpoint/complete_events") - .post( - gson.toJson(logEventRequest).toRequestBody( - "application/json; charset=utf-8".toMediaType() - ) - ) - requestBuilder.addHeader("Authorization", "Bearer $devChatAPIKey") - requestBuilder.addHeader("Content-Type", "application/json") - try { - httpClient.newCall(requestBuilder.build()).execute().use { response -> - logger.info("Log event response: $response") - } - logger.info("Code completion log event: $logEventRequest") - } catch (e: Exception) { - logger.warn(e) + suspend fun postEvent(browser: Browser? = null, logEventRequest: LogEventRequest): Unit = suspendCancellableCoroutine { + // 创建一个包含命令和事件数据的消息 + val message = mapOf( + "command" to "logEvent", + "id" to logEventRequest.completionId, + "language" to logEventRequest.language, + "name" to logEventRequest.type, + "value" to logEventRequest + ) + + // 使用 Browser 类的 sendToWebView 方法发送消息 + if (browser == null) { + logger.warn("Browser instance is null, cannot send log event to webview.") + } else { + browser.sendToWebView(message) } + + // 记录日志 + logger.info("Code completion log event: $logEventRequest") } } \ No newline at end of file diff --git a/src/main/kotlin/ai/devchat/plugin/completion/agent/AgentService.kt b/src/main/kotlin/ai/devchat/plugin/completion/agent/AgentService.kt index 81bfddf..def44fc 100644 --- a/src/main/kotlin/ai/devchat/plugin/completion/agent/AgentService.kt +++ b/src/main/kotlin/ai/devchat/plugin/completion/agent/AgentService.kt @@ -1,5 +1,6 @@ package ai.devchat.plugin.completion.agent +import ai.devchat.plugin.DevChatService import com.intellij.lang.Language import com.intellij.openapi.Disposable import com.intellij.openapi.application.ReadAction @@ -16,6 +17,7 @@ import com.intellij.openapi.application.ModalityState import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume import kotlinx.coroutines.CancellationException +import com.intellij.openapi.project.Project @Service class AgentService : Disposable { @@ -68,8 +70,9 @@ suspend fun provideCompletion(editor: Editor, offset: Int, manually: Boolean = f } } - suspend fun postEvent(event: Agent.LogEventRequest) { - agent.postEvent(event) + suspend fun postEvent(project: Project, event: Agent.LogEventRequest) { + var browser = project.getService(DevChatService::class.java).browser + agent.postEvent(browser, event) } override fun dispose() { From dbf1f07b9468a53316715288221c6f9277210a04 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Thu, 14 Nov 2024 12:47:03 +0800 Subject: [PATCH 13/13] update gui submodule --- gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui b/gui index dc4ac3b..7152d8d 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit dc4ac3bfa6134c02cb604f370f063c560edf361a +Subproject commit 7152d8d28ab9c768a9d25976055e4adca37c0735