diff --git a/gradle.properties b/gradle.properties index eb5ccd87..72fd3df2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ # Gradle Releases -> https://github.com/gradle/gradle/releases libraryGroup = com.simiacryptus.skyenet -libraryVersion = 1.0.80 +libraryVersion = 1.0.81 gradleVersion = 7.6.1 diff --git a/webui/build.gradle.kts b/webui/build.gradle.kts index 760cb88f..083bb6ff 100644 --- a/webui/build.gradle.kts +++ b/webui/build.gradle.kts @@ -44,6 +44,10 @@ dependencies { compileOnly(group = "software.amazon.awssdk", name = "aws-sdk-java", version = "2.21.9") compileOnly("org.jsoup:jsoup:1.17.2") + + implementation("com.google.zxing:core:3.5.3") + implementation("com.google.zxing:javase:3.5.3") + implementation("org.openapitools:openapi-generator:7.3.0") implementation("org.openapitools:openapi-generator-cli:7.3.0") diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt index c531aeec..7ff76c44 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyDiffLinks.kt @@ -14,47 +14,70 @@ fun SocketManagerBase.addApplyDiffLinks( task: SessionTask, ui: ApplicationInterface, ): String { + + + val patch = { code: String, diff: String -> + val isCurlyBalanced = FileValidationUtils.isCurlyBalanced(code) + val isSquareBalanced = FileValidationUtils.isSquareBalanced(code) + val isParenthesisBalanced = FileValidationUtils.isParenthesisBalanced(code) + val isQuoteBalanced = FileValidationUtils.isQuoteBalanced(code) + val isSingleQuoteBalanced = FileValidationUtils.isSingleQuoteBalanced(code) + var newCode = IterativePatchUtil.patch(code, diff) + newCode = newCode.replace("\r", "") + val isCurlyBalancedNew = FileValidationUtils.isCurlyBalanced(newCode) + val isSquareBalancedNew = FileValidationUtils.isSquareBalanced(newCode) + val isParenthesisBalancedNew = FileValidationUtils.isParenthesisBalanced(newCode) + val isQuoteBalancedNew = FileValidationUtils.isQuoteBalanced(newCode) + val isSingleQuoteBalancedNew = FileValidationUtils.isSingleQuoteBalanced(newCode) + val isError = ((isCurlyBalanced && !isCurlyBalancedNew) || + (isSquareBalanced && !isSquareBalancedNew) || + (isParenthesisBalanced && !isParenthesisBalancedNew) || + (isQuoteBalanced && !isQuoteBalancedNew) || + (isSingleQuoteBalanced && !isSingleQuoteBalancedNew)) + PatchResult(newCode, !isError) + } + val diffPattern = """(?s)(? val diffVal: String = diffBlock.groupValues[1] - val applydiffTask = ui.newTask(false) + val buttons = ui.newTask(false) lateinit var hrefLink: StringBuilder var reverseHrefLink: StringBuilder? = null - hrefLink = applydiffTask.complete(hrefLink("Apply Diff", classname = "href-link cmd-button") { + hrefLink = buttons.complete(hrefLink("Apply Diff", classname = "href-link cmd-button") { try { - val newCode = IterativePatchUtil.patch(code(), diffVal).replace("\r", "") - handle(newCode) + val newCode = patch(code(), diffVal) + handle(newCode.newCode) hrefLink.set("""
Diff Applied
""") - applydiffTask.complete() + buttons.complete() reverseHrefLink?.clear() } catch (e: Throwable) { hrefLink.append("""
Error: ${e.message}
""") - applydiffTask.complete() + buttons.complete() task.error(ui, e) } })!! - val patch = IterativePatchUtil.patch(code(), diffVal).replace("\r", "") + val patch = patch(code(), diffVal).newCode val test1 = DiffUtil.formatDiff( DiffUtil.generateDiff( code().replace("\r", "").lines(), patch.lines() ) ) - val patchRev = IterativePatchUtil.patch( + val patchRev = patch( code().lines().reversed().joinToString("\n"), diffVal.lines().reversed().joinToString("\n") - ).replace("\r", "") + ).newCode if (patchRev != patch) { - reverseHrefLink = applydiffTask.complete(hrefLink("(Bottom to Top)", classname = "href-link cmd-button") { + reverseHrefLink = buttons.complete(hrefLink("(Bottom to Top)", classname = "href-link cmd-button") { try { val reversedCode = code().lines().reversed().joinToString("\n") val reversedDiff = diffVal.lines().reversed().joinToString("\n") - val newReversedCode = IterativePatchUtil.patch(reversedCode, reversedDiff).replace("\r", "") + val newReversedCode = patch(reversedCode, reversedDiff).newCode val newCode = newReversedCode.lines().reversed().joinToString("\n") handle(newCode) reverseHrefLink!!.set("""
Diff Applied (Bottom to Top)
""") - applydiffTask.complete() + buttons.complete() hrefLink.clear() } catch (e: Throwable) { task.error(ui, e) @@ -73,7 +96,7 @@ fun SocketManagerBase.addApplyDiffLinks( "Diff" to renderMarkdown("```diff\n$diffVal\n```", ui = ui, tabs = true), "Verify" to renderMarkdown("```diff\n$test1\n```", ui = ui, tabs = true), ), ui = ui, split = true - ) + "\n" + applydiffTask.placeholder + ) + "\n" + buttons.placeholder } else { displayMapInTabs( mapOf( @@ -81,7 +104,7 @@ fun SocketManagerBase.addApplyDiffLinks( "Verify" to renderMarkdown("```diff\n$test1\n```", ui = ui, tabs = true), "Reverse" to renderMarkdown("```diff\n$test2\n```", ui = ui, tabs = true), ), ui = ui, split = true - ) + "\n" + applydiffTask.placeholder + ) + "\n" + buttons.placeholder } markdown.replace(diffBlock.value, newValue) } diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt index 2cda1f5e..4d55217f 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/AddApplyFileDiffLinks.kt @@ -1,34 +1,63 @@ package com.simiacryptus.diff -import com.simiacryptus.diff.IterativePatchUtil.patch +import com.simiacryptus.jopenai.API +import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.models.ChatModels import com.simiacryptus.skyenet.AgentPatterns +import com.simiacryptus.skyenet.AgentPatterns.displayMapInTabs +import com.simiacryptus.skyenet.core.actors.SimpleActor import com.simiacryptus.skyenet.set import com.simiacryptus.skyenet.webui.application.ApplicationInterface -import com.simiacryptus.skyenet.webui.session.SessionTask import com.simiacryptus.skyenet.webui.session.SocketManagerBase -import com.simiacryptus.skyenet.webui.util.MarkdownUtil import com.simiacryptus.skyenet.webui.util.MarkdownUtil.renderMarkdown +import org.apache.commons.codec.digest.Md5Crypt import java.io.File +import java.nio.file.Files import java.nio.file.Path import java.util.concurrent.TimeUnit +import java.util.regex.Pattern import kotlin.io.path.readText +private fun String.reverseLines(): String = lines().reversed().joinToString("\n") + fun SocketManagerBase.addApplyFileDiffLinks( root: Path, - code: () -> Map, response: String, handle: (Map) -> Unit, ui: ApplicationInterface, + api: API, ): String { - val headerPattern = """(?s)(?> = - diffPattern.findAll(response).map { it.range to it.groupValues[1] }.toList() - val codeblocks = codeblockPattern.findAll(response).filter { - when (it.groupValues[1]) { - "diff" -> false + val findAll = codeblockPattern.findAll(response).toList() + val diffs: List> = findAll.filter { block -> + val header = headers.lastOrNull { it.first.endInclusive < block.range.start } + val filename = resolve(root, header?.second ?: "Unknown") + when { + !root.toFile().resolve(filename).exists() -> false + //block.groupValues[1] == "diff" -> true + else -> true + } + }.map { it.range to it.groupValues[2] }.toList() + + val codeblocks = findAll.filter { block -> + val header = headers.lastOrNull { it.first.endInclusive < block.range.start } + val filename = resolve(root, header?.second ?: "Unknown") + when { + root.toFile().resolve(filename).exists() -> false + block.groupValues[1] == "diff" -> false else -> true } }.map { it.range to it }.toList() @@ -36,8 +65,9 @@ fun SocketManagerBase.addApplyFileDiffLinks( val header = headers.lastOrNull { it.first.endInclusive < diffBlock.first.start } val filename = resolve(root, header?.second ?: "Unknown") val diffVal = diffBlock.second - val newValue = renderDiffBlock(root, filename, diffVal, handle, ui) - markdown.replace("```diff\n$diffVal\n```", newValue) + val newValue = renderDiffBlock(root, filename, diffVal, handle, ui, api) + val regex = "(?s)```[^\n]*\n?${Pattern.quote(diffVal)}\n?```".toRegex() + markdown.replace(regex, newValue) } val withSaveLinks = codeblocks.fold(withPatchLinks) { markdown, codeBlock -> val header = headers.lastOrNull { it.first.endInclusive < codeBlock.first.start } @@ -67,7 +97,7 @@ fun SocketManagerBase.addApplyFileDiffLinks( |``` """.trimMargin() markdown.replace( - codeblockRaw, AgentPatterns.displayMapInTabs( + codeblockRaw, displayMapInTabs( mapOf( "New" to renderMarkdown(codeblockRaw, ui = ui), "Old" to renderMarkdown( @@ -106,12 +136,12 @@ fun resolve(root: Path, filename: String): String { filename.trim() } var filepath = path(root, filename) - if (filepath?.toFile()?.exists() == false) filepath = null - if (null != filepath) return filepath.toString() + if (filepath?.toFile()?.exists() == false) filepath = null // reset if file not found + if (null != filepath) return filepath.let { root.relativize(it).toString() }.toString() // return if file found + + // if file not found, search for file in the root directory val files = root.toFile().recurseFiles().filter { it.name == filename.split('/', '\\').last() } - if (files.size == 1) { - filepath = files.first().toPath() - } + if (files.size == 1) filepath = files.first().toPath() // if only one file found, return it return filepath?.let { root.relativize(it).toString() } ?: filename } @@ -133,7 +163,8 @@ private fun SocketManagerBase.renderDiffBlock( filename: String, diffVal: String, handle: (Map) -> Unit, - ui: ApplicationInterface + ui: ApplicationInterface, + api: API? ): String { val diffTask = ui.newTask(root = false) @@ -143,63 +174,79 @@ private fun SocketManagerBase.renderDiffBlock( val newCodeTaskSB = newCodeTask.add("") val patchTask = ui.newTask(root = false) val patchTaskSB = patchTask.add("") + val fixTask = ui.newTask(root = false) val prevCode2Task = ui.newTask(root = false) val prevCode2TaskSB = prevCode2Task.add("") val newCode2Task = ui.newTask(root = false) val newCode2TaskSB = newCode2Task.add("") val patch2Task = ui.newTask(root = false) val patch2TaskSB = patch2Task.add("") - val verifyFwdTabs = AgentPatterns.displayMapInTabs( + + + + + + val filepath = path(root, filename) + val prevCode = load(filepath) + val relativize = try { + root.relativize(filepath) + } catch (e: Throwable) { + filepath + } + var filehash: String = "" + + val applydiffTask = ui.newTask(false) + lateinit var hrefLink: StringBuilder + var isApplied = false + + var originalCode = load(filepath) + lateinit var revert: String + var newCode = patch(originalCode, diffVal) + + + + val verifyFwdTabs = if(!newCode.isValid) displayMapInTabs( + mapOf( + "Code" to (prevCodeTask?.placeholder ?: ""), + "Preview" to (newCodeTask?.placeholder ?: ""), + "Echo" to (patchTask?.placeholder ?: ""), + "Fix" to (fixTask?.placeholder ?: ""), + ) + ) else displayMapInTabs( mapOf( "Code" to (prevCodeTask?.placeholder ?: ""), "Preview" to (newCodeTask?.placeholder ?: ""), "Echo" to (patchTask?.placeholder ?: ""), ) ) - val verifyRevTabs = AgentPatterns.displayMapInTabs( + val verifyRevTabs = displayMapInTabs( mapOf( "Code" to (prevCode2Task?.placeholder ?: ""), "Preview" to (newCode2Task?.placeholder ?: ""), "Echo" to (patch2Task?.placeholder ?: ""), ) ) - val verifyTabs = AgentPatterns.displayMapInTabs( + val verifyTabs = displayMapInTabs( mapOf( "Forward" to verifyFwdTabs, "Reverse" to verifyRevTabs, ) ) - val mainTabs = AgentPatterns.displayMapInTabs( + val mainTabs = displayMapInTabs( mapOf( "Diff" to (diffTask?.placeholder ?: ""), "Verify" to verifyTabs, ) ) - diffTask?.complete(renderMarkdown("```diff\n$diffVal\n```", ui = ui)) - - val filepath = path(root, filename) - val relativize = try { - root.relativize(filepath) - } catch (e: Throwable) { - filepath - } - var filehash: Int? = 0 - - val applydiffTask = ui.newTask(false) - lateinit var hrefLink: StringBuilder - var isApplied = false - - var originalCode = load(filepath) - lateinit var revert: String - val apply1 = hrefLink("Apply Diff", classname = "href-link cmd-button") { + var apply1 = hrefLink("Apply Diff", classname = "href-link cmd-button") { try { originalCode = load(filepath) - val newCode = patch(originalCode, diffVal) - filepath?.toFile()?.writeText(newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") - handle(mapOf(relativize!! to newCode)) + newCode = patch(originalCode, diffVal) + filepath?.toFile()?.writeText(newCode.newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") + handle(mapOf(relativize!! to newCode.newCode)) hrefLink.set("""
Diff Applied
""" + revert) applydiffTask.complete() isApplied = true @@ -208,13 +255,102 @@ private fun SocketManagerBase.renderDiffBlock( applydiffTask.error(null, e) } } + if(!newCode.isValid) { + val fixPatchLink = hrefLink("Fix Patch", classname = "href-link cmd-button") { + try { + val header = fixTask.header("Attempting to fix patch...") + + val patchFixer = SimpleActor( + prompt = """ + |You are a helpful AI that helps people with coding. + | + |Response should use one or more code patches in diff format within ```diff code blocks. + |Each diff should be preceded by a header that identifies the file being modified. + |The diff format should use + for line additions, - for line deletions. + |The diff should include 2 lines of context before and after every change. + | + |Example: + | + |Here are the patches: + | + |### src/utils/exampleUtils.js + |```diff + | // Utility functions for example feature + | const b = 2; + | function exampleFunction() { + |- return b + 1; + |+ return b + 2; + | } + |``` + | + |### tests/exampleUtils.test.js + |```diff + | // Unit tests for exampleUtils + | const assert = require('assert'); + | const { exampleFunction } = require('../src/utils/exampleUtils'); + | + | describe('exampleFunction', () => { + |- it('should return 3', () => { + |+ it('should return 4', () => { + | assert.equal(exampleFunction(), 3); + | }); + | }); + |``` + """.trimMargin(), + model = ChatModels.GPT4o, + temperature = 0.3 + ) + + val echoDiff = try { + DiffUtil.formatDiff( + DiffUtil.generateDiff( + prevCode.lines(), + newCode.newCode.lines() + ) + ) + } catch (e: Throwable) { + renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) + } + + var answer = patchFixer.answer( + listOf( + """ + |Code: + |```${filename.split('.').lastOrNull() ?: ""} + |$prevCode + |``` + | + |Patch: + |```diff + |$diffVal + |``` + | + |Effective Patch: + |```diff + | $echoDiff + |``` + | + |Please provide a fix for the diff above in the form of a diff patch. + """.trimMargin() + ), api as OpenAIClient + ) + answer = ui.socketManager?.addApplyFileDiffLinks(root, answer, handle, ui, api) ?: answer + header?.clear() + fixTask.complete(answer) + } catch (e: Throwable) { + log.error("Error in fix patch", e) + } + } + //apply1 += fixPatchLink + fixTask.complete(fixPatchLink) + } val apply2 = hrefLink("(Bottom to Top)", classname = "href-link cmd-button") { try { originalCode = load(filepath) - val newCode2 = patch( - originalCode.lines().reversed().joinToString("\n"), - diffVal.lines().reversed().joinToString("\n") - ).lines().reversed().joinToString("\n") + val originalLines = originalCode.reverseLines() + val diffLines = diffVal.reverseLines() + val patch1 = patch(originalLines, diffLines) + val newCode2 = patch1.newCode.reverseLines() filepath?.toFile()?.writeText(newCode2, Charsets.UTF_8) ?: log.warn("File not found: $filepath") handle(mapOf(relativize!! to newCode2)) hrefLink.set("""
Diff Applied (Bottom to Top)
""" + revert) @@ -241,122 +377,118 @@ private fun SocketManagerBase.renderDiffBlock( hrefLink = applydiffTask.complete(apply1 + "\n" + apply2)!! lateinit var scheduledFn: () -> Unit + var lastModifiedTime: Long = -1 scheduledFn = { - val thisFilehash = try { - filepath?.toFile()?.readText().hashCode() - } catch (e: Throwable) { - null - } - if (!isApplied) { - if (thisFilehash != filehash) { - updateVerification( - filepath, - diffVal, - ui, - newCodeTaskSB, - filename, - newCodeTask, - prevCodeTaskSB, - prevCodeTask, - patchTaskSB, - patchTask, - newCode2TaskSB, - newCode2Task, - prevCode2TaskSB, - prevCode2Task, - patch2TaskSB, + try { + val currentModifiedTime = Files.getLastModifiedTime(filepath).toMillis() + if (currentModifiedTime != lastModifiedTime) { + lastModifiedTime = currentModifiedTime + val thisFilehash = Md5Crypt.md5Crypt(filepath?.toFile()?.readText()?.toByteArray()) + if (!isApplied && thisFilehash != filehash) { + val newCode = patch(prevCode, diffVal) + val echoDiff = try { + DiffUtil.formatDiff( + DiffUtil.generateDiff( + prevCode.lines(), + newCode.newCode.lines() + ) + ) + } catch (e: Throwable) { + renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) + } + newCodeTaskSB?.set( + renderMarkdown( + "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${newCode}\n```", + ui = ui, tabs = false + ) + ) + newCodeTask.complete("") + prevCodeTaskSB?.set( + renderMarkdown( + "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${prevCode}\n```", + ui = ui, tabs = false + ) + ) + prevCodeTask.complete("") + patchTaskSB?.set( + renderMarkdown( + "# $filename\n\n```diff\n ${echoDiff}\n```", + ui = ui, + tabs = false + ) + ) + patchTask.complete("") + val newCode2 = patch( + load(filepath).reverseLines(), + diffVal.reverseLines() + ).newCode.lines().reversed().joinToString("\n") + val echoDiff2 = try { + DiffUtil.formatDiff( + DiffUtil.generateDiff( + prevCode.lines(), + newCode2.lines(), + ) + ) + } catch (e: Throwable) { + renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) + } + newCode2TaskSB?.set( + renderMarkdown( + "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${newCode2}\n```", + ui = ui, tabs = false + ) + ) + newCode2Task.complete("") + prevCode2TaskSB?.set( + renderMarkdown( + "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${prevCode}\n```", + ui = ui, tabs = false + ) + ) + prevCode2Task.complete("") + patch2TaskSB?.set( + renderMarkdown( + "# $filename\n\n```diff\n ${echoDiff2}\n```", + ui = ui, + tabs = false + ) + ) patch2Task - ) - filehash = thisFilehash + .complete("") + filehash = thisFilehash + } + } + if (!isApplied) { scheduledThreadPoolExecutor.schedule(scheduledFn, 1000, TimeUnit.MILLISECONDS) } + } catch (e: Throwable) { + log.error("Error in scheduled function", e) } } - scheduledThreadPoolExecutor.schedule(scheduledFn, 100, TimeUnit.MILLISECONDS) + scheduledThreadPoolExecutor.schedule(scheduledFn, 1000, TimeUnit.MILLISECONDS) val newValue = mainTabs + "\n" + applydiffTask.placeholder return newValue } -private fun updateVerification( - filepath: Path?, - diffVal: String, - ui: ApplicationInterface, - newCodeTaskSB: StringBuilder?, - filename: String, - newCodeTask: SessionTask, - prevCodeTaskSB: StringBuilder?, - prevCodeTask: SessionTask, - patchTaskSB: StringBuilder?, - patchTask: SessionTask, - newCode2TaskSB: StringBuilder?, - newCode2Task: SessionTask, - prevCode2TaskSB: StringBuilder?, - prevCode2Task: SessionTask, - patch2TaskSB: StringBuilder?, - patch2Task: SessionTask -) { - val prevCode = load(filepath) - val newCode = patch(prevCode, diffVal) - val echoDiff = try { - DiffUtil.formatDiff( - DiffUtil.generateDiff( - prevCode.lines(), - newCode.lines() - ) - ) - } catch (e: Throwable) { - renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) - } - - newCodeTaskSB?.set( - renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${newCode}\n```", - ui = ui, tabs = false - ) - ) - newCodeTask.complete("") - prevCodeTaskSB?.set( - renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${prevCode}\n```", - ui = ui, tabs = false - ) - ) - prevCodeTask.complete("") - patchTaskSB?.set(renderMarkdown("# $filename\n\n```diff\n ${echoDiff}\n```", ui = ui, tabs = false)) - patchTask.complete("") - - - val newCode2 = patch( - load(filepath).lines().reversed().joinToString("\n"), - diffVal.lines().reversed().joinToString("\n") - ).lines().reversed().joinToString("\n") - val echoDiff2 = try { - DiffUtil.formatDiff( - DiffUtil.generateDiff( - prevCode.lines(), - newCode2.lines(), - ) - ) - } catch (e: Throwable) { - renderMarkdown("```\n${e.stackTraceToString()}\n```", ui = ui) - } - - newCode2TaskSB?.set( - renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${newCode2}\n```", - ui = ui, tabs = false - ) - ) - newCode2Task.complete("") - prevCode2TaskSB?.set( - renderMarkdown( - "# $filename\n\n```${filename.split('.').lastOrNull() ?: ""}\n${prevCode}\n```", - ui = ui, tabs = false - ) - ) - prevCode2Task.complete("") - patch2TaskSB?.set(renderMarkdown("# $filename\n\n```diff\n ${echoDiff2}\n```", ui = ui, tabs = false)) - patch2Task.complete("") +private val patch = { code: String, diff: String -> + val isCurlyBalanced = FileValidationUtils.isCurlyBalanced(code) + val isSquareBalanced = FileValidationUtils.isSquareBalanced(code) + val isParenthesisBalanced = FileValidationUtils.isParenthesisBalanced(code) + val isQuoteBalanced = FileValidationUtils.isQuoteBalanced(code) + val isSingleQuoteBalanced = FileValidationUtils.isSingleQuoteBalanced(code) + var newCode = IterativePatchUtil.patch(code, diff) + newCode = newCode.replace("\r", "") + val isCurlyBalancedNew = FileValidationUtils.isCurlyBalanced(newCode) + val isSquareBalancedNew = FileValidationUtils.isSquareBalanced(newCode) + val isParenthesisBalancedNew = FileValidationUtils.isParenthesisBalanced(newCode) + val isQuoteBalancedNew = FileValidationUtils.isQuoteBalanced(newCode) + val isSingleQuoteBalancedNew = FileValidationUtils.isSingleQuoteBalanced(newCode) + val isError = ((isCurlyBalanced && !isCurlyBalancedNew) || + (isSquareBalanced && !isSquareBalancedNew) || + (isParenthesisBalanced && !isParenthesisBalancedNew) || + (isQuoteBalanced && !isQuoteBalancedNew) || + (isSingleQuoteBalanced && !isSingleQuoteBalancedNew)) + PatchResult(newCode, !isError) } @@ -403,12 +535,14 @@ private fun path(root: Path, filename: String): Path? { fun findFile(root: Path, filename: String): Path? { return try { when { + root.resolve(filename).toFile().exists() -> root.resolve(filename) + /* filename is absolute */ filename.startsWith("/") -> { val resolve = File(filename) if (resolve.exists()) resolve.toPath() else findFile(root, filename.removePrefix("/")) } - /* win absolute */ + /* windows absolute */ filename.indexOf(":\\") == 1 -> { val resolve = File(filename) if (resolve.exists()) resolve.toPath() else findFile( @@ -417,7 +551,6 @@ fun findFile(root: Path, filename: String): Path? { ) } - root.resolve(filename).toFile().exists() -> root.resolve(filename) null != root.parent && root != root.parent -> findFile(root.parent, filename) else -> null } @@ -452,7 +585,7 @@ fun applyFileDiffs( try { val originalCode = filepath.readText(Charsets.UTF_8) val newCode = patch(originalCode, diffVal) - filepath?.toFile()?.writeText(newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") + filepath?.toFile()?.writeText(newCode.newCode, Charsets.UTF_8) ?: log.warn("File not found: $filepath") } catch (e: Throwable) { log.warn("Error", e) } diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/FileValidationUtils.kt b/webui/src/main/kotlin/com/simiacryptus/diff/FileValidationUtils.kt new file mode 100644 index 00000000..f79333cd --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/diff/FileValidationUtils.kt @@ -0,0 +1,68 @@ +package com.simiacryptus.diff + +class FileValidationUtils { + companion object { + fun isCurlyBalanced(code: String): Boolean { + var count = 0 + for (char in code) { + when (char) { + '{' -> count++ + '}' -> count-- + } + if (count < 0) return false + } + return count == 0 + } + + fun isSingleQuoteBalanced(code: String): Boolean { + var count = 0 + var escaped = false + for (char in code) { + when { + char == '\\' -> escaped = !escaped + char == '\'' && !escaped -> count++ + else -> escaped = false + } + } + return count % 2 == 0 + } + + fun isSquareBalanced(code: String): Boolean { + var count = 0 + for (char in code) { + when (char) { + '[' -> count++ + ']' -> count-- + } + if (count < 0) return false + } + return count == 0 + } + + fun isParenthesisBalanced(code: String): Boolean { + var count = 0 + for (char in code) { + when (char) { + '(' -> count++ + ')' -> count-- + } + if (count < 0) return false + } + return count == 0 + } + + fun isQuoteBalanced(code: String): Boolean { + var count = 0 + var escaped = false + for (char in code) { + when { + char == '\\' -> escaped = !escaped + char == '"' && !escaped -> count++ + else -> escaped = false + } + } + return count % 2 == 0 + } + } + +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/PatchResult.kt b/webui/src/main/kotlin/com/simiacryptus/diff/PatchResult.kt new file mode 100644 index 00000000..37502e2d --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/diff/PatchResult.kt @@ -0,0 +1,6 @@ +package com.simiacryptus.diff + +data class PatchResult( + val newCode: String, + val isValid: Boolean, +) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt index b7ec96c6..f2e11272 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/Discussable.kt @@ -37,7 +37,7 @@ ${ val idx: Int = size this.set(label(idx), newTask.placeholder) main(idx, newTask) - this.selectedTab = idx + //this.selectedTab = idx header?.clear() newTask.complete() } @@ -176,7 +176,7 @@ ${ return } try { - if (null != tabIndex) tabs.selectedTab = tabIndex + //if (null != tabIndex) tabs.selectedTab = tabIndex tabContent.apply { val prevTab = toString() set(prevTab) @@ -201,7 +201,7 @@ ${ val header = newTask.header("Processing...") tabs[tabs.label(idx)] = newTask.placeholder main(idx, newTask) - tabs.selectedTab = idx + //tabs.selectedTab = idx header?.clear() newTask.complete() semaphore.acquire() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt index 937aef65..d06f1625 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/TabbedDisplay.kt @@ -18,7 +18,7 @@ open class TabbedDisplay(
${renderTabButtons()} ${ - tabs.withIndex().joinToString("\n") + tabs.toTypedArray().withIndex().joinToString("\n") { (idx, t) -> renderContentTab(t, idx) } }
@@ -31,7 +31,7 @@ open class TabbedDisplay( open fun renderTabButtons() = """
${ - tabs.withIndex().joinToString("\n") { (idx, pair) -> + tabs.toTypedArray().withIndex().joinToString("\n") { (idx, pair) -> if (idx == selectedTab) { """""" } else { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt index eb630f2b..d2caabae 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/WebDevApp.kt @@ -367,20 +367,14 @@ class WebDevAgent( renderMarkdown( ui.socketManager!!.addApplyFileDiffLinks( root = root.toPath(), - code = { - codeFiles.filter { - if (it.name.lowercase().endsWith(".png")) return@filter false - if (it.name.lowercase().endsWith(".jpg")) return@filter false - true - }.map { it to root.resolve(it.toFile()).readText() }.toMap() - }, response = code, handle = { newCodeMap -> newCodeMap.forEach { (path, newCode) -> task.complete("$path Updated") } }, - ui = ui + ui = ui, + api = api ) ) }, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt index 0e76cfc6..2bd120fe 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/webui/servlet/SessionShareServlet.kt @@ -22,11 +22,26 @@ import org.apache.http.impl.client.HttpClients import java.net.URI import kotlin.reflect.jvm.javaType import kotlin.reflect.typeOf +import com.google.zxing.BarcodeFormat +import com.google.zxing.qrcode.QRCodeWriter +import com.google.zxing.client.j2se.MatrixToImageWriter +import java.io.ByteArrayOutputStream +import java.util.Base64 class SessionShareServlet( private val server: ApplicationServer, ) : HttpServlet() { + private fun generateQRCodeDataURL(url: String): String { + val qrCodeWriter = QRCodeWriter() + val bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, 200, 200) + val outputStream = ByteArrayOutputStream() + MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream) + val imageBytes = outputStream.toByteArray() + val base64Image = Base64.getEncoder().encodeToString(imageBytes) + return "data:image/png;base64,$base64Image" + } + override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { val user = authenticationManager.getUser(req.getCookie()) @@ -49,31 +64,38 @@ class SessionShareServlet( val session = StorageInterface.parseSessionID(sessionID) val pool = ApplicationServices.clientManager.getPool(session, user) val infoFile = storageInterface.getDataDir(user, session).resolve("info.json").apply { parentFile.mkdirs() } - val json = if(infoFile.exists()) JsonUtil.fromJson>(infoFile.readText(), typeOf>().javaType) else mapOf() + val json = if (infoFile.exists()) JsonUtil.fromJson>( + infoFile.readText(), + typeOf>().javaType + ) else mapOf() val sessionSettings = (json as? Map)?.toMutableMap() ?: mutableMapOf() val previousShare = sessionSettings["shareId"] + var shareURL = url(appName, previousShare ?: long64()) + var qrCodeDataURL = generateQRCodeDataURL(shareURL) when { - null != previousShare && validateUrl(url(appName, previousShare)) -> { + null != previousShare && validateUrl(shareURL) -> { log.info("Reusing shareId: $previousShare") resp.contentType = "text/html" resp.status = HttpServletResponse.SC_OK //language=HTML resp.writer.write( - """ - | - | - | Save Session - | - | - | - | - |

Sharing URL

- |

${url(appName, previousShare)}

- | - | - """.trimMargin() + """ + | + | Save Session + | + | + | + | + |

Sharing URL

+ |

shareURL

+ | + |

Sharing URL

+ |

$shareURL

+ | QR Code for $shareURL + | + |""".trimMargin() ) } @@ -110,22 +132,25 @@ class SessionShareServlet( resp.contentType = "text/html" resp.status = HttpServletResponse.SC_OK //language=HTML + shareURL = url(appName, shareId) + qrCodeDataURL = generateQRCodeDataURL(shareURL) resp.writer.write( """ - | - | - | Saving Session - | - | - | - | - |

Saving Session... This page will soon be ready!

- |

${url(appName, shareId)}

- |

To monitor progress, you can use the session threads page

- | - | - """.trimMargin() + | + | + | Saving Session + | + | + | + | + |

Saving Session... This page will soon be ready!

+ |

$shareURL

+ | QR Code for $shareURL + |

To monitor progress, you can use the session threads page

+ | + | + """.trimMargin() ) } } @@ -157,5 +182,4 @@ class SessionShareServlet( var domain = System.getProperty("domain", "apps.simiacrypt.us") } -} - +} \ No newline at end of file diff --git a/webui/src/main/resources/application/index.html b/webui/src/main/resources/application/index.html index 7911f863..83bdfb9e 100644 --- a/webui/src/main/resources/application/index.html +++ b/webui/src/main/resources/application/index.html @@ -10,12 +10,12 @@ - + @@ -26,8 +26,6 @@
- - diff --git a/webui/src/main/resources/shared/_main.scss b/webui/src/main/resources/shared/_main.scss index a2886212..b0a25e1d 100644 --- a/webui/src/main/resources/shared/_main.scss +++ b/webui/src/main/resources/shared/_main.scss @@ -413,11 +413,10 @@ pre.response-message { #footer { position: fixed; bottom: 0; - width: 100vw; + right: 5px; text-align: right; z-index: 1000; padding: 10px; - background-color: $toolbar-bg-color; a { color: $footer-link-color;