From 3f6d17aba2c7b8037addfe9d5c5130680d02bc81 Mon Sep 17 00:00:00 2001 From: Gadfly Date: Tue, 16 Dec 2025 13:47:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=87=E7=BA=A7org.jetbrains.intelli?= =?UTF-8?q?j.platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + build.gradle | 265 +++++++++++++----- gradle.properties | 3 + sandbox-idea.properties | 5 + settings.gradle | 10 +- .../claudecodegui/ClaudeSDKToolWindow.java | 88 +++++- .../github/claudecodegui/ClaudeSession.java | 38 +++ .../bridge/BridgeDirectoryResolver.java | 67 ++++- webview/scripts/extract-version.mjs | 8 +- 9 files changed, 389 insertions(+), 97 deletions(-) create mode 100644 sandbox-idea.properties diff --git a/.gitignore b/.gitignore index 6fda8892..e166b540 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ webview/src/version/version.ts # 自定义沙箱目录(如果启用) .sandbox/ +logs/*.log +.intellijPlatform/ diff --git a/build.gradle b/build.gradle index 50c50744..65686f3c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,132 +1,212 @@ -import org.jetbrains.intellij.IntelliJPluginExtension - plugins { id 'java' - id 'org.jetbrains.intellij' version '1.17.4' + id 'org.jetbrains.intellij.platform' version '2.10.5' } -group 'com.github.idea-claude-code-gui' -version '0.1.0-beta3' +group = 'com.github.idea-claude-code-gui' +version = '0.1.0-beta3' -sourceCompatibility = 17 -targetCompatibility = 17 +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} repositories { mavenCentral() + intellijPlatform { + defaultRepositories() + } } dependencies { implementation 'com.google.code.gson:gson:2.10.1' + + intellijPlatform { + intellijIdeaCommunity('2024.3.1') + instrumentationTools() + } } -// 配置 IntelliJ Platform 插件 -intellij { - version = '2024.3' // 使用 2024.3 稳定版本 - type = 'IC' // IC = IntelliJ IDEA Community Edition - - // 下载源码和 JavaDoc - downloadSources = true - - // 插件依赖(如果需要) - plugins = [] - - // 禁用字节码插桩,避免生成 instrumented JAR - instrumentCode = false -} - -tasks.withType(JavaCompile) { +tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } +// Ensure plugin.xml is patched with the correct since/until build and change notes patchPluginXml { + // plugin compatibility range sinceBuild = '233' - untilBuild = '263.*' // 兼容到 2026.3 版本,支持更新的 IDE 版本 + untilBuild = '263.*' - changeNotes = """ - - """ + // Release notes shown in plugin repositories + // Dynamically load the latest changelog entry from CHANGELOG.md and convert to simple HTML. + def changelogFile = file('CHANGELOG.md') + def computedChangeNotes = '' + if (changelogFile.exists()) { + def lines = changelogFile.readLines('UTF-8') + // Find first section header (lines starting with '#####') + def start = -1 + for (int i = 0; i < lines.size(); i++) { + if (lines[i].trim().startsWith('#####')) { + start = i + break + } + } + + if (start >= 0) { + def sb = new StringBuilder() + boolean inList = false + int i = start + 1 + while (i < lines.size() && !lines[i].trim().startsWith('#####')) { + def line = lines[i] + def t = line.trim() + + if (t.startsWith('- ')) { + if (!inList) { sb.append(''); inList = false } + sb.append('
') + } else { + if (inList) { sb.append(''); inList = false } + // paragraph + def para = t.replace('&', '&').replace('<', '<').replace('>', '>') + sb.append('

' + para + '

') + } + + i++ + } + if (inList) { sb.append('') } + computedChangeNotes = sb.toString() + } + } + + changeNotes = computedChangeNotes ?: '

No changelog found.

' } -runIde { - // 启用 JCEF 支持 - jvmArgs '-Djcef.sandbox.enable=false' -} - -buildSearchableOptions { +// Disable product-specific searchable options/instrument tasks if present +// Use matching to cover task name variants like buildSearchableOptionsIC / buildSearchableOptionsIU etc. +tasks.matching { it.name.startsWith('buildSearchableOptions') }.configureEach { enabled = false } -// 禁用 instrumented JAR 生成(这是导致 "invalid plugin descriptor" 错误的根源) -// instrumented JAR 是用于代码覆盖率测试的,不应该被打包到最终的插件分发包中 -tasks.named("instrumentCode") { +tasks.named('buildSearchableOptions') { enabled = false } -tasks.named("instrumentTestCode") { +tasks.named('instrumentCode') { enabled = false } +tasks.named('jarSearchableOptions') { + enabled = false +} + +tasks.named('runIde') { + jvmArgumentProviders.add({ + [ + '-Djcef.sandbox.enable=false', + // 禁用插件自动卸载(避免检测到文件变更时自动卸载) + '-Didea.auto.reload.plugins=false', + // 禁用插件动态卸载 + '-Didea.dynamic.plugins.allowed=false', + // 启用内部模式 + '-Didea.is.internal=true', + // 禁用文件系统事件监控(可选) + // '-Didea.filewatcher.disabled=true', + // 增加插件加载超时 + '-Didea.plugins.load.timeout=60000' + ] + } as CommandLineArgumentProvider) + + // 设置系统属性 + systemProperty 'idea.auto.reload.plugins', 'false' + systemProperty 'idea.dynamic.plugins.allowed', 'false' + systemProperty 'idea.is.internal', 'true' + systemProperty 'idea.plugin.in.sandbox.mode', 'true' + + // 复制自定义配置到sandbox + doFirst { + def sandboxConfig = file('sandbox-idea.properties') + if (sandboxConfig.exists()) { + def sandboxDir = layout.buildDirectory.dir("idea-sandbox").get().asFile + def configDir = new File(sandboxDir, 'config') + configDir.mkdirs() + + copy { + from sandboxConfig + into configDir + rename { 'idea.properties' } + } + println "已复制sandbox配置到: ${configDir}/idea.properties" + } + } +} + // 统一的 AI Bridge(合并 Claude 和 Codex) def aiBridgeDir = file("ai-bridge") -def aiBridgePackDir = file("$buildDir/ai-bridge-pack") -def aiBridgeArchive = new File(aiBridgePackDir, "ai-bridge.zip") +def aiBridgePackDir = layout.buildDirectory.dir('ai-bridge-pack').get().asFile +def aiBridgeArchive = new File(aiBridgePackDir, 'ai-bridge.zip') // 自动构建 webview def webviewDir = file('webview') tasks.register('buildWebview', Exec) { workingDir webviewDir - // 根据操作系统选择命令 if (System.getProperty('os.name').toLowerCase().contains('windows')) { commandLine 'cmd', '/c', 'npm', 'run', 'build' } else { commandLine 'npm', 'run', 'build' } - // 只在 webview 目录存在时执行(使用变量避免配置缓存问题) onlyIf { webviewDir.exists() } - - // 输出构建信息 - doFirst { - println "Building webview..." - } - doLast { - println "Webview build completed." - } + doFirst { println "Building webview..." } + doLast { println "Webview build completed." } } tasks.register('packageAiBridge', Zip) { onlyIf { aiBridgeDir.exists() } - archiveFileName = "ai-bridge.zip" + archiveFileName = 'ai-bridge.zip' destinationDirectory = aiBridgePackDir from(aiBridgeDir) - exclude("node_modules/.pnpm/**") + exclude('node_modules/.pnpm/**') includeEmptyDirs = true } -// 提取插件名称,避免在闭包中访问 extensions -def intellijExt = project.extensions.getByType(IntelliJPluginExtension) -def pluginName = intellijExt.pluginName.getOrElse(project.name) +// 提取插件名称 +def pluginName = project.name -tasks.named("prepareSandbox") { - dependsOn("buildWebview", "packageAiBridge") - def sandboxRootProvider = layout.buildDirectory.dir("idea-sandbox") +tasks.matching { it.name.startsWith('prepareSandbox') }.configureEach { + dependsOn('buildWebview', 'packageAiBridge') + def sandboxRootProvider = layout.buildDirectory.dir('idea-sandbox') doLast { - if (!aiBridgeDir.exists()) { - throw new GradleException("ai-bridge 目录不存在,请确保已拉取并安装依赖。") + if (!aiBridgeDir.exists() && !aiBridgeArchive.exists()) { + throw new GradleException('ai-bridge 目录不存在,请确保已拉取并安装依赖。') } def sandboxRoot = sandboxRootProvider.get().asFile def pluginDir = new File(sandboxRoot, "plugins/${pluginName}") - // 部署 ai-bridge - def extractedDir = new File(pluginDir, "ai-bridge") - def archiveTarget = new File(pluginDir, "ai-bridge.zip") + def extractedDir = new File(pluginDir, 'ai-bridge') + def archiveTarget = new File(pluginDir, 'ai-bridge.zip') project.delete(extractedDir) project.delete(archiveTarget) project.copy { @@ -136,6 +216,53 @@ tasks.named("prepareSandbox") { } } -tasks.named("buildPlugin") { - dependsOn("packageAiBridge") +// 配置 prepareSandbox 之后的任务,确保 ai-bridge.zip 被包含 +tasks.matching { it.name == 'preparePluginForDistribution' || it.name == 'composedJar' }.configureEach { + dependsOn('packageAiBridge') } + +tasks.named('buildPlugin') { + dependsOn('packageAiBridge') + + doLast { + // 解压插件 zip,添加 ai-bridge.zip,然后重新打包 + def distributionFile = archiveFile.get().asFile + if (distributionFile.exists() && aiBridgeArchive.exists()) { + def tempDir = file("${layout.buildDirectory.get()}/tmp/plugin-repack") + delete(tempDir) + + // 解压 + copy { + from zipTree(distributionFile) + into tempDir + } + + // 找到插件目录并添加 ai-bridge.zip + def pluginDir = new File(tempDir, pluginName) + if (pluginDir.exists()) { + copy { + from aiBridgeArchive + into pluginDir + } + + // 删除 searchableOptions jar(如果存在) + fileTree(new File(pluginDir, 'lib')).matching { + include '**/*searchableOptions*.jar' + }.each { jarFile -> + delete(jarFile) + println "已删除 searchableOptions jar: ${jarFile.name}" + } + + // 重新打包 + delete(distributionFile) + ant.zip(destfile: distributionFile) { + fileset(dir: tempDir) + } + println "已将 ai-bridge.zip 添加到插件包并移除 searchableOptions jar" + } + + delete(tempDir) + } + } +} + diff --git a/gradle.properties b/gradle.properties index 7fc6f1ff..44c22ed3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,4 @@ kotlin.code.style=official +# org.gradle.unsafe.configuration-cache=true +org.jetbrains.intellij.buildFeature.selfUpdateCheck=false + diff --git a/sandbox-idea.properties b/sandbox-idea.properties new file mode 100644 index 00000000..af3548a2 --- /dev/null +++ b/sandbox-idea.properties @@ -0,0 +1,5 @@ +idea.auto.reload.plugins=false +idea.is.internal=true +idea.plugin.in.sandbox.mode=true +idea.plugins.load.timeout=60000 + diff --git a/settings.gradle b/settings.gradle index 2721d753..2f423eb6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,9 @@ -rootProject.name = 'idea-claude-code-gui' \ No newline at end of file +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + + } +} + +rootProject.name = 'idea-claude-code-gui' diff --git a/src/main/java/com/github/claudecodegui/ClaudeSDKToolWindow.java b/src/main/java/com/github/claudecodegui/ClaudeSDKToolWindow.java index 882710a7..52405292 100644 --- a/src/main/java/com/github/claudecodegui/ClaudeSDKToolWindow.java +++ b/src/main/java/com/github/claudecodegui/ClaudeSDKToolWindow.java @@ -757,26 +757,48 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware { private void pushUsageUpdateFromMessages(List messages) { try { + System.out.println("[Backend] pushUsageUpdateFromMessages called with " + messages.size() + " messages"); + JsonObject lastUsage = null; for (int i = messages.size() - 1; i >= 0; i--) { ClaudeSession.Message msg = messages.get(i); - if (msg.type != ClaudeSession.Message.Type.ASSISTANT || msg.raw == null) continue; - if (!msg.raw.has("message")) continue; - JsonObject message = msg.raw.getAsJsonObject("message"); - if (message.has("usage")) { - lastUsage = message.getAsJsonObject("usage"); + + if (msg.type != ClaudeSession.Message.Type.ASSISTANT || msg.raw == null) { + continue; + } + + // 检查不同的可能结构 + if (msg.raw.has("message")) { + JsonObject message = msg.raw.getAsJsonObject("message"); + if (message.has("usage")) { + lastUsage = message.getAsJsonObject("usage"); + break; + } + } + + // 检查usage是否在raw的根级别 + if (msg.raw.has("usage")) { + lastUsage = msg.raw.getAsJsonObject("usage"); break; } } + if (lastUsage == null) { + System.out.println("[Backend] WARNING: No usage info found in messages!"); + } + int inputTokens = lastUsage != null && lastUsage.has("input_tokens") ? lastUsage.get("input_tokens").getAsInt() : 0; int cacheWriteTokens = lastUsage != null && lastUsage.has("cache_creation_input_tokens") ? lastUsage.get("cache_creation_input_tokens").getAsInt() : 0; int cacheReadTokens = lastUsage != null && lastUsage.has("cache_read_input_tokens") ? lastUsage.get("cache_read_input_tokens").getAsInt() : 0; + int outputTokens = lastUsage != null && lastUsage.has("output_tokens") ? lastUsage.get("output_tokens").getAsInt() : 0; - int usedTokens = inputTokens + cacheWriteTokens + cacheReadTokens; + int usedTokens = inputTokens + cacheWriteTokens + cacheReadTokens + outputTokens; int maxTokens = MODEL_CONTEXT_LIMITS.getOrDefault(currentModel, 200_000); int percentage = Math.min(100, maxTokens > 0 ? (int) ((usedTokens * 100.0) / maxTokens) : 0); + System.out.println("[Backend] Pushing usage update: input=" + inputTokens + ", cacheWrite=" + cacheWriteTokens + ", cacheRead=" + cacheReadTokens + ", output=" + outputTokens + ", total=" + usedTokens + ", max=" + maxTokens + ", percentage=" + percentage + "%"); + + JsonObject usageUpdate = new JsonObject(); usageUpdate.addProperty("percentage", percentage); usageUpdate.addProperty("totalTokens", usedTokens); @@ -786,13 +808,22 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware { String usageJson = new Gson().toJson(usageUpdate); SwingUtilities.invokeLater(() -> { - String js = "if (window.onUsageUpdate) { window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "'); }"; if (browser != null && !disposed) { + // 使用安全的调用方式,检查函数是否存在 + String js = "(function() {" + + " if (typeof window.onUsageUpdate === 'function') {" + + " window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "');" + + " console.log('[Backend->Frontend] Usage update sent successfully');" + + " } else {" + + " console.warn('[Backend->Frontend] window.onUsageUpdate not found');" + + " }" + + "})();"; browser.getCefBrowser().executeJavaScript(js, browser.getCefBrowser().getURL(), 0); } }); } catch (Exception e) { System.err.println("[Backend] Failed to push usage update: " + e.getMessage()); + e.printStackTrace(); } } @@ -842,8 +873,17 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware { usageUpdate.addProperty("maxTokens", maxTokens); String usageJson = new Gson().toJson(usageUpdate); - String js = "if (window.onUsageUpdate) { window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "'); }"; + if (browser != null && !disposed) { + // 使用安全的调用方式 + String js = "(function() {" + + " if (typeof window.onUsageUpdate === 'function') {" + + " window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "');" + + " console.log('[Backend->Frontend] Usage reset for new session');" + + " } else {" + + " console.warn('[Backend->Frontend] window.onUsageUpdate not found');" + + " }" + + "})();"; browser.getCefBrowser().executeJavaScript(js, browser.getCefBrowser().getURL(), 0); } }); @@ -879,14 +919,34 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware { private void callJavaScript(String functionName, String... args) { if (disposed || browser == null) { + System.err.println("[ClaudeSDKToolWindow] 无法调用 JS 函数 " + functionName + ": disposed=" + disposed + ", browser=" + (browser == null ? "null" : "exists")); return; } - try { - String js = JsUtils.buildJsCall(functionName, args); - browser.getCefBrowser().executeJavaScript(js, browser.getCefBrowser().getURL(), 0); - } catch (Exception e) { - System.err.println("[ClaudeSDKToolWindow] 调用 JS 函数失败: " + functionName + ", 错误: " + e.getMessage()); - } + + SwingUtilities.invokeLater(() -> { + if (disposed || browser == null) { + return; + } + try { + String js = JsUtils.buildJsCall(functionName, args); + + // 先检查函数是否存在,再调用 + String checkAndCall = + "(function() {" + + " if (typeof window." + functionName + " === 'function') {" + + " " + js + + " console.log('[Backend->Frontend] Successfully called " + functionName + "');" + + " } else {" + + " console.warn('[Backend->Frontend] Function " + functionName + " not found on window');" + + " }" + + "})();"; + + browser.getCefBrowser().executeJavaScript(checkAndCall, browser.getCefBrowser().getURL(), 0); + } catch (Exception e) { + System.err.println("[ClaudeSDKToolWindow] 调用 JS 函数失败: " + functionName + ", 错误: " + e.getMessage()); + e.printStackTrace(); + } + }); } /** diff --git a/src/main/java/com/github/claudecodegui/ClaudeSession.java b/src/main/java/com/github/claudecodegui/ClaudeSession.java index 57a9430f..3a5f1e00 100644 --- a/src/main/java/com/github/claudecodegui/ClaudeSession.java +++ b/src/main/java/com/github/claudecodegui/ClaudeSession.java @@ -546,6 +546,44 @@ public class ClaudeSession { loading = false; updateState(); System.out.println("[ClaudeSession] Message end received, loading set to false"); + } else if ("result".equals(type) && content.startsWith("{")) { + // 处理结果消息(包含最终的usage信息) + try { + JsonObject resultJson = gson.fromJson(content, JsonObject.class); + System.out.println("[ClaudeSession] Result message received"); + + // 如果当前消息的raw中usage为0,则用result中的usage进行更新 + if (currentAssistantMessage != null && currentAssistantMessage.raw != null) { + JsonObject message = currentAssistantMessage.raw.has("message") && currentAssistantMessage.raw.get("message").isJsonObject() + ? currentAssistantMessage.raw.getAsJsonObject("message") + : null; + + // 检查当前消息的usage是否全为0 + boolean needsUsageUpdate = false; + if (message != null && message.has("usage")) { + JsonObject usage = message.getAsJsonObject("usage"); + int inputTokens = usage.has("input_tokens") ? usage.get("input_tokens").getAsInt() : 0; + int outputTokens = usage.has("output_tokens") ? usage.get("output_tokens").getAsInt() : 0; + if (inputTokens == 0 && outputTokens == 0) { + needsUsageUpdate = true; + } + } else { + needsUsageUpdate = true; + } + + if (needsUsageUpdate && resultJson.has("usage")) { + JsonObject resultUsage = resultJson.getAsJsonObject("usage"); + if (message != null) { + message.add("usage", resultUsage); + currentAssistantMessage.raw = currentAssistantMessage.raw; + notifyMessageUpdate(); + System.out.println("[ClaudeSession] Updated assistant message usage from result message"); + } + } + } + } catch (Exception e) { + System.err.println("[ClaudeSession] Failed to parse result message: " + e.getMessage()); + } } else if ("slash_commands".equals(type)) { // 处理斜杠命令列表 try { diff --git a/src/main/java/com/github/claudecodegui/bridge/BridgeDirectoryResolver.java b/src/main/java/com/github/claudecodegui/bridge/BridgeDirectoryResolver.java index ff7be99e..41159355 100644 --- a/src/main/java/com/github/claudecodegui/bridge/BridgeDirectoryResolver.java +++ b/src/main/java/com/github/claudecodegui/bridge/BridgeDirectoryResolver.java @@ -158,14 +158,14 @@ public class BridgeDirectoryResolver { try { String pluginsRoot = PathManager.getPluginsPath(); - if (pluginsRoot != null && !pluginsRoot.isEmpty()) { + if (!pluginsRoot.isEmpty()) { addCandidate(possibleDirs, Paths.get(pluginsRoot, PLUGIN_DIR_NAME, SDK_DIR_NAME).toFile()); addCandidate(possibleDirs, Paths.get(pluginsRoot, PLUGIN_ID, SDK_DIR_NAME).toFile()); } // 使用系统路径下的 plugins 目录代替已废弃的 getPluginTempPath() String systemPath = PathManager.getSystemPath(); - if (systemPath != null && !systemPath.isEmpty()) { + if (!systemPath.isEmpty()) { Path sandboxPath = Paths.get(systemPath, "plugins"); addCandidate(possibleDirs, sandboxPath.resolve(PLUGIN_DIR_NAME).resolve(SDK_DIR_NAME).toFile()); addCandidate(possibleDirs, sandboxPath.resolve(PLUGIN_ID).resolve(SDK_DIR_NAME).toFile()); @@ -282,7 +282,55 @@ public class BridgeDirectoryResolver { } } } - return null; + + // 如果在插件目录或 lib 下找不到,尝试查找常见的 sandbox 顶级 plugins 目录和 system/config 下的 plugins + List fallbackCandidates = new ArrayList<>(); + try { + // 向上查找祖先,寻找可能的 idea-sandbox 根目录或包含顶级 plugins 的目录 + File ancestor = pluginDir; + int climbs = 0; + while (climbs < 6) { + File parent = ancestor.getParentFile(); + if (parent == null) break; + + File maybeTopPlugins = new File(parent, "plugins"); + if (maybeTopPlugins.exists() && maybeTopPlugins.isDirectory()) { + fallbackCandidates.add(new File(maybeTopPlugins, PLUGIN_DIR_NAME + File.separator + SDK_ARCHIVE_NAME)); + fallbackCandidates.add(new File(maybeTopPlugins, PLUGIN_ID + File.separator + SDK_ARCHIVE_NAME)); + } + + // system/config siblings under this parent + File maybeSystemPlugins = new File(parent, "system/plugins"); + File maybeConfigPlugins = new File(parent, "config/plugins"); + if (maybeSystemPlugins.exists() && maybeSystemPlugins.isDirectory()) { + fallbackCandidates.add(new File(maybeSystemPlugins, PLUGIN_DIR_NAME + File.separator + SDK_ARCHIVE_NAME)); + fallbackCandidates.add(new File(maybeSystemPlugins, PLUGIN_ID + File.separator + SDK_ARCHIVE_NAME)); + } + if (maybeConfigPlugins.exists() && maybeConfigPlugins.isDirectory()) { + fallbackCandidates.add(new File(maybeConfigPlugins, PLUGIN_DIR_NAME + File.separator + SDK_ARCHIVE_NAME)); + fallbackCandidates.add(new File(maybeConfigPlugins, PLUGIN_ID + File.separator + SDK_ARCHIVE_NAME)); + } + + ancestor = parent; + climbs++; + } + } catch (Throwable ignore) { + // ignore fallback discovery errors + } + + // 打印并尝试这些候选路径 + for (File f : fallbackCandidates) { + System.out.println("[BridgeResolver] 尝试候选路径: " + f.getAbsolutePath() + " (存在: " + f.exists() + ")"); + if (f.exists()) { + archiveFile = f; + break; + } + } + + if (!archiveFile.exists()) { + return null; + } + } File extractedDir = new File(pluginDir, SDK_DIR_NAME); @@ -372,26 +420,27 @@ public class BridgeDirectoryResolver { } /** - * 手动设置 claude-bridge 目录路径 + * 手动设置 claude-bridge 目录路径. */ public void setSdkDir(String path) { this.cachedSdkDir = new File(path); } /** - * 获取当前使用的 claude-bridge 目录 + * 获取当前使用的 claude-bridge 目录. */ public File getSdkDir() { - if (cachedSdkDir == null) { - return findSdkDir(); + if (this.cachedSdkDir == null) { + return this.findSdkDir(); } - return cachedSdkDir; + return this.cachedSdkDir; } /** - * 清除缓存 + * 清除缓存. */ public void clearCache() { this.cachedSdkDir = null; } } + diff --git a/webview/scripts/extract-version.mjs b/webview/scripts/extract-version.mjs index c6405d6a..cb085f5d 100644 --- a/webview/scripts/extract-version.mjs +++ b/webview/scripts/extract-version.mjs @@ -16,14 +16,14 @@ const buildGradlePath = path.join(projectRoot, 'build.gradle'); const buildGradleContent = fs.readFileSync(buildGradlePath, 'utf8'); // 提取版本号 -// 查找类似 version '0.1.0-beta3' 这样的行 -let versionMatch = buildGradleContent.match(/^version\s+'(.+)'$/m); +// 查找类似 version = '0.1.0-beta3' 这样的行 +let versionMatch = buildGradleContent.match(/^version\s*=\s*'(.+)'$/m); if (!versionMatch) { // 如果上面的正则失败,尝试另一种方式 const lines = buildGradleContent.split('\n'); - const versionLine = lines.find(line => line.trim().startsWith('version ')); + const versionLine = lines.find(line => line.trim().startsWith('version =')); if (versionLine) { - const match = versionLine.match(/version\s+'(.+)'/); + const match = versionLine.match(/version\s*=\s*'(.+)'/); if (match) { versionMatch = match; }