Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor & bug fix #21

Merged
merged 7 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 11 additions & 27 deletions src/main/kotlin/ai/devchat/cli/DevChatResponse.kt
Original file line number Diff line number Diff line change
@@ -1,39 +1,23 @@
package ai.devchat.cli

/*
* User: Daniel Hu <[email protected]>
* Date: Mon Oct 16 22:40:06 2023 +0800
*
* Hello! How can I assist you today?
*
* prompt 6e2a0d9b5c15eb33008250fee40383e77e8f80c75d9644b15bda60be256c8010
*/
class DevChatResponse(line: String) {
class DevChatResponse {
var user: String? = null
var date: String? = null
var message: String? = null
var promptHash: String? = null

init {
if (line.startsWith("User: ") && user == null) {
user = line.substring("User: ".length)
} else if (line.startsWith("Date: ") && date == null) {
date = line.substring("Date: ".length)
// 71 is the length of string
// "prompt 6e2a0d9b5c15eb33008250fee40383e77e8f80c75d9644b15bda60be256c8010"
} else if (line.startsWith("prompt ") && line.length == 71) {
promptHash = line.substring("prompt ".length)
message += "\n"
} else if (!line.isEmpty()) {
if (message == null) {
message = line
} else {
message += """

$line
""".trimIndent()
fun update(line: String) : DevChatResponse {
when {
line.startsWith("User: ") -> user = user ?: line.substring("User: ".length)
line.startsWith("Date: ") -> date = date ?: line.substring("Date: ".length)
// 71 is the length of the prompt hash
line.startsWith("prompt ") && line.length == 71 -> {
promptHash = line.substring("prompt ".length)
message = message?.let { "$it\n" } ?: "\n"
}
line.isNotEmpty() -> message = message?.let { "$it\n$line" } ?: line
}
return this
}

override fun toString(): String {
Expand Down
30 changes: 15 additions & 15 deletions src/main/kotlin/ai/devchat/cli/DevChatWrapper.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ai.devchat.cli

import ai.devchat.common.DevChatPathUtil
import ai.devchat.common.Log
import ai.devchat.common.Settings
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONArray
import com.intellij.util.containers.addIfNotNull
Expand All @@ -9,21 +11,19 @@ import java.io.IOException

private const val DEFAULT_LOG_MAX_COUNT = 10000

class DevChatWrapper {
private var apiBase: String? = null
private var apiKey: String? = null
class DevChatWrapper(
private val command: String = DevChatPathUtil.devchatBinPath,
private var apiBase: String? = null,
private var apiKey: String? = null,
private var currentModel: String? = null
private var command: String

constructor(command: String) {
this.command = command
}

constructor(apiBase: String?, apiKey: String?, currentModel: String?, command: String) {
this.apiBase = apiBase
this.apiKey = apiKey
this.currentModel = currentModel
this.command = command
) {
init {
if (apiBase.isNullOrEmpty() || apiKey.isNullOrEmpty() || currentModel.isNullOrEmpty()) {
val (key, api, model) = Settings.getAPISettings()
apiBase = apiBase ?: api
apiKey = apiKey ?: key
currentModel = currentModel ?: model
}
}

private fun execCommand(commands: List<String>, callback: ((String) -> Unit)?): String? {
Expand All @@ -44,7 +44,7 @@ class DevChatWrapper {
val text = process.inputStream.bufferedReader().use { reader ->
callback?.let {
reader.forEachLine(it)
null
""
} ?: reader.readText()
}
val errors = process.errorStream.bufferedReader().use(BufferedReader::readText)
Expand Down
19 changes: 19 additions & 0 deletions src/main/kotlin/ai/devchat/common/Settings.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ai.devchat.common

import ai.devchat.idea.settings.DevChatSettingsState
import ai.devchat.idea.storage.SensitiveDataStorage

object Settings {
fun getAPISettings() : Triple<String?, String?, String?> {
val settings = DevChatSettingsState.instance
val apiKey = SensitiveDataStorage.key
if (settings.apiBase.isEmpty()) {
settings.apiBase = when {
apiKey?.startsWith("sk-") == true -> "https://api.openai.com/v1"
apiKey?.startsWith("DC.") == true -> "https://api.devchat.ai/v1"
else -> settings.apiBase
}
}
return Triple(apiKey, settings.apiBase, settings.defaultModel)
}
}
2 changes: 0 additions & 2 deletions src/main/kotlin/ai/devchat/devchat/ActionHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,5 @@ package ai.devchat.devchat
import com.alibaba.fastjson.JSONObject

interface ActionHandler {
fun setMetadata(metadata: JSONObject)
fun setPayload(payload: JSONObject)
fun executeAction()
}
71 changes: 30 additions & 41 deletions src/main/kotlin/ai/devchat/devchat/ActionHandlerFactory.kt
Original file line number Diff line number Diff line change
@@ -1,52 +1,41 @@
package ai.devchat.devchat

import ai.devchat.devchat.handler.*
import java.lang.reflect.InvocationTargetException
import com.alibaba.fastjson.JSONObject
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor

class ActionHandlerFactory {
private val actionHandlerMap: Map<String, KClass<out ActionHandler>> =
object : HashMap<String, KClass<out ActionHandler>>() {
init {
put(DevChatActions.SEND_MESSAGE_REQUEST, SendMessageRequestHandler::class)
put(DevChatActions.SET_OR_UPDATE_KEY_REQUEST, SetOrUpdateKeyRequestHandler::class)
put(DevChatActions.LIST_COMMANDS_REQUEST, ListCommandsRequestHandler::class)
put(DevChatActions.LOAD_CONVERSATIONS_REQUEST, LoadConversationRequestHandler::class)
put(DevChatActions.LOAD_HISTORY_MESSAGES_REQUEST, LoadHistoryMessagesRequestHandler::class)
put(DevChatActions.LIST_TOPICS_REQUEST, ListTopicsRequestHandler::class)
put(DevChatActions.INSERT_CODE_REQUEST, InsertCodeRequestHandler::class)
put(DevChatActions.REPLACE_FILE_CONTENT_REQUEST, ReplaceFileContentHandler::class)
put(DevChatActions.VIEW_DIFF_REQUEST, ViewDiffRequestHandler::class)
put(DevChatActions.LIST_CONTEXTS_REQUEST, ListContextsRequestHandler::class)
put(DevChatActions.LIST_MODELS_REQUEST, ListModelsRequestHandler::class)
put(DevChatActions.ADD_CONTEXT_REQUEST, AddContextRequestHandler::class)
put(DevChatActions.GET_KEY_REQUEST, GetKeyRequestHandler::class)
put(DevChatActions.COMMIT_CODE_REQUEST, CommitCodeRequestHandler::class)
put(DevChatActions.GET_SETTING_REQUEST, GetSettingRequestHandler::class)
put(DevChatActions.UPDATE_SETTING_REQUEST, UpdateSettingRequestHandler::class)
put(DevChatActions.SHOW_SETTING_DIALOG_REQUEST, ShowSettingDialogRequestHandler::class)
put(DevChatActions.DELETE_LAST_CONVERSATION_REQUEST, DeleteLastConversationRequestHandler::class)
put(DevChatActions.DELETE_TOPIC_REQUEST, DeleteTopicRequestHandler::class)
}
}
private val actionHandlerMap: Map<String, KClass<out ActionHandler>> = mapOf(
DevChatActions.SEND_MESSAGE_REQUEST to SendMessageRequestHandler::class,
DevChatActions.SET_OR_UPDATE_KEY_REQUEST to SetOrUpdateKeyRequestHandler::class,
DevChatActions.LIST_COMMANDS_REQUEST to ListCommandsRequestHandler::class,
DevChatActions.LOAD_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,
DevChatActions.INSERT_CODE_REQUEST to InsertCodeRequestHandler::class,
DevChatActions.REPLACE_FILE_CONTENT_REQUEST to ReplaceFileContentHandler::class,
DevChatActions.VIEW_DIFF_REQUEST to ViewDiffRequestHandler::class,
DevChatActions.LIST_CONTEXTS_REQUEST to ListContextsRequestHandler::class,
DevChatActions.LIST_MODELS_REQUEST to ListModelsRequestHandler::class,
DevChatActions.ADD_CONTEXT_REQUEST to AddContextRequestHandler::class,
DevChatActions.GET_KEY_REQUEST to GetKeyRequestHandler::class,
DevChatActions.COMMIT_CODE_REQUEST to CommitCodeRequestHandler::class,
DevChatActions.GET_SETTING_REQUEST to GetSettingRequestHandler::class,
DevChatActions.UPDATE_SETTING_REQUEST to UpdateSettingRequestHandler::class,
DevChatActions.SHOW_SETTING_DIALOG_REQUEST to ShowSettingDialogRequestHandler::class,
DevChatActions.DELETE_LAST_CONVERSATION_REQUEST to DeleteLastConversationRequestHandler::class,
DevChatActions.DELETE_TOPIC_REQUEST to DeleteTopicRequestHandler::class,
)

fun createActionHandler(action: String): ActionHandler {
val handlerClass = actionHandlerMap[action]
return if (handlerClass != null) {
try {
handlerClass.primaryConstructor!!.call(DevChatActionHandler.instance)
} catch (e: NoSuchMethodException) {
throw RuntimeException("Failed to instantiate action handler for: $action", e)
} catch (e: InstantiationException) {
throw RuntimeException("Failed to instantiate action handler for: $action", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Failed to instantiate action handler for: $action", e)
} catch (e: InvocationTargetException) {
throw RuntimeException("Failed to instantiate action handler for: $action", e)
}
} else {
throw RuntimeException("Action handler not found: $action")
fun createActionHandler(action: String, metadata: JSONObject, payload: JSONObject): ActionHandler {
val handlerClass = actionHandlerMap[action] ?: throw RuntimeException("Action handler not found: $action")
return try {
handlerClass.primaryConstructor!!.call(metadata, payload)
} catch (e: Exception) {
// Catch any exception since the handling is the same
throw RuntimeException("Failed to instantiate action handler for: $action", e)
}
}
}
50 changes: 50 additions & 0 deletions src/main/kotlin/ai/devchat/devchat/BaseActionHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ai.devchat.devchat

import ai.devchat.cli.DevChatWrapper
import ai.devchat.common.Log
import com.alibaba.fastjson.JSONObject

const val DEFAULT_RESPONSE_FUNC = "IdeaToJSMessage"

abstract class BaseActionHandler(
val metadata: JSONObject? = null,
val payload: JSONObject? = null
) : ActionHandler {
val handler = DevChatActionHandler.instance
val wrapper = DevChatWrapper()
val jsCallback: String = metadata?.getString("callback") ?: DEFAULT_RESPONSE_FUNC

abstract val actionName: String

open fun action() { send() }

open fun except(exception: Exception) {
send(
metadata = mapOf(
"status" to "error",
"error" to exception
)
)
}

fun send(metadata: Map<String, Any?>? = null, payload: Map<String, Any?>? = null) {
handler?.sendResponse(
actionName,
jsCallback,
metadata?.let { JSONObject(it) },
payload?.let { JSONObject(it) },
)
}

override fun executeAction() {
try {
Log.info("Handling $actionName request")
action()
} catch (e: Exception) {
e.printStackTrace()
Log.error("Exception occurred while handle action $actionName: ${e.message}")
except(e)
}
}

}
26 changes: 16 additions & 10 deletions src/main/kotlin/ai/devchat/devchat/DevChatActionHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import org.cef.browser.CefBrowser
* DevChatActionHandler class uses singleton pattern.
*/
class DevChatActionHandler private constructor() {
val devChat: DevChatWrapper = DevChatWrapper(DevChatPathUtil.devchatBinPath)
private var cefBrowser: CefBrowser? = null
var project: Project? = null
private set
Expand All @@ -23,7 +22,8 @@ class DevChatActionHandler private constructor() {
fun handle(
action: String,
jsCallback: String,
callback: (JSONObject) -> Unit,
success: (JSONObject, JSONObject) -> Unit,
fail: (JSONObject, JSONObject) -> Unit,
) {
val response = JSONObject()
response["action"] = action
Expand All @@ -34,26 +34,32 @@ class DevChatActionHandler private constructor() {

try {
Log.info("Handling $action request")
callback(payload)
metadata["status"] = "success"
metadata["error"] = ""
success(metadata, payload)
} catch (e: Exception) {
Log.error("Exception occurred while handle action $action: ${e.message}")
metadata["status"] = "error"
metadata["error"] = e.message
fail(metadata, payload)
}
cefBrowser!!.executeJavaScript("$jsCallback($response)", "", 0)
}

fun sendResponse(action: String, responseFunc: String, callback: (JSONObject, JSONObject) -> Unit) {
fun sendResponse(
action: String,
jsCallback: String,
metadata: JSONObject? = null,
payload: JSONObject? = null,
) {
val response = JSONObject()
response["action"] = action
val metadata = JSONObject()
val payload = JSONObject()
response["metadata"] = metadata
response["payload"] = payload
callback(metadata, payload)
cefBrowser!!.executeJavaScript("$responseFunc($response)", "", 0)
response["metadata"] = metadata ?: JSONObject(mapOf(
"status" to "success",
"error" to ""
))
response["payload"] = payload ?: JSONObject()
cefBrowser!!.executeJavaScript("$jsCallback($response)", "", 0)
}

companion object {
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/ai/devchat/devchat/DevChatActions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ object DevChatActions {
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 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"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,17 @@
package ai.devchat.devchat.handler

import ai.devchat.devchat.ActionHandler
import ai.devchat.devchat.DevChatActionHandler
import ai.devchat.devchat.DevChatActions
import ai.devchat.devchat.*
import com.alibaba.fastjson.JSONObject

class AddContextNotifyHandler(private val devChatActionHandler: DevChatActionHandler) : ActionHandler {
private var metadata: JSONObject? = null
private var payload: JSONObject? = null
val RESPONSE_FUNC = "IdeaToJSMessage"
override fun executeAction() {
devChatActionHandler.sendResponse(
DevChatActions.ADD_CONTEXT_NOTIFY,
RESPONSE_FUNC
) { metadata: JSONObject, payload: JSONObject ->
metadata["status"] = "success"
metadata["error"] = ""
payload["path"] = this.payload!!.getString("path")
payload["content"] = this.payload!!.getString("content")
payload["languageId"] = this.payload!!.getString("languageId")
payload["startLine"] = this.payload!!.getInteger("startLine")
}
}

override fun setMetadata(metadata: JSONObject) {
this.metadata = metadata
}

override fun setPayload(payload: JSONObject) {
this.payload = payload
class AddContextNotifyHandler(metadata: JSONObject?, payload: JSONObject?) : BaseActionHandler(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")
))
}
}
Loading
Loading