From 0d11a9a13a140a252bc1b9abe49c85a0a4bdb2ef Mon Sep 17 00:00:00 2001 From: Kirill Starkov Date: Fri, 16 Aug 2024 15:44:07 +0300 Subject: [PATCH] add events with keymap, open file, etc --- .../refactai/lsp/LSPProcessHolder.kt | 145 ------------------ .../refactai/panes/sharedchat/Events.kt | 11 +- .../panes/sharedchat/SharedChatPane.kt | 47 +++--- 3 files changed, 35 insertions(+), 168 deletions(-) diff --git a/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt b/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt index 7ae0c0e1..4b51b192 100644 --- a/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt +++ b/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt @@ -1,7 +1,6 @@ package com.smallcloud.refactai.lsp import com.google.gson.Gson -import com.google.gson.JsonObject import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationInfo @@ -20,13 +19,11 @@ import com.smallcloud.refactai.Resources.binPrefix import com.smallcloud.refactai.account.AccountManagerChangedNotifier import com.smallcloud.refactai.io.InferenceGlobalContextChangedNotifier import com.smallcloud.refactai.notifications.emitError -import com.smallcloud.refactai.panes.sharedchat.* import org.apache.hc.core5.concurrent.ComplexFuture import java.net.URI import java.nio.file.Files import java.nio.file.Paths import java.nio.file.StandardCopyOption -import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import java.util.concurrent.TimeUnit import kotlin.io.path.Path @@ -348,146 +345,4 @@ class LSPProcessHolder(val project: Project) : Disposable { return res } } - -// fun fetchCaps(): Future { -//// // This causes the ide to crash :/ -//// if(this.capabilities.codeChatModels.isNotEmpty()) { -//// println("caps_cached") -//// return FutureTask { this.capabilities } -//// } -// -// val res = InferenceGlobalContext.connection.get( -// url.resolve("/v1/caps"), -// dataReceiveEnded = {}, -// errorDataReceived = {} -// ) -// -// return res.thenApply { -// val body = it.get() as String -// val caps = Gson().fromJson(body, LSPCapabilities::class.java) -// caps -// } -// } - -// fun fetchSystemPrompts(): Future { -// val res = InferenceGlobalContext.connection.get( -// url.resolve("/v1/customization"), -// dataReceiveEnded = {}, -// errorDataReceived = {} -// ) -// val json = res.thenApply { -// val body = it.get() as String -// val prompts = Gson().fromJson(body, CustomPromptsResponse::class.java) -// prompts.systemPrompts -// } -// -// return json -// } - -// fun fetchCommandCompletion(query: String, cursor: Int, count: Int = 5): Future { -// -// val requestBody = Gson().toJson(mapOf("query" to query, "cursor" to cursor, "top_n" to count)) -// -// val res = InferenceGlobalContext.connection.post( -// url.resolve("/v1/at-command-completion"), -// requestBody, -// ) -// // TODO: could this have a detail message? -// val json = res.thenApply { -// val body = it.get() as String -// // handle error -// // if(body.startsWith("detail")) -// Gson().fromJson(body, CommandCompletionResponse::class.java) -// } -// -// return json -// } - -// fun fetchCommandPreview(query: String): Future { -// val requestBody = Gson().toJson(mapOf("query" to query)) -// -// val response = InferenceGlobalContext.connection.post( -// url.resolve("/v1/at-command-preview"), -// requestBody -// ) -// -// val json: Future = response.thenApply { -// val responseBody = it.get() as String -// if (responseBody.startsWith("detail")) { -// Events.AtCommands.Preview.Response(emptyArray()) -// } else { -// Events.gson.fromJson(responseBody, Events.AtCommands.Preview.Response::class.java) -// } -// } -// -// return json -// } - -// fun sendChat( -// id: String, -// messages: ChatMessages, -// model: String, -// onlyDeterministicMessages: Boolean = false, -// takeNote: Boolean = false, -// tools: Array? = emptyArray(), -// dataReceived: (String, String) -> Unit, -// dataReceiveEnded: (String) -> Unit, -// errorDataReceived: (JsonObject) -> Unit, -// failedDataReceiveEnded: (Throwable?) -> Unit, -// ): CompletableFuture> { -// -// val parameters = mapOf("max_new_tokens" to 2048) -// -// val maybeTools = if (!tools.isNullOrEmpty()) tools else { null } -// -// val requestBody = Gson().toJson(mapOf( -// "messages" to messages.map { -// val content = if(it.content is String) { it.content } else { Gson().toJson(it.content) } -// mapOf("role" to it.role, "content" to content, "tool_calls" to it.toolCalls, "tool_call_id" to it.toolCallId) -// }, -// "model" to model, -// "parameters" to parameters, -// "stream" to true, -// "tools" to maybeTools, -// "only_deterministic_messages" to onlyDeterministicMessages, -// "chat_id" to id, -// "max_tokens" to 2048 -// )) -// -// val headers = mapOf("Authorization" to "Bearer ${AccountManager.apiKey}") -// val request = InferenceGlobalContext.connection.post( -// url.resolve("/v1/chat"), -// requestBody, -// headers = headers, -// dataReceived = dataReceived, -// dataReceiveEnded = dataReceiveEnded, -// errorDataReceived = errorDataReceived, -// failedDataReceiveEnded = failedDataReceiveEnded, -// requestId = id, -// ) -// -// return request -// -// } - - - -// fun getAvailableTools(): Future> { -// val headers = mapOf("Authorization" to "Bearer ${AccountManager.apiKey}") -// -// val request = InferenceGlobalContext.connection.get( -// url.resolve("/v1/tools"), -// headers=headers, -// dataReceiveEnded = {}, -// errorDataReceived = {} -// ) -// -// val json = request.thenApply { -// val res = it.get() as String -// Gson().fromJson(res, Array::class.java) -// } -// -// return json -// -// } } \ No newline at end of file diff --git a/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/Events.kt b/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/Events.kt index 2bfc6d99..bf554bc3 100644 --- a/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/Events.kt +++ b/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/Events.kt @@ -1,9 +1,10 @@ package com.smallcloud.refactai.panes.sharedchat +import com.google.gson.GsonBuilder +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement import com.google.gson.annotations.SerializedName -import com.google.gson.* -import com.smallcloud.refactai.panes.sharedchat.browser.getActionKeybinding -import com.smallcloud.refactai.settings.AppSettingsState import com.smallcloud.refactai.settings.Host import com.smallcloud.refactai.settings.HostDeserializer import java.io.Serializable @@ -73,9 +74,9 @@ class Events { // EventNames.FromChat.FIM_READY.value -> p2?.deserialize(payload, Fim.Ready::class.java) EventNames.FromChat.FIM_REQUEST.value -> Fim.Request() - EventNames.FromChat.OPEN_EXTERNAL_URL.value -> OpenHotKeys() + EventNames.FromChat.OPEN_HOTKEYS.value -> OpenHotKeys() EventNames.FromChat.OPEN_FILE.value -> p2?.deserialize(payload, OpenFile::class.java) - else -> null + else -> null } } diff --git a/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/SharedChatPane.kt b/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/SharedChatPane.kt index 3d420a2a..861c7e7e 100644 --- a/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/SharedChatPane.kt +++ b/src/main/kotlin/com/smallcloud/refactai/panes/sharedchat/SharedChatPane.kt @@ -1,44 +1,43 @@ package com.smallcloud.refactai.panes.sharedchat import com.intellij.ide.BrowserUtil -import com.intellij.lang.Language import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.event.SelectionEvent import com.intellij.openapi.editor.event.SelectionListener import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManagerEvent import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.keymap.impl.ui.KeymapPanel import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.TextRange +import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiDocumentManager import com.intellij.testFramework.LightVirtualFile -import com.intellij.util.ui.UIUtil import com.smallcloud.refactai.account.AccountManager -import com.smallcloud.refactai.account.AccountManager.Companion.instance import com.smallcloud.refactai.account.LoginStateService import com.smallcloud.refactai.io.InferenceGlobalContextChangedNotifier -import com.smallcloud.refactai.lsp.LSPProcessHolder import com.smallcloud.refactai.panes.sharedchat.Events.ActiveFile.ActiveFileToChat import com.smallcloud.refactai.panes.sharedchat.Events.Editor import com.smallcloud.refactai.panes.sharedchat.browser.ChatWebView -import com.smallcloud.refactai.settings.AppSettingsState import com.smallcloud.refactai.settings.AppSettingsConfigurable +import com.smallcloud.refactai.settings.AppSettingsState import com.smallcloud.refactai.settings.Host import org.jetbrains.annotations.NotNull import java.beans.PropertyChangeListener +import java.io.File import javax.swing.JPanel import javax.swing.UIManager class SharedChatPane(val project: Project) : JPanel(), Disposable { - + private val logger = Logger.getInstance(SharedChatPane::class.java) private val editor = Editor(project) var id: String? = null; @@ -75,6 +74,7 @@ class SharedChatPane(val project: Project) : JPanel(), Disposable { is Host.Enterprise -> { accountManager.apiKey = host.apiKey settings.userInferenceUri = host.endpointAddress + ApplicationManager.getApplication().getService(LoginStateService::class.java).tryToWebsiteLogin(true) } is Host.SelfHost -> { accountManager.apiKey = "any-key-will-work" @@ -104,9 +104,7 @@ class SharedChatPane(val project: Project) : JPanel(), Disposable { } private fun handleNewFile(content: String) { - // TODO: file type? val vf = LightVirtualFile("Untitled", content) - val fileDescriptor = OpenFileDescriptor(project, vf) ApplicationManager.getApplication().invokeLater { @@ -116,7 +114,7 @@ class SharedChatPane(val project: Project) : JPanel(), Disposable { private fun addEventListeners() { - println("Adding ide event listeners") + logger.info("Adding ide event listeners") val listener: FileEditorManagerListener = object : FileEditorManagerListener { override fun fileOpened(@NotNull source: FileEditorManager, @NotNull file: VirtualFile) { this@SharedChatPane.sendActiveFileInfo() @@ -152,12 +150,12 @@ class SharedChatPane(val project: Project) : JPanel(), Disposable { project.messageBus.connect() .subscribe(InferenceGlobalContextChangedNotifier.TOPIC, object : InferenceGlobalContextChangedNotifier { override fun astFlagChanged(newValue: Boolean) { - println("ast changed to: $newValue") + logger.info("ast changed to: $newValue") this@SharedChatPane.sendUserConfig() } override fun vecdbFlagChanged(newValue: Boolean) { - println("vecdb changed to: $newValue") + logger.info("vecdb changed to: $newValue") this@SharedChatPane.sendUserConfig() } }) @@ -187,18 +185,31 @@ class SharedChatPane(val project: Project) : JPanel(), Disposable { postMessage(message) } - // TODO: handleOpenHotKeys - private fun handleOpenHotKeys() { - // TODO: handle open hotkey + ApplicationManager.getApplication().invokeLater { + ShowSettingsUtil.getInstance().showSettingsDialog(project, KeymapPanel::class.java) { + it.enableSearch("Refact.ai") + } + } } private fun handleOpenFile(fileName: String, line: Int?) { - // TODO: handle opening file + val file = File(fileName) + val vf = VfsUtil.findFileByIoFile(file, true) ?: return + + val fileDescriptor = OpenFileDescriptor(project, vf) + + ApplicationManager.getApplication().invokeLater { + val editor = FileEditorManager.getInstance(project).openTextEditor(fileDescriptor, true) + line?.let { + editor?.caretModel?.moveToLogicalPosition(LogicalPosition(line, 0)) + } + } + } private fun handleEvent(event: Events.FromChat) { - println("Event received: $event") + logger.info("Event received: $event") when (event) { is Events.Editor.Paste -> this.handlePaste(event.content) is Events.Editor.NewFile -> this.handleNewFile(event.content)