diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt index 1aebfd227e..fc796c37ea 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/CodingAgent.kt @@ -86,7 +86,7 @@ class CodingAgent( private val toolOrchestrator = ToolOrchestrator(toolRegistry, policyEngine, renderer, mcpConfigService = mcpToolConfigService) private val errorRecoveryAgent = ErrorRecoveryAgent(projectPath, llmService) - private val analysisAgent = AnalysisAgent(llmService, contentThreshold = 5000) + private val analysisAgent = AnalysisAgent(llmService, contentThreshold = 15000) private val mcpToolsInitializer = McpToolsInitializer() // 执行器 diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/compiler/service/DefaultDevInsCompilerService.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/compiler/service/DefaultDevInsCompilerService.kt new file mode 100644 index 0000000000..2b01a0d8c4 --- /dev/null +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/compiler/service/DefaultDevInsCompilerService.kt @@ -0,0 +1,70 @@ +package cc.unitmesh.devins.compiler.service + +import cc.unitmesh.devins.compiler.DevInsCompiler +import cc.unitmesh.devins.compiler.context.CompilerContext +import cc.unitmesh.devins.compiler.result.DevInsCompiledResult +import cc.unitmesh.devins.compiler.variable.VariableScope +import cc.unitmesh.devins.compiler.variable.VariableType +import cc.unitmesh.devins.filesystem.ProjectFileSystem + +/** + * 默认的 DevIns 编译器服务实现 + * + * 使用 mpp-core 的 DevInsCompiler,基于自定义 AST 解析器。 + * 适用于 CLI、Desktop、WASM 等跨平台环境。 + * + * 特点: + * - 跨平台支持(JS, WASM, Desktop JVM, Android, iOS) + * - 基于自定义 DevInsParser 解析 + * - 命令输出为占位符格式(如 {{FILE_CONTENT:path}}) + * - 不支持 IDE 特定功能(Symbol 解析、重构等) + */ +class DefaultDevInsCompilerService : DevInsCompilerService { + + override suspend fun compile(source: String, fileSystem: ProjectFileSystem): DevInsCompiledResult { + val context = CompilerContext().apply { + this.fileSystem = fileSystem + } + val compiler = DevInsCompiler(context) + return compiler.compileFromSource(source) + } + + override suspend fun compile( + source: String, + fileSystem: ProjectFileSystem, + variables: Map + ): DevInsCompiledResult { + val context = CompilerContext().apply { + this.fileSystem = fileSystem + } + + // 添加自定义变量 + variables.forEach { (name, value) -> + context.variableTable.addVariable( + name = name, + varType = inferVariableType(value), + value = value, + scope = VariableScope.USER_DEFINED + ) + } + + val compiler = DevInsCompiler(context) + return compiler.compileFromSource(source) + } + + override fun supportsIdeFeatures(): Boolean = false + + override fun getName(): String = "DefaultDevInsCompilerService (mpp-core)" + + private fun inferVariableType(value: Any): VariableType { + return when (value) { + is String -> VariableType.STRING + is Int, is Long, is Double, is Float -> VariableType.NUMBER + is Boolean -> VariableType.BOOLEAN + is List<*> -> VariableType.ARRAY + is Map<*, *> -> VariableType.OBJECT + else -> VariableType.UNKNOWN + } + } +} + diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/compiler/service/DevInsCompilerService.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/compiler/service/DevInsCompilerService.kt new file mode 100644 index 0000000000..1feaf0d270 --- /dev/null +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/compiler/service/DevInsCompilerService.kt @@ -0,0 +1,101 @@ +package cc.unitmesh.devins.compiler.service + +import cc.unitmesh.devins.compiler.result.DevInsCompiledResult +import cc.unitmesh.devins.filesystem.ProjectFileSystem +import kotlin.concurrent.Volatile + +/** + * DevIns 编译器服务接口 + * + * 提供可切换的编译器核心,支持: + * - mpp-core 默认实现(跨平台,基于自定义 AST) + * - IDEA 专用实现(基于 PSI,支持 IDE 功能如 Symbol 解析、重构等) + * + * 使用方式: + * ```kotlin + * // 在 mpp-idea 中使用 IDEA 编译器 + * val compilerService = IdeaDevInsCompilerService(project) + * val llmService = KoogLLMService(config, compilerService = compilerService) + * + * // 在 CLI/Desktop 中使用默认编译器 + * val llmService = KoogLLMService(config) + * // compilerService 默认为 DefaultDevInsCompilerService + * ``` + */ +interface DevInsCompilerService { + + /** + * 编译 DevIns 源代码 + * + * @param source DevIns 源代码字符串 + * @param fileSystem 项目文件系统,用于解析文件路径 + * @return 编译结果 + */ + suspend fun compile(source: String, fileSystem: ProjectFileSystem): DevInsCompiledResult + + /** + * 编译 DevIns 源代码,带有自定义变量 + * + * @param source DevIns 源代码字符串 + * @param fileSystem 项目文件系统 + * @param variables 自定义变量映射 + * @return 编译结果 + */ + suspend fun compile( + source: String, + fileSystem: ProjectFileSystem, + variables: Map + ): DevInsCompiledResult + + /** + * 检查编译器是否支持 IDE 功能 + * + * IDE 功能包括: + * - Symbol 解析 (/symbol 命令) + * - 代码重构 (/refactor 命令) + * - 数据库操作 (/database 命令) + * - 代码结构分析 (/structure 命令) + * - 符号使用查找 (/usage 命令) + * + * @return true 如果支持 IDE 功能 + */ + fun supportsIdeFeatures(): Boolean = false + + /** + * 获取编译器名称,用于日志和调试 + */ + fun getName(): String + + companion object { + /** + * 全局编译器服务实例 + * 可以在应用启动时设置为 IDEA 专用实现 + */ + @Volatile + private var instance: DevInsCompilerService? = null + + /** + * 获取当前编译器服务实例 + * 如果未设置,返回默认实现 + */ + fun getInstance(): DevInsCompilerService { + return instance ?: DefaultDevInsCompilerService() + } + + /** + * 设置全局编译器服务实例 + * 应在应用启动时调用 + */ + fun setInstance(service: DevInsCompilerService) { + instance = service + } + + /** + * 重置为默认实现 + */ + fun reset() { + instance = null + } + } +} + diff --git a/mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/KoogLLMService.kt b/mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/KoogLLMService.kt index b9cb556e58..536e7e7008 100644 --- a/mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/KoogLLMService.kt +++ b/mpp-core/src/commonMain/kotlin/cc/unitmesh/llm/KoogLLMService.kt @@ -7,8 +7,7 @@ import ai.koog.prompt.llm.LLModel import ai.koog.prompt.params.LLMParams import ai.koog.prompt.streaming.StreamFrame import cc.unitmesh.agent.logging.getLogger -import cc.unitmesh.devins.compiler.DevInsCompilerFacade -import cc.unitmesh.devins.compiler.context.CompilerContext +import cc.unitmesh.devins.compiler.service.DevInsCompilerService import cc.unitmesh.devins.filesystem.EmptyFileSystem import cc.unitmesh.devins.filesystem.ProjectFileSystem import cc.unitmesh.devins.llm.Message @@ -17,29 +16,40 @@ import cc.unitmesh.llm.compression.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion -import kotlinx.serialization.json.Json import kotlinx.datetime.Clock +/** + * LLM 服务 + * + * @param config 模型配置 + * @param compressionConfig 压缩配置 + * @param compilerService 可选的编译器服务,用于编译 DevIns 命令 + * 如果不提供,将使用 DevInsCompilerService.getInstance() + */ class KoogLLMService( private val config: ModelConfig, - private val compressionConfig: CompressionConfig = CompressionConfig() + private val compressionConfig: CompressionConfig = CompressionConfig(), + private val compilerService: DevInsCompilerService? = null ) { private val logger = getLogger("KoogLLMService") private val executor: SingleLLMPromptExecutor by lazy { ExecutorFactory.create(config) } - + private val model: LLModel by lazy { ModelRegistry.createModel(config.provider, config.modelName) ?: ModelRegistry.createGenericModel(config.provider, config.modelName) } - + private val compressionService: ChatCompressionService by lazy { ChatCompressionService(executor, model, compressionConfig) } - + + // 获取实际使用的编译器服务 + private val actualCompilerService: DevInsCompilerService + get() = compilerService ?: DevInsCompilerService.getInstance() + // Token 追踪 private var lastTokenInfo: TokenInfo = TokenInfo() private var messagesSinceLastCompression = 0 @@ -125,16 +135,14 @@ class KoogLLMService( } private suspend fun compilePrompt(userPrompt: String, fileSystem: ProjectFileSystem): String { - val context = CompilerContext().apply { - this.fileSystem = fileSystem - } - - val compiledResult = DevInsCompilerFacade.compile(userPrompt, context) + val compiledResult = actualCompilerService.compile(userPrompt, fileSystem) if (compiledResult.hasError) { - logger.warn { "⚠️ [KoogLLMService] 编译错误: ${compiledResult.errorMessage}" } + logger.warn { "⚠️ [KoogLLMService] 编译错误 (${actualCompilerService.getName()}): ${compiledResult.errorMessage}" } } + logger.debug { "📝 [KoogLLMService] 使用编译器: ${actualCompilerService.getName()}, IDE功能: ${actualCompilerService.supportsIdeFeatures()}" } + return compiledResult.output } diff --git a/mpp-core/src/jvmTest/kotlin/cc/unitmesh/devins/compiler/service/DevInsCompilerServiceTest.kt b/mpp-core/src/jvmTest/kotlin/cc/unitmesh/devins/compiler/service/DevInsCompilerServiceTest.kt new file mode 100644 index 0000000000..78b18048ea --- /dev/null +++ b/mpp-core/src/jvmTest/kotlin/cc/unitmesh/devins/compiler/service/DevInsCompilerServiceTest.kt @@ -0,0 +1,120 @@ +package cc.unitmesh.devins.compiler.service + +import cc.unitmesh.devins.filesystem.EmptyFileSystem +import kotlinx.coroutines.test.runTest +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class DevInsCompilerServiceTest { + + @AfterTest + fun tearDown() { + DevInsCompilerService.reset() + } + + @Test + fun `getInstance returns DefaultDevInsCompilerService when not set`() { + val service = DevInsCompilerService.getInstance() + assertNotNull(service) + assertEquals("DefaultDevInsCompilerService (mpp-core)", service.getName()) + assertFalse(service.supportsIdeFeatures()) + } + + @Test + fun `setInstance allows custom implementation`() { + val customService = object : DevInsCompilerService { + override suspend fun compile(source: String, fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem) = + cc.unitmesh.devins.compiler.result.DevInsCompiledResult(output = "custom: $source") + + override suspend fun compile( + source: String, + fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem, + variables: Map + ) = compile(source, fileSystem) + + override fun supportsIdeFeatures() = true + override fun getName() = "CustomCompilerService" + } + + DevInsCompilerService.setInstance(customService) + + val service = DevInsCompilerService.getInstance() + assertEquals("CustomCompilerService", service.getName()) + assertTrue(service.supportsIdeFeatures()) + } + + @Test + fun `reset restores default implementation`() { + val customService = object : DevInsCompilerService { + override suspend fun compile(source: String, fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem) = + cc.unitmesh.devins.compiler.result.DevInsCompiledResult(output = "custom") + + override suspend fun compile( + source: String, + fileSystem: cc.unitmesh.devins.filesystem.ProjectFileSystem, + variables: Map + ) = compile(source, fileSystem) + + override fun supportsIdeFeatures() = true + override fun getName() = "CustomCompilerService" + } + + DevInsCompilerService.setInstance(customService) + assertEquals("CustomCompilerService", DevInsCompilerService.getInstance().getName()) + + DevInsCompilerService.reset() + assertEquals("DefaultDevInsCompilerService (mpp-core)", DevInsCompilerService.getInstance().getName()) + } +} + +class DefaultDevInsCompilerServiceTest { + + @Test + fun `compile returns output for simple text`() = runTest { + val service = DefaultDevInsCompilerService() + val result = service.compile("Hello World", EmptyFileSystem()) + + assertEquals("Hello World", result.output) + assertFalse(result.hasError) + } + + @Test + fun `compile handles DevIns commands`() = runTest { + val service = DefaultDevInsCompilerService() + // /file command should produce placeholder in mpp-core implementation + val result = service.compile("/file:test.kt", EmptyFileSystem()) + + // The mpp-core compiler outputs placeholders for commands + assertNotNull(result.output) + } + + @Test + fun `supportsIdeFeatures returns false`() { + val service = DefaultDevInsCompilerService() + assertFalse(service.supportsIdeFeatures()) + } + + @Test + fun `getName returns correct name`() { + val service = DefaultDevInsCompilerService() + assertEquals("DefaultDevInsCompilerService (mpp-core)", service.getName()) + } + + @Test + fun `compile with variables works`() = runTest { + val service = DefaultDevInsCompilerService() + val variables = mapOf( + "name" to "test", + "count" to 42, + "enabled" to true + ) + + val result = service.compile("Hello \$name", EmptyFileSystem(), variables) + assertNotNull(result.output) + } +} + diff --git a/mpp-idea/build.gradle.kts b/mpp-idea/build.gradle.kts index 323b6c2987..5cf4bf53e4 100644 --- a/mpp-idea/build.gradle.kts +++ b/mpp-idea/build.gradle.kts @@ -37,6 +37,31 @@ repositories { } } +// Global exclusions for heavy dependencies not needed in IntelliJ plugin +// These exclusions apply to ALL configurations including transitive dependencies from includeBuild projects +configurations.all { + exclude(group = "aws.sdk.kotlin") // AWS SDK (~30MB) - from ai.koog:prompt-executor-bedrock-client + exclude(group = "aws.smithy.kotlin") // AWS Smithy runtime + exclude(group = "org.apache.tika") // Apache Tika (~100MB) - document parsing + exclude(group = "org.apache.poi") // Apache POI - Office document parsing (from Tika) + exclude(group = "org.apache.pdfbox") // PDFBox (~10MB) - PDF parsing + exclude(group = "net.sourceforge.plantuml") // PlantUML (~20MB) - from dev.snipme:highlights + exclude(group = "org.jsoup") // Jsoup HTML parser + exclude(group = "ai.koog", module = "prompt-executor-bedrock-client") // Bedrock executor + // Redis/Lettuce - not needed in IDEA plugin (~5MB) + exclude(group = "io.lettuce") + exclude(group = "io.projectreactor") + // RxJava - not needed (~2.6MB) + exclude(group = "io.reactivex.rxjava3") + // RSyntaxTextArea - IDEA has its own editor (~1.3MB) + exclude(group = "com.fifesoft") + // Netty - not needed for IDEA plugin (~3MB) + exclude(group = "io.netty") + // pty4j/jediterm - IDEA has its own terminal (~3MB) + exclude(group = "org.jetbrains.pty4j") + exclude(group = "org.jetbrains.jediterm") +} + dependencies { // Depend on mpp-ui and mpp-core JVM targets for shared UI components and ConfigManager // For KMP projects, we need to depend on the JVM target specifically @@ -98,6 +123,7 @@ dependencies { exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core-jvm") exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core") exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core-jvm") + // Note: Heavy dependencies (AWS, Tika, POI, PDFBox, PlantUML, Jsoup) are excluded globally above } // Use platform-provided kotlinx libraries to avoid classloader conflicts @@ -192,6 +218,21 @@ tasks { useJUnitPlatform() } + // Exclude large font files from mpp-ui that are not needed in IDEA plugin + // These fonts are for Desktop/WASM apps, IDEA has its own fonts + named("prepareSandbox") { + // Exclude font files from the plugin distribution + // NotoSansSC-Regular.ttf (~18MB), NotoColorEmoji.ttf (~11MB x2), FiraCode fonts (~1MB) + exclude("**/fonts/**") + exclude("**/composeResources/**/font/**") + exclude("**/*.ttf") + exclude("**/*.otf") + // Also exclude icon files meant for desktop app + exclude("**/icon.icns") + exclude("**/icon.ico") + exclude("**/icon-512.png") + } + // Task to verify no conflicting dependencies are included register("verifyNoDuplicateDependencies") { group = "verification" diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/compiler/IdeaDevInsCompilerService.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/compiler/IdeaDevInsCompilerService.kt new file mode 100644 index 0000000000..1ef74a1034 --- /dev/null +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/compiler/IdeaDevInsCompilerService.kt @@ -0,0 +1,133 @@ +package cc.unitmesh.devins.idea.compiler + +import cc.unitmesh.devins.compiler.result.DevInsCompiledResult +import cc.unitmesh.devins.compiler.service.DevInsCompilerService +import cc.unitmesh.devins.filesystem.ProjectFileSystem +import cc.unitmesh.devti.language.compiler.DevInsCompiler +import cc.unitmesh.devti.language.psi.DevInFile +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.util.PsiUtilBase + +/** + * IDEA 专用的 DevIns 编译器服务 + * + * 使用 devins-lang 模块的 DevInsCompiler,基于 IntelliJ PSI 解析。 + * 支持完整的 IDE 功能: + * - Symbol 解析 (/symbol 命令) + * - 代码重构 (/refactor 命令) + * - 数据库操作 (/database 命令) + * - 代码结构分析 (/structure 命令) + * - 符号使用查找 (/usage 命令) + * - 文件操作 (/file, /write, /edit_file 命令) + * - 进程管理 (/launch_process, /kill_process 等) + * + * @param project IntelliJ Project 实例 + * @param editor 可选的编辑器实例,用于获取当前光标位置 + */ +class IdeaDevInsCompilerService( + private val project: Project, + private val editor: Editor? = null +) : DevInsCompilerService { + + override suspend fun compile(source: String, fileSystem: ProjectFileSystem): DevInsCompiledResult { + return compileInternal(source) + } + + override suspend fun compile( + source: String, + fileSystem: ProjectFileSystem, + variables: Map + ): DevInsCompiledResult { + // TODO: 支持自定义变量注入到 VariableTable + return compileInternal(source) + } + + override fun supportsIdeFeatures(): Boolean = true + + override fun getName(): String = "IdeaDevInsCompilerService (devins-lang PSI)" + + private suspend fun compileInternal(source: String): DevInsCompiledResult { + // 从字符串创建 DevInFile + val devInFile = DevInFile.fromString(project, source) + + // 获取当前编辑器和光标位置的元素 + val currentEditor = editor ?: FileEditorManager.getInstance(project).selectedTextEditor + val element = currentEditor?.let { getElementAtCaret(it) } + + // 创建并执行编译器 + val compiler = DevInsCompiler(project, devInFile, currentEditor, element) + val ideaResult = compiler.compile() + + // 转换为 mpp-core 的 DevInsCompiledResult + return convertToMppResult(ideaResult) + } + + private fun getElementAtCaret(editor: Editor): PsiElement? { + return runReadAction { + val offset = editor.caretModel.currentCaret.offset + val psiFile = PsiUtilBase.getPsiFileInEditor(editor, project) ?: return@runReadAction null + + var element = psiFile.findElementAt(offset) ?: return@runReadAction null + if (element is PsiWhiteSpace) { + element = element.parent + } + element + } + } + + /** + * 将 devins-lang 的编译结果转换为 mpp-core 的格式 + */ + private fun convertToMppResult( + ideaResult: cc.unitmesh.devti.language.compiler.DevInsCompiledResult + ): DevInsCompiledResult { + return DevInsCompiledResult( + input = ideaResult.input, + output = ideaResult.output, + isLocalCommand = ideaResult.isLocalCommand, + hasError = ideaResult.hasError, + errorMessage = null, // IDEA 版本没有 errorMessage 字段 + executeAgent = ideaResult.executeAgent?.let { agent -> + cc.unitmesh.devins.compiler.result.CustomAgentConfig( + name = agent.name, + type = agent.state.name, + parameters = emptyMap() + ) + }, + nextJob = null, // DevInFile 不能直接转换,需要时再处理 + config = ideaResult.config?.let { hobbitHole -> + cc.unitmesh.devins.compiler.result.FrontMatterConfig( + name = hobbitHole.name, + description = hobbitHole.description, + variables = emptyMap(), + lifecycle = emptyMap(), + functions = emptyList(), + agents = emptyList() + ) + } + ) + } + + companion object { + /** + * 创建 IDEA 编译器服务实例 + */ + fun create(project: Project, editor: Editor? = null): IdeaDevInsCompilerService { + return IdeaDevInsCompilerService(project, editor) + } + + /** + * 注册为全局编译器服务 + * 应在 IDEA 插件启动时调用 + */ + fun registerAsGlobal(project: Project, editor: Editor? = null) { + DevInsCompilerService.setInstance(IdeaDevInsCompilerService(project, editor)) + } + } +} + diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt index b94a41189a..c0b17eda14 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt @@ -9,6 +9,8 @@ import cc.unitmesh.agent.config.PreloadingStatus import cc.unitmesh.agent.config.ToolConfigFile import cc.unitmesh.agent.tool.ToolType import cc.unitmesh.agent.tool.schema.ToolCategory +import cc.unitmesh.devins.compiler.service.DevInsCompilerService +import cc.unitmesh.devins.idea.compiler.IdeaDevInsCompilerService import cc.unitmesh.devins.idea.renderer.JewelRenderer import cc.unitmesh.devins.ui.config.AutoDevConfigWrapper import cc.unitmesh.devins.ui.config.ConfigManager @@ -65,6 +67,11 @@ class IdeaAgentViewModel( // LLM Service (created from config) private var llmService: KoogLLMService? = null + // IDEA DevIns Compiler Service (uses PSI-based compiler with full IDE features) + private val ideaCompilerService: DevInsCompilerService by lazy { + IdeaDevInsCompilerService.create(project) + } + // CodingAgent instance private var codingAgent: CodingAgent? = null private var agentInitialized = false @@ -121,8 +128,12 @@ class IdeaAgentViewModel( _currentModelConfig.value = modelConfig // Create LLM service if config is valid + // Inject IDEA compiler service for full IDE feature support if (modelConfig != null && modelConfig.isValid()) { - llmService = KoogLLMService.create(modelConfig) + llmService = KoogLLMService( + config = modelConfig, + compilerService = ideaCompilerService + ) // Start MCP preloading after LLM service is created startMcpPreloading() } diff --git a/mpp-idea/src/main/resources/META-INF/plugin.xml b/mpp-idea/src/main/resources/META-INF/plugin.xml index f3c963d3f7..6101d71f1c 100644 --- a/mpp-idea/src/main/resources/META-INF/plugin.xml +++ b/mpp-idea/src/main/resources/META-INF/plugin.xml @@ -1,11 +1,11 @@ cc.unitmesh.devins.idea - AutoDev Compose UI - UnitMesh + AutoDev Next + UnitMesh diff --git a/mpp-ui/build.gradle.kts b/mpp-ui/build.gradle.kts index c5f321e74c..eeb55f8719 100644 --- a/mpp-ui/build.gradle.kts +++ b/mpp-ui/build.gradle.kts @@ -696,7 +696,8 @@ tasks.register("downloadWasmFonts") { group = "build" description = "Download fonts for WASM UTF-8 support (not committed to Git)" - fontDir.set(file("src/commonMain/composeResources/font")) + // Fonts are only needed for WASM platform, so download to wasmJsMain + fontDir.set(file("src/wasmJsMain/composeResources/font")) useCJKFont.set(project.findProperty("useCJKFont")?.toString()?.toBoolean() ?: true) } diff --git a/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.android.kt b/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.android.kt index d2756605ce..102f83de33 100644 --- a/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.android.kt +++ b/mpp-ui/src/androidMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.android.kt @@ -1,5 +1,6 @@ package cc.unitmesh.devins.ui.compose.sketch +import androidx.compose.runtime.Composable import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -30,3 +31,10 @@ actual fun getFiraCodeFontFamily(): FontFamily { FontFamily.Monospace } } + +/** + * Android implementation - use default font family + * Android has good system font support for UTF-8 + */ +@Composable +actual fun getUtf8FontFamily(): FontFamily = FontFamily.Default diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt index 0ba49a12ab..69dcd043e9 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt @@ -17,17 +17,14 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import autodev_intellij.mpp_ui.generated.resources.NotoSansSC_Regular -import autodev_intellij.mpp_ui.generated.resources.Res -import cc.unitmesh.agent.Platform import cc.unitmesh.agent.render.TimelineItem import cc.unitmesh.devins.llm.Message import cc.unitmesh.devins.llm.MessageRole import cc.unitmesh.devins.ui.compose.icons.AutoDevComposeIcons import cc.unitmesh.devins.ui.compose.sketch.SketchRenderer +import cc.unitmesh.devins.ui.compose.sketch.getUtf8FontFamily import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.jetbrains.compose.resources.Font @Composable fun AgentMessageList( @@ -278,7 +275,7 @@ fun MessageItem( } else { Text( text = message.content, - fontFamily = if (Platform.isWasm) FontFamily(Font(Res.font.NotoSansSC_Regular)) else FontFamily.Monospace, + fontFamily = getUtf8FontFamily(), style = MaterialTheme.typography.bodyMedium ) } diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/editor/DevInEditorInput.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/editor/DevInEditorInput.kt index 3a45add3ce..b693d16301 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/editor/DevInEditorInput.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/editor/DevInEditorInput.kt @@ -25,8 +25,6 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import autodev_intellij.mpp_ui.generated.resources.NotoSansSC_Regular -import autodev_intellij.mpp_ui.generated.resources.Res import cc.unitmesh.agent.Platform import cc.unitmesh.agent.mcp.McpClientManager import cc.unitmesh.agent.mcp.McpConfig @@ -41,13 +39,13 @@ import cc.unitmesh.devins.ui.compose.editor.completion.CompletionPopup import cc.unitmesh.devins.ui.compose.editor.completion.CompletionTrigger import cc.unitmesh.devins.ui.compose.editor.highlighting.DevInSyntaxHighlighter import cc.unitmesh.devins.ui.config.ConfigManager +import cc.unitmesh.devins.ui.compose.sketch.getUtf8FontFamily import cc.unitmesh.devins.workspace.WorkspaceManager import cc.unitmesh.llm.KoogLLMService import cc.unitmesh.llm.ModelConfig import cc.unitmesh.llm.PromptEnhancer import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.jetbrains.compose.resources.Font /** * DevIn 编辑器输入组件 @@ -485,7 +483,7 @@ fun DevInEditorInput( .onPreviewKeyEvent { handleKeyEvent(it) }, textStyle = TextStyle( - fontFamily = if (Platform.isWasm) FontFamily(Font(Res.font.NotoSansSC_Regular)) else FontFamily.Monospace, + fontFamily = getUtf8FontFamily(), fontSize = inputFontSize, color = MaterialTheme.colorScheme.onSurface, lineHeight = inputLineHeight diff --git a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.kt b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.kt index 7053a1fa0c..7999a37b04 100644 --- a/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.kt +++ b/mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.kt @@ -1,5 +1,6 @@ package cc.unitmesh.devins.ui.compose.sketch +import androidx.compose.runtime.Composable import androidx.compose.ui.text.font.FontFamily /** @@ -12,3 +13,11 @@ expect fun getFiraCodeFontFamily(): FontFamily * Get default monospace font family as fallback */ fun getDefaultMonospaceFontFamily(): FontFamily = FontFamily.Monospace + +/** + * Get UTF-8 font family for text display + * On WASM, returns Noto Sans SC for CJK support + * On other platforms, returns default font family + */ +@Composable +expect fun getUtf8FontFamily(): FontFamily diff --git a/mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.ios.kt b/mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.ios.kt index 61a74f917d..a9323e9558 100644 --- a/mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.ios.kt +++ b/mpp-ui/src/iosMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.ios.kt @@ -1,5 +1,6 @@ package cc.unitmesh.devins.ui.compose.sketch +import androidx.compose.runtime.Composable import androidx.compose.ui.text.font.FontFamily /** @@ -12,3 +13,10 @@ actual fun getFiraCodeFontFamily(): FontFamily { return FontFamily.Monospace } +/** + * iOS implementation - use default font family + * iOS has good system font support for UTF-8 + */ +@Composable +actual fun getUtf8FontFamily(): FontFamily = FontFamily.Default + diff --git a/mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.js.kt b/mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.js.kt index 1cca68b762..d18464c1ec 100644 --- a/mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.js.kt +++ b/mpp-ui/src/jsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.js.kt @@ -1,5 +1,6 @@ package cc.unitmesh.devins.ui.compose.sketch +import androidx.compose.runtime.Composable import androidx.compose.ui.text.font.FontFamily /** @@ -14,3 +15,10 @@ actual fun getFiraCodeFontFamily(): FontFamily { // The browser will use the system's monospace font return FontFamily.Monospace } + +/** + * JS implementation - use default font family + * Browser has good system font support for UTF-8 + */ +@Composable +actual fun getUtf8FontFamily(): FontFamily = FontFamily.Default diff --git a/mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.jvm.kt b/mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.jvm.kt index 168039403d..5edd109a24 100644 --- a/mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.jvm.kt +++ b/mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.jvm.kt @@ -1,5 +1,6 @@ package cc.unitmesh.devins.ui.compose.sketch +import androidx.compose.runtime.Composable import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.platform.Font @@ -29,3 +30,10 @@ actual fun getFiraCodeFontFamily(): FontFamily { FontFamily.Monospace } } + +/** + * JVM implementation - use default font family + * JVM has good system font support for UTF-8 + */ +@Composable +actual fun getUtf8FontFamily(): FontFamily = FontFamily.Default diff --git a/mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.wasmJs.kt b/mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.wasmJs.kt index d6ea550974..c6ffa413ce 100644 --- a/mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.wasmJs.kt +++ b/mpp-ui/src/wasmJsMain/kotlin/cc/unitmesh/devins/ui/compose/sketch/CodeFont.wasmJs.kt @@ -1,14 +1,27 @@ package cc.unitmesh.devins.ui.compose.sketch +import androidx.compose.runtime.Composable import androidx.compose.ui.text.font.FontFamily +import autodev_intellij.mpp_ui.generated.resources.NotoSansSC_Regular +import autodev_intellij.mpp_ui.generated.resources.Res +import org.jetbrains.compose.resources.Font /** * WASM JS implementation of FiraCode font loading * Falls back to default monospace - * + * * Note: This is non-composable to match the expect declaration in commonMain */ actual fun getFiraCodeFontFamily(): FontFamily { // WASM uses default monospace for code return FontFamily.Monospace } + +/** + * WASM implementation - use Noto Sans SC for CJK support + * This font is only bundled in WASM builds (~18MB) + */ +@Composable +actual fun getUtf8FontFamily(): FontFamily { + return FontFamily(Font(Res.font.NotoSansSC_Regular)) +}