diff --git a/shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/ShireRunner.kt b/shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/ShireRunner.kt index 4e55f360a..80ad66aba 100644 --- a/shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/ShireRunner.kt +++ b/shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/ShireRunner.kt @@ -2,11 +2,16 @@ package com.phodal.shirelang.run.runner import com.intellij.execution.process.ProcessHandler import com.intellij.execution.ui.ConsoleView +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.editor.Editor +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator import com.intellij.openapi.project.Project +import com.phodal.shire.llm.model.ChatMessage import com.phodal.shirecore.agent.InteractionType import com.phodal.shirelang.compiler.hobbit.HobbitHole import com.phodal.shirelang.run.ShireConfiguration +import com.phodal.shirelang.run.runner.tasks.FileGenerateTask data class ShireRunnerContext( val configuration: ShireConfiguration, @@ -25,12 +30,29 @@ abstract class ShireRunner(open val context: ShireRunnerContext) { } fun handleResult() { + val msgs: List = listOf() + val targetFile = context.editor?.virtualFile + when (context.hole?.interaction) { InteractionType.AppendCursor -> TODO() InteractionType.AppendCursorStream -> TODO() - InteractionType.OutputFile -> TODO() - InteractionType.ReplaceSelection -> TODO() - InteractionType.ReplaceCurrentFile -> TODO() + InteractionType.OutputFile -> { + // todo: replace real file name + val fileName = targetFile?.name + val task = FileGenerateTask(context.myProject, msgs, fileName) + ProgressManager.getInstance() + .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) + } + InteractionType.ReplaceSelection -> { + } + InteractionType.ReplaceCurrentFile -> { + // todo: replace real file name + val fileName = targetFile?.name + val task = FileGenerateTask(context.myProject, msgs, fileName) + + ProgressManager.getInstance() + .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) + } InteractionType.InsertBeforeSelection -> { TODO() } diff --git a/shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/tasks/FileGenerateTask.kt b/shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/tasks/FileGenerateTask.kt new file mode 100644 index 000000000..82d571c66 --- /dev/null +++ b/shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/tasks/FileGenerateTask.kt @@ -0,0 +1,93 @@ +package com.phodal.shirelang.run.runner.tasks + +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileTypes.PlainTextLanguage +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import com.phodal.shire.llm.LlmProvider +import com.phodal.shire.llm.model.ChatMessage +import com.phodal.shire.llm.model.ChatRole +import com.phodal.shirecore.markdown.Code +import com.phodal.shirelang.ShireBundle +import kotlinx.coroutines.flow.cancellable +import kotlinx.coroutines.runBlocking +import java.nio.file.Path +import kotlin.io.path.Path +import kotlinx.coroutines.flow.* + +class FileGenerateTask( + @JvmField val project: Project, + val messages: List, + val fileName: String?, + val codeOnly: Boolean = false, + val taskName: String = ShireBundle.message("intentions.request.background.process.title") +) : + Task.Backgroundable(project, taskName) { + private val projectRoot = project.guessProjectDir()!! + + override fun run(indicator: ProgressIndicator) { + val requestPrompt = messages.filter { it.role == ChatRole.User }.joinToString("\n") { it.content } + val systemPrompt = messages.filter { it.role == ChatRole.System }.joinToString("\n") { it.content } + + val stream = LlmProvider.provider(project)?.stream(requestPrompt, systemPrompt, false) + ?: return + + var result = "" + runBlocking { + stream.cancellable().collect { + result += it + } + } + + val inferFileName = if (fileName == null) { + val language = Code.parse(result).language + val timestamp = System.currentTimeMillis() + "output-" + timestamp + if (language == PlainTextLanguage.INSTANCE) ".txt" else ".$language" + } else { + fileName + } + + val file = project.guessProjectDir()!!.toNioPath().resolve(inferFileName).toFile() + if (!file.exists()) { + file.createNewFile() + } + + if (codeOnly) { + val code = Code.parse(result).text + file.writeText(code) + refreshAndOpenInEditor(file.toPath(), projectRoot) + return + } else { + file.writeText(result) + refreshAndOpenInEditor(Path(projectRoot.path), projectRoot) + } + } + + protected fun refreshAndOpenInEditor(file: Path, parentDir: VirtualFile) { + runBlocking { + ProgressManager.getInstance().run(object : Modal(project, "Refreshing Project Model", true) { + override fun run(indicator: ProgressIndicator) { + repeat(5) { + val virtualFile = LocalFileSystem.getInstance().findFileByNioFile(file) + if (virtualFile == null) { + VfsUtil.markDirtyAndRefresh(true, true, true, parentDir) + } else { + try { + FileEditorManager.getInstance(project).openFile(virtualFile, true) + return + } catch (e: Exception) { + // + } + } + } + } + }) + } + } +} \ No newline at end of file diff --git a/shirelang/src/main/resources/messages/ShireBundle.properties b/shirelang/src/main/resources/messages/ShireBundle.properties index 16e0983a0..0e263532e 100644 --- a/shirelang/src/main/resources/messages/ShireBundle.properties +++ b/shirelang/src/main/resources/messages/ShireBundle.properties @@ -19,3 +19,4 @@ shire.llm.done=\nDone! shire.prompt.fix.command=You are a top software developer in the world, which can help me to fix the issue.\nWhen I use shell-like language and compile the script, I got an error, can you help me to fix it?\n\nOrigin script:\n```\n{0}\n```\n\nScript with result:\n####\n{1}\n#### shire.prompt.fix.run-result=You are a top software developer in the world, which can help me to fix the issue.\n\nHere is the run result, can you help me to fix it?\nRun result:\n####\n{0}\n#### +intentions.request.background.process.title=Generate File diff --git a/src/main/kotlin/com/phodal/shire/llm/LlmProvider.kt b/src/main/kotlin/com/phodal/shire/llm/LlmProvider.kt index 99c31e371..4a3a128c3 100644 --- a/src/main/kotlin/com/phodal/shire/llm/LlmProvider.kt +++ b/src/main/kotlin/com/phodal/shire/llm/LlmProvider.kt @@ -75,15 +75,5 @@ interface LlmProvider { providers.first() } } - - enum class ChatRole { - System, - Assistant, - User; - - fun roleName(): String { - return this.name.lowercase() - } - } } } diff --git a/src/main/kotlin/com/phodal/shire/llm/model/ChatMessage.kt b/src/main/kotlin/com/phodal/shire/llm/model/ChatMessage.kt index 01e86ac7b..a5c85eb47 100644 --- a/src/main/kotlin/com/phodal/shire/llm/model/ChatMessage.kt +++ b/src/main/kotlin/com/phodal/shire/llm/model/ChatMessage.kt @@ -2,8 +2,18 @@ package com.phodal.shire.llm.model import kotlinx.serialization.Serializable +enum class ChatRole { + System, + Assistant, + User; + + fun roleName(): String { + return this.name.lowercase() + } +} + @Serializable -data class ChatMessage(val role: String, val content: String) +data class ChatMessage(val role: ChatRole, val content: String) @Serializable data class CustomRequest(val chatMessages: List)