Skip to content

Commit 8b559db

Browse files
authored
Merge pull request #20 from devchat-ai/conversation-history
Conversation history
2 parents 17f93a4 + ed7991a commit 8b559db

20 files changed

Lines changed: 290 additions & 302 deletions

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ jobs:
3737
uses: actions/checkout@v4
3838
- name: Get latest version of main.html and main.js
3939
run: |
40-
wget $(curl -s https://api.github.com/repos/devchat-ai/devchat-plugins-frontend/releases/latest | grep -oP '"browser_download_url": "\K(.*)(?=")' | grep main.html) -O src/main/resources/static/main.html
41-
wget $(curl -s https://api.github.com/repos/devchat-ai/devchat-plugins-frontend/releases/latest | grep -oP '"browser_download_url": "\K(.*)(?=")' | grep main.js) -O src/main/resources/static/main.js
40+
wget $(curl -s https://api.github.com/repositories/709076669/releases/latest | grep -oP '"browser_download_url": "\K(.*)(?=")' | grep main.html) -O src/main/resources/static/main.html
41+
wget $(curl -s https://api.github.com/repositories/709076669/releases/latest | grep -oP '"browser_download_url": "\K(.*)(?=")' | grep main.js) -O src/main/resources/static/main.js
4242
4343
# Validate wrapper
4444
- name: Gradle Wrapper Validation

src/main/kotlin/ai/devchat/cli/DevChatResponse.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@ package ai.devchat.cli
88
*
99
* prompt 6e2a0d9b5c15eb33008250fee40383e77e8f80c75d9644b15bda60be256c8010
1010
*/
11-
class DevChatResponse {
12-
@JvmField
11+
class DevChatResponse(line: String) {
1312
var user: String? = null
14-
@JvmField
1513
var date: String? = null
16-
@JvmField
1714
var message: String? = null
18-
@JvmField
1915
var promptHash: String? = null
20-
fun populateFromLine(line: String) {
16+
17+
init {
2118
if (line.startsWith("User: ") && user == null) {
2219
user = line.substring("User: ".length)
2320
} else if (line.startsWith("Date: ") && date == null) {

src/main/kotlin/ai/devchat/cli/DevChatResponseConsumer.kt

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/main/kotlin/ai/devchat/cli/DevChatWrapper.kt

Lines changed: 56 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@ package ai.devchat.cli
33
import ai.devchat.common.Log
44
import com.alibaba.fastjson.JSON
55
import com.alibaba.fastjson.JSONArray
6+
import com.intellij.util.containers.addIfNotNull
67
import java.io.BufferedReader
78
import java.io.IOException
8-
import java.io.InputStream
9-
import java.io.InputStreamReader
10-
import java.util.*
11-
import java.util.function.BiConsumer
12-
import java.util.function.Consumer
9+
10+
private const val DEFAULT_LOG_MAX_COUNT = 10000
1311

1412
class DevChatWrapper {
1513
private var apiBase: String? = null
1614
private var apiKey: String? = null
1715
private var currentModel: String? = null
1816
private var command: String
19-
private val DEFAULT_LOG_MAX_COUNT = "100"
2017

2118
constructor(command: String) {
2219
this.command = command
@@ -29,23 +26,27 @@ class DevChatWrapper {
2926
this.command = command
3027
}
3128

32-
private fun execCommand(commands: List<String?>): String {
29+
private fun execCommand(commands: List<String>, callback: ((String) -> Unit)?): String? {
3330
val pb = ProcessBuilder(commands)
34-
if (apiBase != null) {
35-
pb.environment()["OPENAI_API_BASE"] = apiBase
36-
Log.info("api_base: $apiBase")
31+
val env = pb.environment()
32+
33+
apiBase?.let {
34+
env["OPENAI_API_BASE"] = it
35+
Log.info("api_base: $it")
3736
}
38-
if (apiKey != null) {
39-
pb.environment()["OPENAI_API_KEY"] = apiKey
40-
Log.info(
41-
"api_key: " + apiKey!!.substring(0, 5) + "..."
42-
+ apiKey!!.substring(apiKey!!.length - 4, apiKey!!.length)
43-
)
37+
apiKey?.let {
38+
env["OPENAI_API_KEY"] = it
39+
Log.info("api_key: ${it.substring(0, 5)}...${it.substring(it.length - 4)}")
4440
}
4541
return try {
46-
Log.info("Executing command: " + java.lang.String.join(" ", pb.command()))
42+
Log.info("Executing command: ${commands.joinToString(" ")}}")
4743
val process = pb.start()
48-
val text = process.inputStream.bufferedReader().use(BufferedReader::readText)
44+
val text = process.inputStream.bufferedReader().use { reader ->
45+
callback?.let {
46+
reader.forEachLine(it)
47+
null
48+
} ?: reader.readText()
49+
}
4950
val errors = process.errorStream.bufferedReader().use(BufferedReader::readText)
5051
process.waitFor()
5152
val exitCode = process.exitValue()
@@ -67,143 +68,57 @@ class DevChatWrapper {
6768
}
6869
}
6970

70-
private fun execCommand(commands: List<String?>, callback: Consumer<String>) {
71-
val pb = ProcessBuilder(commands)
72-
if (apiBase != null) {
73-
pb.environment()["OPENAI_API_BASE"] = apiBase
74-
Log.info("api_base: $apiBase")
75-
}
76-
if (apiKey != null) {
77-
pb.environment()["OPENAI_API_KEY"] = apiKey
78-
Log.info(
79-
"api_key: " + apiKey!!.substring(0, 5) + "..."
80-
+ apiKey!!.substring(apiKey!!.length - 4, apiKey!!.length)
81-
)
82-
}
83-
try {
84-
Log.info("Executing command: " + java.lang.String.join(" ", pb.command()))
85-
val process = pb.start()
86-
readOutputByLine(process.inputStream, callback)
87-
val exitCode = process.waitFor()
88-
if (exitCode != 0) {
89-
val error = readOutput(process.errorStream)
90-
Log.error(
91-
"Failed to execute command: " + java.lang.String.join(" ", pb.command()) + " Exit Code: " + exitCode
92-
+ " Error: " + error
93-
)
94-
throw RuntimeException(
95-
"Failed to execute command: " + java.lang.String.join(" ", pb.command()) + " Exit Code: " + exitCode
96-
+ " Error: " + error
97-
)
98-
}
99-
} catch (e: IOException) {
100-
Log.error("Failed to execute command: " + java.lang.String.join(" ", pb.command()))
101-
throw RuntimeException("Failed to execute command: " + java.lang.String.join(" ", pb.command()), e)
102-
} catch (e: InterruptedException) {
103-
Log.error("Failed to execute command: " + java.lang.String.join(" ", pb.command()))
104-
throw RuntimeException("Failed to execute command: " + java.lang.String.join(" ", pb.command()), e)
105-
}
71+
val prompt: (MutableList<Pair<String, String?>>, String, ((String) -> Unit)?) -> Unit get() = {
72+
flags: MutableList<Pair<String, String?>>, message: String, callback: ((String) -> Unit)? ->
73+
flags.addAll(listOf("model" to currentModel, "" to message))
74+
subCommand(listOf("prompt"))(flags, callback)
10675
}
10776

108-
@Throws(IOException::class)
109-
private fun readOutput(inputStream: InputStream): String {
110-
val reader = BufferedReader(InputStreamReader(inputStream))
111-
val output = StringBuilder()
112-
reader.forEachLine { line ->
113-
output.append(line)
114-
output.append('\n')
115-
}
116-
return output.toString()
77+
val logTopic: (String, Int?) -> JSONArray get() = {topic: String, maxCount: Int? ->
78+
val num: Int = maxCount ?: DEFAULT_LOG_MAX_COUNT
79+
JSON.parseArray(log(mutableListOf(
80+
"topic" to topic,
81+
"max-count" to num.toString()
82+
), null))
11783
}
11884

119-
@Throws(IOException::class)
120-
private fun readOutputByLine(inputStream: InputStream, callback: Consumer<String>) {
121-
val reader = BufferedReader(InputStreamReader(inputStream))
122-
reader.forEachLine { line -> callback.accept(line) }
123-
}
85+
val run get() = subCommand(listOf("run"))
86+
val log get() = subCommand(listOf("log"))
87+
val topic get() = subCommand(listOf("topic"))
12488

125-
fun runPromptCommand(flags: MutableMap<String, List<String?>>, message: String?, callback: Consumer<String>) {
126-
try {
127-
flags["model"] = listOf(currentModel)
128-
val commands = prepareCommand("prompt", flags, message)
129-
execCommand(commands, callback)
130-
} catch (e: Exception) {
131-
throw RuntimeException("Fail to run [prompt] command", e)
132-
}
133-
}
89+
val topicList: JSONArray get() = JSON.parseArray(topic(mutableListOf("list" to null), null))
90+
val commandList: JSONArray get() = JSON.parseArray(run(mutableListOf("list" to null), null))
13491

135-
fun runLogCommand(flags: Map<String, List<String?>>?): String {
136-
return try {
137-
val commands: List<String?> = prepareCommand(flags, "log")
138-
execCommand(commands)
139-
} catch (e: Exception) {
140-
throw RuntimeException("Failed to run [log] command", e)
141-
}
142-
}
14392

144-
val commandList: JSONArray
145-
get() {
146-
val result = runCommand(null, "run", "--list")
147-
return JSON.parseArray(result)
148-
}
149-
val commandNamesList: Array<String?>
150-
get() {
151-
val commandList = commandList
152-
val names = arrayOfNulls<String>(commandList.size)
153-
for (i in commandList.indices) {
154-
names[i] = commandList.getJSONObject(i).getString("name")
155-
}
156-
return names
93+
fun runCommand(subCommands: List<String>?, flags: List<Pair<String, String?>>? = null, callback: ((String) -> Unit)? = null): String? {
94+
val cmd: MutableList<String> = mutableListOf(command)
95+
cmd.addAll(subCommands.orEmpty())
96+
flags?.forEach { (name, value) ->
97+
cmd.add("--$name")
98+
cmd.addIfNotNull(value)
15799
}
158-
159-
fun listConversationsInOneTopic(topicHash: String): JSONArray {
160-
val result = runLogCommand(
161-
java.util.Map.of<String, List<String?>>(
162-
"topic", listOf(topicHash),
163-
"max-count", listOf(DEFAULT_LOG_MAX_COUNT)
164-
)
165-
)
166-
return JSON.parseArray(result)
167-
}
168-
169-
fun listTopics(): JSONArray {
170-
val result = runCommand(null, "topic", "--list")
171-
return JSON.parseArray(result)
172-
}
173-
174-
fun runCommand(flags: Map<String, List<String?>>?, vararg subCommands: String): String {
175100
return try {
176-
val commands: List<String?> = prepareCommand(flags, *subCommands)
177-
execCommand(commands)
101+
execCommand(cmd, callback)
178102
} catch (e: Exception) {
179-
Log.error("Failed to run [run] command: " + e.message)
180-
throw RuntimeException("Failed to run [${subCommands}] command", e)
103+
Log.error("Failed to run command $cmd: ${e.message}")
104+
throw RuntimeException("Failed to run command $cmd", e)
181105
}
182106
}
183107

184-
private fun prepareCommand(flags: Map<String, List<String?>>?, vararg subCommands: String): MutableList<String?> {
185-
val commands: MutableList<String?> = ArrayList()
186-
commands.add(command)
187-
Collections.addAll(commands, *subCommands)
188-
if (flags == null) {
189-
return commands
190-
}
191-
flags.forEach(BiConsumer { flag: String, values: List<String?> ->
192-
for (value in values) {
193-
commands.add("--$flag")
194-
commands.add(value)
108+
private fun subCommand(subCommands: List<String>): (MutableList<Pair<String, String?>>?, ((String) -> Unit)?) -> String? {
109+
val cmd: MutableList<String> = mutableListOf(command)
110+
cmd.addAll(subCommands)
111+
return {flags: List<Pair<String, String?>>?, callback: ((String) -> Unit)? ->
112+
flags?.forEach { (name, value) ->
113+
cmd.add("--$name")
114+
cmd.addIfNotNull(value)
115+
}
116+
try {
117+
execCommand(cmd, callback)
118+
} catch (e: Exception) {
119+
Log.error("Failed to run command $cmd: ${e.message}")
120+
throw RuntimeException("Failed to run command $cmd", e)
195121
}
196-
})
197-
return commands
198-
}
199-
200-
private fun prepareCommand(subCommand: String, flags: Map<String, List<String?>>, message: String?): List<String?> {
201-
val commands = prepareCommand(flags, subCommand)
202-
// Add the message to the command list
203-
if (!message.isNullOrEmpty()) {
204-
commands.add("--")
205-
commands.add(message)
206122
}
207-
return commands
208123
}
209124
}

src/main/kotlin/ai/devchat/devchat/ActionHandlerFactory.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ class ActionHandlerFactory {
1212
put(DevChatActions.SEND_MESSAGE_REQUEST, SendMessageRequestHandler::class)
1313
put(DevChatActions.SET_OR_UPDATE_KEY_REQUEST, SetOrUpdateKeyRequestHandler::class)
1414
put(DevChatActions.LIST_COMMANDS_REQUEST, ListCommandsRequestHandler::class)
15-
put(DevChatActions.LIST_CONVERSATIONS_REQUEST, ListConversationsRequestHandler::class)
15+
put(DevChatActions.LOAD_CONVERSATIONS_REQUEST, LoadConversationRequestHandler::class)
16+
put(DevChatActions.LOAD_HISTORY_MESSAGES_REQUEST, LoadHistoryMessagesRequestHandler::class)
1617
put(DevChatActions.LIST_TOPICS_REQUEST, ListTopicsRequestHandler::class)
1718
put(DevChatActions.INSERT_CODE_REQUEST, InsertCodeRequestHandler::class)
1819
put(DevChatActions.REPLACE_FILE_CONTENT_REQUEST, ReplaceFileContentHandler::class)

src/main/kotlin/ai/devchat/devchat/DevChatActionHandler.kt

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package ai.devchat.devchat
22

3+
import ai.devchat.cli.DevChatWrapper
4+
import ai.devchat.common.DevChatPathUtil
5+
import ai.devchat.common.Log
36
import com.alibaba.fastjson.JSONObject
47
import com.intellij.openapi.project.Project
58
import org.cef.browser.CefBrowser
6-
import java.util.function.BiConsumer
79

810
/**
911
* DevChatActionHandler class uses singleton pattern.
1012
*/
1113
class DevChatActionHandler private constructor() {
14+
val devChat: DevChatWrapper = DevChatWrapper(DevChatPathUtil.devchatBinPath)
1215
private var cefBrowser: CefBrowser? = null
1316
var project: Project? = null
1417
private set
@@ -17,15 +20,39 @@ class DevChatActionHandler private constructor() {
1720
this.cefBrowser = cefBrowser
1821
this.project = project
1922
}
23+
fun handle(
24+
action: String,
25+
jsCallback: String,
26+
callback: (JSONObject) -> Unit,
27+
) {
28+
val response = JSONObject()
29+
response["action"] = action
30+
val metadata = JSONObject()
31+
val payload = JSONObject()
32+
response["metadata"] = metadata
33+
response["payload"] = payload
34+
35+
try {
36+
Log.info("Handling $action request")
37+
callback(payload)
38+
metadata["status"] = "success"
39+
metadata["error"] = ""
40+
} catch (e: Exception) {
41+
Log.error("Exception occurred while handle action $action: ${e.message}")
42+
metadata["status"] = "error"
43+
metadata["error"] = e.message
44+
}
45+
cefBrowser!!.executeJavaScript("$jsCallback($response)", "", 0)
46+
}
2047

21-
fun sendResponse(action: String, responseFunc: String, callback: BiConsumer<JSONObject, JSONObject>) {
48+
fun sendResponse(action: String, responseFunc: String, callback: (JSONObject, JSONObject) -> Unit) {
2249
val response = JSONObject()
2350
response["action"] = action
2451
val metadata = JSONObject()
2552
val payload = JSONObject()
2653
response["metadata"] = metadata
2754
response["payload"] = payload
28-
callback.accept(metadata, payload)
55+
callback(metadata, payload)
2956
cefBrowser!!.executeJavaScript("$responseFunc($response)", "", 0)
3057
}
3158

src/main/kotlin/ai/devchat/devchat/DevChatActions.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ object DevChatActions {
1010
const val ADD_CONTEXT_NOTIFY = "addContext/notify"
1111
const val LIST_COMMANDS_REQUEST = "listCommands/request"
1212
const val LIST_COMMANDS_RESPONSE = "listCommands/response"
13-
const val LIST_CONVERSATIONS_REQUEST = "listConversations/request"
14-
const val LIST_CONVERSATIONS_RESPONSE = "listConversations/response"
13+
const val LOAD_CONVERSATIONS_REQUEST = "loadConversations/request"
14+
const val LOAD_CONVERSATIONS_RESPONSE = "loadConversations/response"
15+
const val LOAD_HISTORY_MESSAGES_REQUEST = "loadHistoryMessages/request"
16+
const val LOAD_HISTORY_MESSAGES_RESPONSE = "loadHistoryMessages/response"
1517
const val LIST_TOPICS_REQUEST = "listTopics/request"
1618
const val LIST_TOPICS_RESPONSE = "listTopics/response"
1719
const val INSERT_CODE_REQUEST = "insertCode/request"

src/main/kotlin/ai/devchat/devchat/handler/CommitCodeRequestHandler.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import java.io.BufferedReader
99
import java.io.File
1010
import java.io.IOException
1111
import java.io.InputStreamReader
12-
import java.util.function.BiConsumer
1312

1413
class CommitCodeRequestHandler(private val devChatActionHandler: DevChatActionHandler) : ActionHandler {
1514
private var metadata: JSONObject? = null
@@ -54,7 +53,7 @@ class CommitCodeRequestHandler(private val devChatActionHandler: DevChatActionHa
5453
}
5554
}
5655
}
57-
val processCommitResponse = BiConsumer { metadata: JSONObject, payload: JSONObject ->
56+
val processCommitResponse = { metadata: JSONObject, payload: JSONObject ->
5857
if (error.isEmpty()) {
5958
metadata["status"] = "success"
6059
metadata["error"] = ""

0 commit comments

Comments
 (0)