mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
fix(settings): surface "installed but not runnable" state in tool cards
After an install/update command succeeds, re-probe the tool version: if it still can't be detected the tool is installed-but-unrunnable (e.g. Node too old), so report a warning toast and show "installed · can't run" with the cause, instead of a misleading "success" plus a stale version number. Read the backend `installed_but_broken` flag rather than matching error text; the card's current-version line, action button (no useless "install" for a broken tool), and a "check environment" hint all key off it. Drop the now-redundant version-clearing setState (the refresh merge already nulls it), flatten the action ternary, and use extractErrorMessage in the catch path. Adds toolNotRunnable / toolActionInstalledNotRunnable / installedNotRunnable / toolCheckEnv i18n keys (zh/en/ja).
This commit is contained in:
@@ -33,6 +33,7 @@ import { motion } from "framer-motion";
|
||||
import appIcon from "@/assets/icons/app-icon.png";
|
||||
import { APP_ICON_MAP } from "@/config/appConfig";
|
||||
import type { AppId } from "@/lib/api/types";
|
||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||
|
||||
interface AboutSectionProps {
|
||||
isPortable: boolean;
|
||||
@@ -43,6 +44,9 @@ interface ToolVersion {
|
||||
version: string | null;
|
||||
latest_version: string | null;
|
||||
error: string | null;
|
||||
// 后端已定位到可执行文件但 --version 报错(装了却跑不起来)。直接读此字段,
|
||||
// 不要靠匹配 error 文案反推——避免前端与后端字符串硬耦合。
|
||||
installed_but_broken: boolean;
|
||||
env_type: "windows" | "wsl" | "macos" | "linux" | "unknown";
|
||||
wsl_distro: string | null;
|
||||
}
|
||||
@@ -175,8 +179,8 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
async (
|
||||
toolNames: ToolName[],
|
||||
wslOverrides?: Record<string, WslShellPreference>,
|
||||
) => {
|
||||
if (toolNames.length === 0) return;
|
||||
): Promise<ToolVersion[]> => {
|
||||
if (toolNames.length === 0) return [];
|
||||
|
||||
// 单工具刷新使用统一后端入口(get_tool_versions)并带工具过滤。
|
||||
setLoadingTools((prev) => {
|
||||
@@ -201,8 +205,12 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
}
|
||||
return merged;
|
||||
});
|
||||
|
||||
// 返回刷新结果,调用方可据此判断版本是否真的探到(避免读 state 撞 stale closure)。
|
||||
return updated;
|
||||
} catch (error) {
|
||||
console.error("[AboutSection] Failed to refresh tools", error);
|
||||
return [];
|
||||
} finally {
|
||||
setLoadingTools((prev) => {
|
||||
const next = { ...prev };
|
||||
@@ -379,7 +387,10 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
|
||||
// 逐工具串行执行:每个工具独立成败、独立刷新版本,一个失败不会连坐
|
||||
// 后续工具(后端把整批拼成单脚本 + set -e,会在首个失败处中止整批)。
|
||||
const failures: { toolName: ToolName; detail: string }[] = [];
|
||||
// soft=true 表示"命令成功执行但版本探不到"(装上却跑不起来),
|
||||
// 与命令本身报错(soft=false)区别对待:前者不算硬失败,toast 降级为 warning。
|
||||
const failures: { toolName: ToolName; detail: string; soft: boolean }[] =
|
||||
[];
|
||||
let succeeded = 0;
|
||||
|
||||
for (const toolName of toolNames) {
|
||||
@@ -391,15 +402,27 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
wslShellByTool,
|
||||
);
|
||||
// 静默执行真正结束后刷新该工具版本,卡片立即反映结果。
|
||||
await refreshToolVersions([toolName], wslShellByTool);
|
||||
succeeded += 1;
|
||||
const refreshed = await refreshToolVersions(
|
||||
[toolName],
|
||||
wslShellByTool,
|
||||
);
|
||||
const tool = refreshed.find((t) => t.name === toolName);
|
||||
if (tool?.version) {
|
||||
succeeded += 1;
|
||||
} else {
|
||||
// 命令退出码为 0、但刷新后仍探不到版本:多半是"装上了却跑不起来"
|
||||
// (如 openclaw 要求更高的 Node 版本)。refreshToolVersions 的 merge 已把
|
||||
// version 置空并写入后端 error,这里只需归类为软失败并展示原因。
|
||||
const detail = tool?.error?.trim() || t("settings.toolNotRunnable");
|
||||
failures.push({ toolName, detail, soft: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[AboutSection] Failed to run tool action for ${toolName}`,
|
||||
error,
|
||||
);
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
failures.push({ toolName, detail });
|
||||
const detail = extractErrorMessage(error) || String(error);
|
||||
failures.push({ toolName, detail, soft: false });
|
||||
} finally {
|
||||
setToolActions((prev) => {
|
||||
const next = { ...prev };
|
||||
@@ -442,7 +465,15 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
.join("\n")
|
||||
: failures[0]?.detail;
|
||||
|
||||
if (succeeded === 0) {
|
||||
const hardFailures = failures.filter((f) => !f.soft);
|
||||
|
||||
if (succeeded === 0 && hardFailures.length === 0) {
|
||||
// 命令均成功执行、但工具都探不到版本(装上却跑不起来)→ 降级为 warning 并解释原因。
|
||||
toast.warning(t("settings.toolActionInstalledNotRunnable"), {
|
||||
description: failureDescription || undefined,
|
||||
closeButton: true,
|
||||
});
|
||||
} else if (succeeded === 0) {
|
||||
toast.error(t("settings.toolActionFailed"), {
|
||||
description: failureDescription || undefined,
|
||||
closeButton: true,
|
||||
@@ -655,13 +686,18 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
tool.latest_version &&
|
||||
tool.version !== tool.latest_version,
|
||||
);
|
||||
const action = isToolVersionLoading
|
||||
? null
|
||||
: !tool?.version
|
||||
? "install"
|
||||
: isOutdated
|
||||
? "update"
|
||||
: null;
|
||||
// 已安装却跑不起来(如 Node 版本不达标):用它区分卡片文案与按钮,避免把
|
||||
// "装了跑不起来"误判成"未安装"而给出无用的安装按钮(重装同一版本解决不了)。
|
||||
const installedButBroken = Boolean(tool?.installed_but_broken);
|
||||
// loading 和 broken 都没有可执行动作;其余按是否已装/是否过期选择。
|
||||
const action: ToolLifecycleAction | null =
|
||||
isToolVersionLoading || installedButBroken
|
||||
? null
|
||||
: !tool?.version
|
||||
? "install"
|
||||
: isOutdated
|
||||
? "update"
|
||||
: null;
|
||||
const runningAction = toolActions[toolName];
|
||||
const title = tool?.version || tool?.error || t("common.unknown");
|
||||
|
||||
@@ -718,7 +754,11 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
>
|
||||
{isToolVersionLoading
|
||||
? t("common.loading")
|
||||
: tool?.version || t("common.notInstalled")}
|
||||
: tool?.version
|
||||
? tool.version
|
||||
: installedButBroken
|
||||
? t("settings.installedNotRunnable")
|
||||
: t("common.notInstalled")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
@@ -788,6 +828,11 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("common.loading")}
|
||||
</span>
|
||||
) : installedButBroken ? (
|
||||
// 已安装但跑不起来:重装无济于事,不给按钮,给一句指向环境的提示。
|
||||
<span className="text-xs text-yellow-600 dark:text-yellow-400">
|
||||
{t("settings.toolCheckEnv")}
|
||||
</span>
|
||||
) : action ? (
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -672,6 +672,10 @@
|
||||
"toolActionDone": "{{action}} completed for {{count}} tool(s)",
|
||||
"toolActionPartial": "{{action}} succeeded for {{succeeded}}, failed for {{failed}}",
|
||||
"toolActionFailed": "Install/update command failed",
|
||||
"toolNotRunnable": "Installed but not runnable (no version detected)",
|
||||
"toolActionInstalledNotRunnable": "Installed, but it can't run in the current environment — please check",
|
||||
"installedNotRunnable": "Installed · can't run",
|
||||
"toolCheckEnv": "Check environment",
|
||||
"envBadge": {
|
||||
"wsl": "WSL",
|
||||
"windows": "Win",
|
||||
|
||||
@@ -672,6 +672,10 @@
|
||||
"toolActionDone": "{{count}} 個のツールの{{action}}が完了しました",
|
||||
"toolActionPartial": "{{succeeded}} 個成功、{{failed}} 個失敗({{action}})",
|
||||
"toolActionFailed": "インストール/更新コマンドの実行に失敗しました",
|
||||
"toolNotRunnable": "インストール済みですが実行できません(バージョン未検出)",
|
||||
"toolActionInstalledNotRunnable": "インストールされましたが、現在の環境では実行できません。確認してください",
|
||||
"installedNotRunnable": "インストール済み・実行不可",
|
||||
"toolCheckEnv": "実行環境を確認",
|
||||
"envBadge": {
|
||||
"wsl": "WSL",
|
||||
"windows": "Win",
|
||||
|
||||
@@ -672,6 +672,10 @@
|
||||
"toolActionDone": "{{count}} 个工具{{action}}完成",
|
||||
"toolActionPartial": "{{succeeded}} 个{{action}}成功,{{failed}} 个失败",
|
||||
"toolActionFailed": "安装/升级命令执行失败",
|
||||
"toolNotRunnable": "已安装但无法运行(未探测到版本)",
|
||||
"toolActionInstalledNotRunnable": "已安装,但当前环境无法运行,请检查",
|
||||
"installedNotRunnable": "已安装·无法运行",
|
||||
"toolCheckEnv": "请检查运行环境",
|
||||
"envBadge": {
|
||||
"wsl": "WSL",
|
||||
"windows": "Win",
|
||||
|
||||
@@ -185,6 +185,7 @@ export const settingsApi = {
|
||||
version: string | null;
|
||||
latest_version: string | null;
|
||||
error: string | null;
|
||||
installed_but_broken: boolean;
|
||||
env_type: "windows" | "wsl" | "macos" | "linux" | "unknown";
|
||||
wsl_distro: string | null;
|
||||
}>
|
||||
|
||||
Reference in New Issue
Block a user