diff --git a/CHANGELOG.md b/CHANGELOG.md index bb04e8d3..088edf87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ ## [Unreleased] +## [1.2.21] + +### Improved + +- Code Chat UI + +### Fixed + +- Fixed settings for service port + ## [1.2.19] ### Fixed diff --git a/build.gradle.kts b/build.gradle.kts index c6a8a1d3..5a37fa57 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ repositories { val kotlin_version = "1.9.20" val jetty_version = "11.0.18" val slf4j_version = "2.0.9" -val skyenet_version = "1.0.30" +val skyenet_version = "1.0.31" dependencies { implementation(group = "com.simiacryptus", name = "joe-penai", version = "1.0.28") diff --git a/gradle.properties b/gradle.properties index 6ab76bbf..e926dd2d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ pluginName=intellij-aicoder pluginRepositoryUrl=https://github.com/SimiaCryptus/intellij-aicoder # SemVer format -> https://semver.org -pluginVersion=1.2.20 +pluginVersion=1.2.21 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html #pluginSinceBuild = 203 pluginSinceBuild=231 diff --git a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt index 78ee4a5c..79194ada 100644 --- a/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt +++ b/src/main/kotlin/com/github/simiacryptus/aicoder/config/AppSettingsState.kt @@ -66,34 +66,46 @@ class AppSettingsState : PersistentStateComponent { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other == null || javaClass != other.javaClass) return false - val that = other as AppSettingsState - if (that.temperature.compareTo(temperature) != 0) return false - if (humanLanguage != that.humanLanguage) return false - if (apiBase != that.apiBase) return false - if (apiKey != that.apiKey) return false - if (modelName != that.modelName) return false - if (apiLog != that.apiLog) return false - if (devActions != that.devActions) return false - if (editRequests != that.editRequests) return false - if (editorActions != that.editorActions) return false + if (javaClass != other?.javaClass) return false + + other as AppSettingsState + + if (listeningPort != other.listeningPort) return false + if (listeningEndpoint != other.listeningEndpoint) return false + if (modalTasks != other.modalTasks) return false + if (suppressErrors != other.suppressErrors) return false + if (apiLog != other.apiLog) return false + if (apiBase != other.apiBase) return false + if (apiKey != other.apiKey) return false + if (temperature != other.temperature) return false + if (modelName != other.modelName) return false + if (tokenCounter != other.tokenCounter) return false + if (humanLanguage != other.humanLanguage) return false + if (devActions != other.devActions) return false + if (editRequests != other.editRequests) return false + if (apiThreads != other.apiThreads) return false + return true } override fun hashCode(): Int { - return Objects.hash( - apiBase, - apiKey, - temperature, - modelName, - apiLog, - devActions, - editRequests, - editorActions - ) + var result = listeningPort + result = 31 * result + listeningEndpoint.hashCode() + result = 31 * result + modalTasks.hashCode() + result = 31 * result + suppressErrors.hashCode() + result = 31 * result + apiLog.hashCode() + result = 31 * result + apiBase.hashCode() + result = 31 * result + apiKey.hashCode() + result = 31 * result + temperature.hashCode() + result = 31 * result + modelName.hashCode() + result = 31 * result + tokenCounter + result = 31 * result + humanLanguage.hashCode() + result = 31 * result + devActions.hashCode() + result = 31 * result + editRequests.hashCode() + result = 31 * result + apiThreads + return result } - companion object { @JvmStatic val instance: AppSettingsState by lazy { diff --git a/src/main/resources/codeChat/chat.css b/src/main/resources/codeChat/chat.css index b1e6adaa..380271af 100644 --- a/src/main/resources/codeChat/chat.css +++ b/src/main/resources/codeChat/chat.css @@ -1,42 +1,70 @@ +:root { + --primary-bg-color: #f7f7f7; + --secondary-bg-color: #ffffff; + --primary-text-color: #333; + --secondary-text-color: #555; + --link-color: #0066cc; + --link-hover-color: #0044aa; + --border-radius: 4px; + --box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + --transition-speed: 0.3s; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + color: var(--primary-text-color); + background-color: var(--primary-bg-color); + margin: 0; + padding: 0; + overflow: clip; +} + #messages { - position: absolute; + position: relative; top: 0; left: 0; right: 0; - bottom: 40px; - overflow-y: auto; - padding: 10px; - padding-top: 40px; + bottom: 0; + padding: 10px 10px 10px; flex-grow: 1; flex-shrink: 1; + background-color: var(--secondary-bg-color); + box-shadow: var(--box-shadow); + overflow-y: auto; } -.message { - background-color: #f0f0f0; - border-radius: 5px; +.chat-input { + background-color: var(--secondary-bg-color); + border-radius: var(--border-radius); padding: 10px; margin-bottom: 10px; - overflow: scroll; - z-index: 100; + overflow: auto; + resize: vertical; + flex: 1; + border: 1px solid #ddd; + box-shadow: var(--box-shadow); } -#form { - position: absolute; +#main-input { + align-items: flex-start; left: 0; right: 0; bottom: 0; display: flex; margin: 0; - padding: 5px; - flex-shrink: 0; + padding: 0; + background-color: var(--primary-bg-color); + width: 100%; } -.reply-form { - width: 100%; +#session { + display: flex; + flex-direction: column; + height: 100vh; } -.chat-input { - resize: vertical; +.reply-form { + width: 100%; } .reply-input { @@ -44,8 +72,14 @@ } .href-link { + text-decoration: none; + color: var(--link-color); + transition: color var(--transition-speed); +} + +.href-link:hover { + color: var(--link-hover-color); text-decoration: underline; - color: blue; } #message { @@ -53,6 +87,10 @@ margin-right: 5px; } +.message-container { + position: relative; +} + #disconnected-overlay { display: none; position: fixed; @@ -61,8 +99,7 @@ width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); - z-index: 999; - display: flex; + z-index: 50; justify-content: center; align-items: center; color: white; @@ -73,43 +110,22 @@ font-size: 3rem; line-height: 1.5; margin-bottom: 20px; - animation-name: bounce; - animation-duration: 0.5s; - animation-iteration-count: infinite; - animation-direction: alternate; + animation: bounce var(--transition-speed) infinite alternate; left: 10%; position: relative; color: firebrick; } -@keyframes bounce { - 0% { - transform: translateY(0); - } - 100% { - transform: translateY(-10px); - } -} - .spinner-border { display: block; width: 40px; height: 40px; border: 4px solid rgba(0, 0, 0, 0.1); - border-left-color: #007bff; + border-left-color: var(--link-color); border-radius: 50%; animation: spin 1s linear infinite; } -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - .sr-only { position: absolute; width: 1px; @@ -122,53 +138,43 @@ border: 0; } - -#toolbar { - background-color: #f1f1f1; +#toolbar, #namebar { + background-color: var(--secondary-bg-color); padding: 5px; position: fixed; top: 0; - width: 100%; - horiz-align: left; - z-index: 1; + text-align: left; + box-shadow: var(--box-shadow); } -#toolbar a { - color: #000; - text-decoration: none; - padding: 5px; -} -#toolbar a:hover { - background-color: #ddd; +#toolbar { + width: 100%; + z-index: 2; } #namebar { - background-color: #f1f1f1; - padding: 5px; - position: fixed; - top: 0; + z-index: 3; right: 0; - horiz-align: right; - z-index: 2; } -#namebar a { - color: #000; +#toolbar a, #namebar a { + color: var(--primary-text-color); text-decoration: none; padding: 5px; + transition: background-color var(--transition-speed); } -#namebar a:hover { - background-color: #ddd; +#toolbar a:hover, #namebar a:hover { + background-color: var(--secondary-text-color); + color: var(--secondary-bg-color); } -/* Modal */ .modal { display: none; position: fixed; - z-index: 1000; + z-index: 100; left: 0; top: 0; width: 100%; @@ -178,16 +184,18 @@ } .modal-content { - background-color: #fefefe; + background-color: var(--secondary-bg-color); margin: 15% auto; padding: 20px; border: 1px solid #888; width: 80%; position: relative; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); } .close { - color: #aaa; + color: var(--secondary-text-color); float: right; font-size: 28px; font-weight: bold; @@ -196,83 +204,88 @@ .close:hover, .close:focus { - color: #000; - text-decoration: none; - cursor: pointer; + color: var(--primary-text-color); } pre { - white-space: pre-wrap; /* Since CSS modules do not automatically apply this */ -} - -#container { - display: flex; - flex-direction: column; - height: 100vh; /* Adjust this value based on your preferred container height */ + white-space: pre-wrap; } -.play-button { +.play-button, .cancel-button { font-size: 48px; font-weight: bold; border: none; background: transparent; cursor: pointer; - transition: transform 0.1s; + transition: transform var(--transition-speed); + text-decoration: none; +} + +.cancel-button { + right: 0; + position: absolute; } -.play-button:focus { +.play-button:focus, .cancel-button:focus { outline: none; } -.play-button:active { +.play-button:active, .cancel-button:active { transform: scale(0.9); } - -.regen-button { - font-size: 48px; - font-weight: bold; - color: chartreuse; - border: none; - background: transparent; - cursor: pointer; - transition: transform 0.1s; +.error { + color: red; } -.regen-button:focus { - outline: none; +.verbose { + display: block; } -.regen-button:active { - transform: scale(0.9); +.verbose-hidden { + display: none; } -.message-container { - position: relative; /* Add this line to set the position property of the parent div */ -} -.cancel-button { - position: absolute; - top: 8px; - right: 8px; - font-size: 16px; - font-weight: bold; - border: none; - background: transparent; - cursor: pointer; - transition: transform 0.1s; +@keyframes bounce { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-10px); + } } -.cancel-button:focus { - outline: none; + + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } -.cancel-button:active { - transform: scale(0.9); +.initial-prompt { + background-color: #f2f2f2; + padding: 10px; + border-radius: 5px; + margin-bottom: 10px; + font-family: Arial, sans-serif; + font-size: 16px; + color: #333; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -.collapsible-content { - overflow: hidden; - max-height: 0; - transition: max-height 0.2s ease-out; +.user-message { + background-color: #e6f7ff; + padding: 10px; + border-radius: 5px; + margin-bottom: 10px; + font-family: Arial, sans-serif; + font-size: 16px; + color: #333; + font-weight: bold; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } diff --git a/src/main/resources/codeChat/chat.js b/src/main/resources/codeChat/chat.js index f946c020..f4d1571a 100644 --- a/src/main/resources/codeChat/chat.js +++ b/src/main/resources/codeChat/chat.js @@ -22,6 +22,9 @@ function getSessionId() { function send(message) { console.log('Sending message:', message); + if (socket.readyState !== 1) { + throw new Error('WebSocket is not open'); + } socket.send(message); } @@ -60,6 +63,8 @@ function connect(sessionId, customReceiveFunction) { } function showDisconnectedOverlay(show) { - const overlay = document.getElementById('disconnected-overlay'); - overlay.style.display = show ? 'block' : 'none'; + const elements = document.getElementsByClassName('ws-control'); + for (let i = 0; i < elements.length; i++) { + elements[i].disabled = show; + } } diff --git a/src/main/resources/codeChat/index.html b/src/main/resources/codeChat/index.html index 79979eb8..45e85851 100644 --- a/src/main/resources/codeChat/index.html +++ b/src/main/resources/codeChat/index.html @@ -13,6 +13,14 @@ +
+
+ + +
+
+
+ -
-
-
- - -
-
-

Disconnected. Attempting to reconnect...

diff --git a/src/main/resources/codeChat/main.js b/src/main/resources/codeChat/main.js index 2d899aa1..06080e86 100644 --- a/src/main/resources/codeChat/main.js +++ b/src/main/resources/codeChat/main.js @@ -69,8 +69,8 @@ document.addEventListener('DOMContentLoaded', () => { } }); - const form = document.getElementById('form'); - const messageInput = document.getElementById('message'); + const form = document.getElementById('main-input'); + const messageInput = document.getElementById('chat-input'); form.addEventListener('submit', (event) => { event.preventDefault(); diff --git a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.groovy b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.groovy index ded1b1c6..313275cb 100644 --- a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.groovy +++ b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AnalogueFileAction.groovy @@ -13,7 +13,7 @@ import org.apache.commons.io.IOUtils import javax.swing.* import java.nio.file.Path -class AnalogueFileAction extends FileContextAction { +class AnalogueFileAction extends FileContextAction { private static class ProjectFile { public String path = "" @@ -91,7 +91,7 @@ class AnalogueFileAction extends FileContextAction Paths should be relative to the project root and should not exist. Output the file path using the a line with the format "File: ". Output the file code directly after the header line with no additional decoration. - """.stripIndent() + """.stripIndent(), null ), new ChatMessage( ChatMessage.Role.user, """ @@ -102,7 +102,7 @@ class AnalogueFileAction extends FileContextAction ``` ${baseFile.code} ``` - """.stripIndent() + """.stripIndent(), null ) ] String response = api.chat( diff --git a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AppendAction.groovy b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AppendAction.groovy index 33522b85..8d7c1fa1 100644 --- a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AppendAction.groovy +++ b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/AppendAction.groovy @@ -20,11 +20,11 @@ class AppendAction extends SelectionAction { request.messages = [ new OpenAIClient.ChatMessage( OpenAIClient.ChatMessage.Role.system, - "Append text to the end of the user's prompt" + "Append text to the end of the user's prompt", null ), new OpenAIClient.ChatMessage( OpenAIClient.ChatMessage.Role.user, - state.selectedText.toString() + state.selectedText.toString(), null ) ] def chatResponse = api.chat(request, AppSettingsState.instance.defaultChatModel()) diff --git a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/CreateFileAction.groovy b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/CreateFileAction.groovy index 0f7d37ba..e7013b05 100644 --- a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/CreateFileAction.groovy +++ b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/CreateFileAction.groovy @@ -10,7 +10,7 @@ import com.simiacryptus.openai.OpenAIClient.ChatRequest import javax.swing.* -class CreateFileAction extends FileContextAction { +class CreateFileAction extends FileContextAction { CreateFileAction() { super(false, true) @@ -94,7 +94,7 @@ class CreateFileAction extends FileContextAction { Paths should be relative to the project root and should not exist. Output the file path using the a line with the format "File: ". Output the file code directly after the header line with no additional decoration. - """.stripIndent() + """.stripIndent(), null ), //language=TEXT new ChatMessage( @@ -102,7 +102,7 @@ class CreateFileAction extends FileContextAction { Create a new file based on the following directive: $directive The file location should be based on the selected path `${basePath}` - """.stripIndent() + """.stripIndent(), null ) ] def response = api.chat( @@ -119,7 +119,7 @@ class CreateFileAction extends FileContextAction { def pathPattern = ~"""File(?:name)?: ['`"]?([^'`"]+)['`"]?""" if (header =~ pathPattern) { def match = (header =~ pathPattern)[0] - outputPath = match[1].trim() + outputPath = match[1].toString() } return new ProjectFile( path: outputPath, diff --git a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/GenerateStoryAction.groovy b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/GenerateStoryAction.groovy index 6950f56f..78f77b98 100644 --- a/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/GenerateStoryAction.groovy +++ b/src/main/resources/sources/groovy/com/github/simiacryptus/aicoder/actions/generic/GenerateStoryAction.groovy @@ -11,7 +11,7 @@ import org.apache.commons.io.FileUtils import javax.swing.* -class GenerateStoryAction extends FileContextAction { +class GenerateStoryAction extends FileContextAction { GenerateStoryAction() { super(false, true) @@ -239,7 +239,7 @@ class GenerateStoryAction extends FileContextAction