Skip to content

Commit

Permalink
Added controls to assistant
Browse files Browse the repository at this point in the history
  • Loading branch information
codemakerai-dev committed Jun 19, 2024
1 parent 90cc3d4 commit 1e184d4
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import ai.codemaker.jetbrains.assistant.Role
import ai.codemaker.jetbrains.file.FileExtensions
import ai.codemaker.jetbrains.service.CodeMakerService
import ai.codemaker.jetbrains.settings.AppSettingsState
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.FileEditorManager
Expand All @@ -25,8 +24,6 @@ import com.intellij.ui.jcef.JBCefBrowserBase
import com.intellij.ui.jcef.JBCefBrowserBuilder
import com.intellij.ui.jcef.JBCefJSQuery
import com.intellij.util.ui.JBUI
import org.cef.browser.CefBrowser
import org.cef.handler.CefLoadHandlerAdapter
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser
Expand All @@ -42,7 +39,7 @@ import javax.swing.JTextField
class AssistantWindowFactory : ToolWindowFactory, DumbAware {

object AssistantWindowFactory {
const val ASSISTANT_VIEW = "/webview/assistant.html"
const val ASSISTANT_HOME_VIEW = "file:///assistant.html"
}

override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
Expand Down Expand Up @@ -74,8 +71,11 @@ class AssistantWindowFactory : ToolWindowFactory, DumbAware {

private fun createChatPanel(): Component {
chatScreen.setProperty(JBCefBrowserBase.Properties.NO_CONTEXT_MENU, true)
chatScreen.jbCefClient.addLoadHandler(LoadHandler(), chatScreen.cefBrowser)
chatScreen.loadHTML(assistantView())
chatScreen.setOpenLinksInExternalBrowser(true)
chatScreen.loadURL(AssistantWindowFactory.ASSISTANT_HOME_VIEW)
val resourceHandler = FileResourceProvider()
resourceHandler.addResource("/") { StreamResourceHandler("webview", this) }
chatScreen.jbCefClient.addRequestHandler(resourceHandler, chatScreen.cefBrowser)
return chatScreen.component
}

Expand Down Expand Up @@ -120,8 +120,10 @@ class AssistantWindowFactory : ToolWindowFactory, DumbAware {

if (AppSettingsState.instance.apiKey.isNullOrEmpty()) {
addMessage(
"To use Assistant features, please first set the API Key in the Extension Settings." +
"\nYou can create free account [here](https://portal.codemaker.ai/#/register).", Role.Assistant)
"To use Assistant features, please first set the API Key in the Extension Settings." +
"\nYou can create free account [here](https://portal.codemaker.ai/#/register).",
Role.Assistant
)
return
}

Expand Down Expand Up @@ -156,39 +158,12 @@ class AssistantWindowFactory : ToolWindowFactory, DumbAware {
chatScreen.cefBrowser.executeJavaScript("window.appendMessage(\"$content\", ${assistant})", "", 0)
}

private fun assistantView(): String {
return AssistantWindowFactory::class.java
.getResource(AssistantWindowFactory.ASSISTANT_VIEW)!!
.readText()
}

private fun renderMarkdown(source: String): String {
val flavour = CommonMarkFlavourDescriptor()
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(source)
return HtmlGenerator(source, parsedTree, flavour).generateHtml()
}

private fun registerEventHandler() {
chatScreen.cefBrowser.executeJavaScript(
"window.openLink = function(link) { " + jsQuery.inject("link") + "};",
chatScreen.cefBrowser.url, 0)

chatScreen.cefBrowser.executeJavaScript("""
document.addEventListener('click', function(e) {
e.preventDefault();
const link = e.target.closest('a').href;
if (link) {
window.openLink(link);
}
});
""".trimIndent(), chatScreen.cefBrowser.url, 0)

jsQuery.addHandler {
BrowserUtil.browse(it)
return@addHandler null
}
}

inner class MessageTextKeyListener : KeyListener {
override fun keyTyped(e: KeyEvent?) {
}
Expand All @@ -202,11 +177,5 @@ class AssistantWindowFactory : ToolWindowFactory, DumbAware {
override fun keyReleased(e: KeyEvent?) {
}
}

inner class LoadHandler : CefLoadHandlerAdapter() {
override fun onLoadingStateChange(browser: CefBrowser?, isLoading: Boolean, canGoBack: Boolean, canGoForward: Boolean) {
registerEventHandler()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2024 CodeMaker AI Inc. All rights reserved.
*/

package ai.codemaker.jetbrains.window

import com.intellij.openapi.util.io.toNioPath
import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame
import org.cef.callback.CefCallback
import org.cef.handler.*
import org.cef.misc.BoolRef
import org.cef.network.CefRequest
import java.net.URL

private typealias CefResourceProvider = () -> CefResourceHandler?

class FileResourceProvider() : CefRequestHandlerAdapter() {

private val protocol = "file"

private val resources = HashMap<String, CefResourceProvider>()

private val REJECTING_RESOURCE_HANDLER: CefResourceHandler = object : CefResourceHandlerAdapter() {
override fun processRequest(request: CefRequest, callback: CefCallback): Boolean {
callback.cancel()
return false
}
}

private val RESOURCE_REQUEST_HANDLER = object : CefResourceRequestHandlerAdapter() {
override fun getResourceHandler(
browser: CefBrowser?,
frame: CefFrame?,
request: CefRequest
): CefResourceHandler? {
val url = URL(request.url)
if (protocol != url.protocol) {
return REJECTING_RESOURCE_HANDLER
}

return resolveHandler(url)
}
}

fun addResource(path: String, resourceProvider: CefResourceProvider) {
resources[path] = resourceProvider
}

override fun getResourceRequestHandler(
browser: CefBrowser?,
frame: CefFrame?,
request: CefRequest?,
isNavigation: Boolean,
isDownload: Boolean,
requestInitiator: String?,
disableDefaultHandling: BoolRef?
): CefResourceRequestHandler {
return RESOURCE_REQUEST_HANDLER
}

private fun resolveHandler(url: URL): CefResourceHandler? {
var path = url.path.toNioPath()
while (path != null) {
val handler = resources[path.toString()]
if (handler != null) {
return handler()
}
path = path.parent
}
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2023 CodeMaker AI Inc. All rights reserved.
*/

package ai.codemaker.jetbrains.window

import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toNioPath
import io.ktor.util.*
import org.cef.callback.CefCallback
import org.cef.handler.CefResourceHandler
import org.cef.misc.IntRef
import org.cef.misc.StringRef
import org.cef.network.CefRequest
import org.cef.network.CefResponse
import java.io.IOException
import java.io.InputStream
import java.net.URL
import java.nio.file.Path

class StreamResourceHandler(private val resourcePath: String, parent: Disposable) : CefResourceHandler, Disposable {

private var input: InputStream? = null
private var mimeType = "text/html"

init {
Disposer.register(parent, this)
}

override fun processRequest(request: CefRequest?, callback: CefCallback?): Boolean {
val url = URL(request!!.url)
val path = url.path

input = this.javaClass.classLoader.getResourceAsStream(Path.of(resourcePath, path).toString())
if (input == null) {
return false
}

when (path.toNioPath().extension) {
"html" -> mimeType = "text/html";
"svg" -> mimeType = "image/svg+xml";
}

callback!!.Continue()
return true
}

override fun getResponseHeaders(response: CefResponse?, responseLength: IntRef?, redirectUrl: StringRef?) {
responseLength!!.set(input!!.available())
response!!.mimeType = mimeType
response!!.status = 200
}

override fun readResponse(
dataOut: ByteArray?,
bytesToRead: Int,
bytesRead: IntRef?,
callback: CefCallback?
): Boolean {
try {
bytesRead!!.set(input!!.read(dataOut!!, 0, bytesToRead))
if (bytesRead!!.get() != -1) {
return true
}
} catch (e: IOException) {
callback!!.cancel()
}
bytesRead!!.set(0)
Disposer.dispose(this)
return false
}

override fun cancel() {
Disposer.dispose(this)
}

override fun dispose() {
try {
input?.close()
} catch (e: IOException) {
// ignores
}
}
}
25 changes: 25 additions & 0 deletions src/main/resources/webview/assistant.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
animation-delay: -0.16s;
}

img.icon {
width: 12px;
height: 12px;
margin: 4px;
}

@keyframes shine {
0%, 80%, 100% {
transform: scale(0);
Expand Down Expand Up @@ -106,6 +112,25 @@
message.innerHTML = body;
card.appendChild(message);

const controls = document.createElement("div");
controls.classList.add("controls");
card.append(controls);

const upVoteButton = document.createElement('img');
upVoteButton.classList.add('icon');
upVoteButton.src = "media/thumbs-up-off.svg";
controls.append(upVoteButton);

const downVoteButton = document.createElement('img');
downVoteButton.classList.add('icon');
downVoteButton.src = "media/thumbs-down-off.svg";
controls.append(downVoteButton);

const copyButton = document.createElement('img');
copyButton.classList.add('icon');
copyButton.src = "media/copy-off.svg";
controls.append(copyButton);

document.getElementById("chat").appendChild(card);
document.getElementById("anchor").scrollIntoView({behavior: "smooth"});
}
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/webview/media/copy-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/main/resources/webview/media/copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/main/resources/webview/media/thumbs-down-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/main/resources/webview/media/thumbs-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/main/resources/webview/media/thumbs-up-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/main/resources/webview/media/thumbs-up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1e184d4

Please sign in to comment.