From 74711e7f1462745508561727bee9b7bee36e549e Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 20 Aug 2024 17:29:41 +0800 Subject: [PATCH] Revert "Customize plugin icon" --- gui | 2 +- .../kotlin/ai/devchat/common/HttpClient.kt | 143 ---------------- .../kotlin/ai/devchat/core/DevChatClient.kt | 158 ++++++++++++------ .../ai/devchat/plugin/DevChatToolWindow.kt | 3 - .../plugin/hints/ChatCVProviderBase.kt | 12 +- 5 files changed, 108 insertions(+), 210 deletions(-) delete mode 100644 src/main/kotlin/ai/devchat/common/HttpClient.kt diff --git a/gui b/gui index 26c7d79e..e7dea52c 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit 26c7d79e9d84643a8f502ed9015444a16f52e8fb +Subproject commit e7dea52c58d9da9dfb47c10dc20d8c35e9fd9837 diff --git a/src/main/kotlin/ai/devchat/common/HttpClient.kt b/src/main/kotlin/ai/devchat/common/HttpClient.kt deleted file mode 100644 index adf05d0a..00000000 --- a/src/main/kotlin/ai/devchat/common/HttpClient.kt +++ /dev/null @@ -1,143 +0,0 @@ -package ai.devchat.common - -import ai.devchat.core.DevChatClient -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOn -import kotlinx.serialization.json.Json -import kotlinx.serialization.serializer -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import java.io.File -import java.io.IOException - -class HttpClient { - val client = OkHttpClient() - val json = Json { ignoreUnknownKeys = true } - - inline fun get( - urlAddress: String, - queryParams: Map = emptyMap() - ): T? { - Log.info("GET request to [$urlAddress] with request parameters: $queryParams") - val urlBuilder = urlAddress.toHttpUrlOrNull()?.newBuilder() ?: return null - queryParams.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v.toString()) } - val url = urlBuilder.build() - val request = Request.Builder().url(url).get().build() - var retries = DevChatClient.MAX_RETRIES - while (retries > 0) { - try { - client.newCall(request).execute().use { response -> - if (!response.isSuccessful) throw IOException( - "Unsuccessful response: ${response.code} ${response.message}" - ) - val result = response.body?.string()?.let { - json.decodeFromString(it) - } - return result - } - } catch (e: IOException) { - Log.warn("$e, retrying...") - retries-- - Thread.sleep(DevChatClient.RETRY_INTERVAL) - } catch (e: Exception) { - Log.warn(e.toString()) - return null - } - } - return null - } - - inline fun post(urlAddress: String, body: T? = null): R? { - Log.info("POST request to [$urlAddress] with request body: $body") - val url = urlAddress.toHttpUrlOrNull() ?: return null - val requestBody = json.encodeToString(serializer(), body).toRequestBody("application/json".toMediaType()) - val request = Request.Builder().url(url).post(requestBody).build() - var retries = DevChatClient.MAX_RETRIES - while (retries > 0) { - try { - val response: Response = client.newCall(request).execute() - if (!response.isSuccessful) throw IOException( - "Unsuccessful response: ${response.code} ${response.message}" - ) - val result = response.body?.let { - json.decodeFromString(it.string()) - } - return result - } catch (e: IOException) { - Log.warn("$e, retrying...") - retries-- - Thread.sleep(DevChatClient.RETRY_INTERVAL) - } catch (e: Exception) { - Log.warn(e.toString()) - return null - } - } - return null - } - - inline fun streamPost(urlAddress: String, body: T? = null): Flow = callbackFlow { - Log.info("POST request to [$urlAddress] with request body: $body") - val url = urlAddress.toHttpUrlOrNull() ?: return@callbackFlow - val requestJson = json.encodeToString(serializer(), body) - val requestBody = requestJson.toRequestBody("application/json".toMediaType()) - val request = Request.Builder().url(url).post(requestBody).build() - val call = client.newCall(request) - - val response = call.execute() - if (!response.isSuccessful) { - throw IOException("Unexpected code $response") - } - response.body?.byteStream()?.use {inputStream -> - val buffer = ByteArray(8192) // 8KB buffer - var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } != -1) { - val chunk = buffer.copyOf(bytesRead).toString(Charsets.UTF_8) - send(json.decodeFromString(chunk)) - } - } - close() - awaitClose { call.cancel() } - }.flowOn(Dispatchers.IO) - - - fun getMedia( - urlAddress: String, - queryParams: Map = emptyMap(), - outFile: File - ) { - Log.info("GET request to [$urlAddress] with request parameters: $queryParams") - val urlBuilder = urlAddress.toHttpUrlOrNull()?.newBuilder() ?: return - queryParams.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v.toString()) } - val url = urlBuilder.build() - val request = Request.Builder().url(url).get().build() - var retries = DevChatClient.MAX_RETRIES - while (retries > 0) { - try { - client.newCall(request).execute().use { response -> - if (!response.isSuccessful) throw IOException( - "Unsuccessful response: ${response.code} ${response.message}" - ) - outFile.outputStream().use { - response.body?.byteStream()?.copyTo(it) - Log.info("Media saved to ${outFile.absolutePath}") - } - } - return - } catch (e: IOException) { - Log.warn("$e, retrying...") - retries-- - Thread.sleep(DevChatClient.RETRY_INTERVAL) - } catch (e: Exception) { - Log.warn(e.toString()) - } - } - } -} - diff --git a/src/main/kotlin/ai/devchat/core/DevChatClient.kt b/src/main/kotlin/ai/devchat/core/DevChatClient.kt index b9fb02a7..894ec1ee 100644 --- a/src/main/kotlin/ai/devchat/core/DevChatClient.kt +++ b/src/main/kotlin/ai/devchat/core/DevChatClient.kt @@ -1,12 +1,14 @@ package ai.devchat.core -import ai.devchat.common.HttpClient import ai.devchat.common.Log import ai.devchat.common.PathUtils import ai.devchat.plugin.localServicePort -import ai.devchat.storage.CONFIG import kotlinx.coroutines.* +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flowOn import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -14,13 +16,18 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.serializer -import java.net.URL +import okhttp3.* +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException import java.nio.file.Files import java.nio.file.Paths import java.time.Instant import kotlin.system.measureTimeMillis + inline fun T.asMap(): Map where T : @Serializable Any { val json = Json { encodeDefaults = true } val jsonString = json.encodeToString(serializer(),this) @@ -202,22 +209,104 @@ fun timeThis(block: suspend () -> Unit) { } } - class DevChatClient { private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO) private val baseURL get() = "http://localhost:$localServicePort" private var job: Job? = null - val client: HttpClient = HttpClient() - companion object { const val DEFAULT_LOG_MAX_COUNT = 10000 const val LOG_RAW_DATA_SIZE_LIMIT = 4 * 1024 // 4kb const val RETRY_INTERVAL: Long = 500 // ms const val MAX_RETRIES: Int = 10 } + private val client = OkHttpClient() private val json = Json { ignoreUnknownKeys = true } + private inline fun get( + path: String, + queryParams: Map = emptyMap() + ): T? { + Log.info("GET request to [$baseURL$path] with request parameters: $queryParams") + val urlBuilder = "$baseURL$path".toHttpUrlOrNull()?.newBuilder() ?: return null + queryParams.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v.toString()) } + val url = urlBuilder.build() + val request = Request.Builder().url(url).get().build() + var retries = MAX_RETRIES + while (retries > 0) { + try { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException( + "Unsuccessful response: ${response.code} ${response.message}" + ) + val result = response.body?.string()?.let { + json.decodeFromString(it) + } + return result + } + } catch (e: IOException) { + Log.warn("$e, retrying...") + retries-- + Thread.sleep(RETRY_INTERVAL) + } catch (e: Exception) { + Log.warn(e.toString()) + return null + } + } + return null + } + + private inline fun post(path: String, body: T? = null): R? { + Log.info("POST request to [$baseURL$path] with request body: $body") + val url = "$baseURL$path".toHttpUrlOrNull() ?: return null + val requestBody = json.encodeToString(serializer(), body).toRequestBody("application/json".toMediaType()) + val request = Request.Builder().url(url).post(requestBody).build() + var retries = MAX_RETRIES + while (retries > 0) { + try { + val response: Response = client.newCall(request).execute() + if (!response.isSuccessful) throw IOException( + "Unsuccessful response: ${response.code} ${response.message}" + ) + val result = response.body?.let { + json.decodeFromString(it.string()) + } + return result + } catch (e: IOException) { + Log.warn("$e, retrying...") + retries-- + Thread.sleep(RETRY_INTERVAL) + } catch (e: Exception) { + Log.warn(e.toString()) + return null + } + } + return null + } + + private inline fun streamPost(path: String, body: T? = null): Flow = callbackFlow { + Log.info("POST request to [$baseURL$path] with request body: $body") + val url = "$baseURL$path".toHttpUrlOrNull() ?: return@callbackFlow + val requestJson = json.encodeToString(serializer(), body) + val requestBody = requestJson.toRequestBody("application/json".toMediaType()) + val request = Request.Builder().url(url).post(requestBody).build() + val call = client.newCall(request) + + val response = call.execute() + if (!response.isSuccessful) { + throw IOException("Unexpected code $response") + } + response.body?.byteStream()?.use {inputStream -> + val buffer = ByteArray(8192) // 8KB buffer + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + val chunk = buffer.copyOf(bytesRead).toString(Charsets.UTF_8) + send(json.decodeFromString(chunk)) + } + } + close() + awaitClose { call.cancel() } + }.flowOn(Dispatchers.IO) fun message( message: ChatRequest, @@ -227,7 +316,7 @@ class DevChatClient { ) { cancelMessage() job = scope.launch { - client.streamPost("$baseURL/message/msg", message) + streamPost("/message/msg", message) .catch { e -> onError(e.toString()) Log.warn("Error on sending message: $e") @@ -246,13 +335,13 @@ class DevChatClient { } fun getWorkflowList(): List? { - return client.get("$baseURL/workflows/list") + return get("/workflows/list") } fun getWorkflowConfig(): WorkflowConfig? { - return client.get("$baseURL/workflows/config") + return get("/workflows/config") } fun updateWorkflows() { - val response: UpdateWorkflowResponse? = client.post("$baseURL/workflows/update") + val response: UpdateWorkflowResponse? = post("/workflows/update") Log.info("Update workflows response: $response") } @@ -264,7 +353,7 @@ class DevChatClient { } else { body["filepath"] = PathUtils.createTempFile(jsonData, "devchat_log_insert_", ".json") } - val response: LogInsertRes? = client.post("$baseURL/logs/insert", body) + val response: LogInsertRes? = post("/logs/insert", body) if (body.containsKey("filepath")) { try { Files.delete(Paths.get(body["filepath"]!!)) @@ -275,13 +364,13 @@ class DevChatClient { return response } fun deleteLog(logHash: String): LogDeleteRes? { - return client.post("$baseURL/logs/delete", mapOf( + return post("/logs/delete", mapOf( "workspace" to PathUtils.workspace, "hash" to logHash )) } fun getTopicLogs(topicRootHash: String, offset: Int = 0, limit: Int = DEFAULT_LOG_MAX_COUNT): List { - return client.get>("$baseURL/topics/$topicRootHash/logs", mapOf( + return get>("/topics/$topicRootHash/logs", mapOf( "limit" to limit, "offset" to offset, "workspace" to PathUtils.workspace, @@ -293,56 +382,17 @@ class DevChatClient { "offset" to offset, "workspace" to PathUtils.workspace, ) - return client.get?>("$baseURL/topics", queryParams).orEmpty() + return get?>("/topics", queryParams).orEmpty() } fun deleteTopic(topicRootHash: String) { - val response: Map? = client.post("$baseURL/topics/delete", mapOf( + val response: Map? = post("/topics/delete", mapOf( "topic_hash" to topicRootHash, "workspace" to PathUtils.workspace, )) Log.info("deleteTopic response data: $response") } - fun getWebappUrl(): String? { - val apiBase = CONFIG["providers.devchat.api_base"] as String - val urlOrPath: String? = client.get("$apiBase/addresses/webapp") - if (urlOrPath.isNullOrEmpty()) { - Log.warn("No webapp url found"); - return null - } - var href = "" - href = if (urlOrPath.startsWith("http://") || urlOrPath.startsWith("https://")) { - urlOrPath - } else { - URL(URL(apiBase), urlOrPath).toString() - } - if (href.endsWith('/')) { - href = href.dropLast(1) - } - if (href.endsWith("/api")) { - href = href.dropLast(4) - } - Log.info("Webapp url: $href") - return href - } - - fun getIconUrl(): String { - try { - val webappUrl = getWebappUrl() - if (!webappUrl.isNullOrEmpty()) { - val iconsUrl = URL(URL(webappUrl), "/api/v1/plugin/icons/") - val res: Map? = client.get(URL(iconsUrl, "filename/intellij").toString()) - res?.get("filename")?.let { - return URL(iconsUrl, it).toString() - } - } - } catch (e: Exception) { - Log.warn(e.toString()) - } - return "/icons/pluginIcon_dark.svg" - } - private fun cancelMessage() { job?.cancel() job = null diff --git a/src/main/kotlin/ai/devchat/plugin/DevChatToolWindow.kt b/src/main/kotlin/ai/devchat/plugin/DevChatToolWindow.kt index 1674d1a1..2bd0a7e8 100644 --- a/src/main/kotlin/ai/devchat/plugin/DevChatToolWindow.kt +++ b/src/main/kotlin/ai/devchat/plugin/DevChatToolWindow.kt @@ -1,14 +1,12 @@ package ai.devchat.plugin import ai.devchat.common.Log -import ai.devchat.core.DC_CLIENT import ai.devchat.core.DevChatWrapper import ai.devchat.installer.DevChatSetupThread import com.intellij.openapi.Disposable import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.IconLoader import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.ui.JBColor @@ -46,7 +44,6 @@ class DevChatToolWindow : ToolWindowFactory, DumbAware, Disposable { while (!pythonReady) { delay(100) } localService = LocalService().start() } - toolWindow.setIcon(IconLoader.getIcon(DC_CLIENT.getIconUrl(), this::class.java.classLoader)) } override fun dispose() { diff --git a/src/main/kotlin/ai/devchat/plugin/hints/ChatCVProviderBase.kt b/src/main/kotlin/ai/devchat/plugin/hints/ChatCVProviderBase.kt index a7f25799..f3b3cfc9 100644 --- a/src/main/kotlin/ai/devchat/plugin/hints/ChatCVProviderBase.kt +++ b/src/main/kotlin/ai/devchat/plugin/hints/ChatCVProviderBase.kt @@ -1,13 +1,10 @@ package ai.devchat.plugin.hints -import ai.devchat.core.DC_CLIENT import ai.devchat.core.DevChatActions import ai.devchat.core.handlers.SendUserMessageHandler import ai.devchat.plugin.DevChatToolWindow import com.alibaba.fastjson.JSONObject -import com.intellij.codeInsight.codeVision.CodeVisionAnchorKind -import com.intellij.codeInsight.codeVision.CodeVisionEntry -import com.intellij.codeInsight.codeVision.CodeVisionRelativeOrdering +import com.intellij.codeInsight.codeVision.* import com.intellij.codeInsight.codeVision.ui.model.ClickableTextCodeVisionEntry import com.intellij.codeInsight.hints.codeVision.CodeVisionProviderBase import com.intellij.codeInsight.hints.settings.language.isInlaySettingsEditor @@ -16,10 +13,7 @@ import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.util.IconLoader import com.intellij.openapi.util.TextRange import com.intellij.openapi.wm.ToolWindowManager -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.SmartPointerManager -import com.intellij.psi.SyntaxTraverser +import com.intellij.psi.* import com.intellij.psi.util.elementType import java.awt.event.MouseEvent import javax.swing.Icon @@ -28,7 +22,7 @@ abstract class ChatCVProviderBase : CodeVisionProviderBase() { abstract fun buildPayload(editor: Editor, element: PsiElement): JSONObject open fun getIcon(): Icon? { - return IconLoader.getIcon(DC_CLIENT.getIconUrl(), this::class.java.classLoader) + return IconLoader.getIcon("/icons/pluginIcon_dark.svg", this::class.java.classLoader) } override fun computeForEditor(editor: Editor, file: PsiFile): List> {