diff --git a/integrations/dsh/src/client/env-tab.tsx b/integrations/dsh/src/client/env-tab.tsx
index 18a3efe4..b556020e 100644
--- a/integrations/dsh/src/client/env-tab.tsx
+++ b/integrations/dsh/src/client/env-tab.tsx
@@ -21,6 +21,9 @@ import type { StatusRpc } from "./status-page.js";
const MINICONDA_URL = "https://docs.conda.io/en/latest/miniconda.html";
+/** Interpreter rows shown before the list asks to expand. */
+const ENV_PAGE = 6;
+
export const envEn = {
connectionTitle: "Service connection",
endpointLabel: "Service URL",
@@ -138,8 +141,39 @@ export const envEn = {
serviceError: "Service operation failed",
configTitle: "ReMe parameters",
controlTitle: "Service control",
- upcoming:
- "This section arrives with the next milestone of the environment feature.",
+ heroEyebrow: "ReMe local runtime",
+ heroReady: "Ready — memory runs locally",
+ heroSetup: "Finish the setup in the steps below",
+ heroService: "Service",
+ heroPython: "Python",
+ heroReme: "ReMe",
+ noneSelected: "not selected",
+ noneDetected: "not detected",
+ envCount: "{count} environments",
+ envRemeCount: "{count} with ReMe",
+ envOthers: "{count} other environments",
+ scannedAt: "Detected at {time}",
+ currentInterpreter: "Current interpreter",
+ notInDetection: "not detected",
+ autoSelected:
+ "The recommended interpreter is used until you pick another one.",
+ savingSelection: "Saving…",
+ changeInterpreter: "Change interpreter",
+ collapseList: "Collapse list",
+ searchPlaceholder: "Filter by version, origin or path",
+ noMatch: "No environment matches the filter.",
+ showAllEnvs: "Show all {count}",
+ showFewer: "Show fewer",
+ depReady: "Ready",
+ depUpgrade: "Upgrade available",
+ depMissing: "Not installed",
+ expand: "Expand",
+ collapse: "Collapse",
+ recommendWhyReme:
+ "Already has a supported reme-ai, and it is a dedicated conda environment.",
+ recommendWhyRemeAny: "Already has a supported reme-ai.",
+ recommendWhyNamed: "Dedicated conda environment, ready for a ReMe install.",
+ recommendWhyFallback: "Fallback: the first usable interpreter.",
};
export const envZh: typeof envEn = {
@@ -252,7 +286,37 @@ export const envZh: typeof envEn = {
serviceError: "服务操作失败",
configTitle: "ReMe 参数",
controlTitle: "服务控制",
- upcoming: "该分区将随环境管理特性的后续里程碑提供。",
+ heroEyebrow: "ReMe 本地运行时",
+ heroReady: "已就绪,记忆保存在本地",
+ heroSetup: "按下方步骤完成配置",
+ heroService: "服务",
+ heroPython: "Python",
+ heroReme: "ReMe",
+ noneSelected: "未选择",
+ noneDetected: "未探测",
+ envCount: "{count} 个环境",
+ envRemeCount: "{count} 个已装 ReMe",
+ envOthers: "另有 {count} 个环境",
+ scannedAt: "探测于 {time}",
+ currentInterpreter: "当前解释器",
+ notInDetection: "不在探测结果中",
+ autoSelected: "当前使用推荐解释器;选择其他解释器后会写入配置。",
+ savingSelection: "正在保存…",
+ changeInterpreter: "更换解释器",
+ collapseList: "收起列表",
+ searchPlaceholder: "按版本、来源或路径筛选",
+ noMatch: "没有匹配的环境。",
+ showAllEnvs: "显示全部 {count} 个",
+ showFewer: "收起多余项",
+ depReady: "就绪",
+ depUpgrade: "可升级",
+ depMissing: "未安装",
+ expand: "展开",
+ collapse: "收起",
+ recommendWhyReme: "已安装可用的 reme-ai,且是独立的 conda 环境。",
+ recommendWhyRemeAny: "已安装可用的 reme-ai。",
+ recommendWhyNamed: "独立的 conda 环境,适合安装 ReMe。",
+ recommendWhyFallback: "兜底选择:第一个可用的解释器。",
};
export type EnvTranslator = (key: keyof typeof envEn) => string;
@@ -369,6 +433,85 @@ function originLabel(t: EnvTranslator, origin: PythonOrigin): string {
}
}
+interface EnvBadge {
+ tone: "" | "ok" | "bad" | "progress";
+ text: string;
+}
+
+/** Human-readable reason behind the "recommended" marker. */
+function recommendReason(t: EnvTranslator, env: PythonEnv): string {
+ const named = env.origin.kind === "conda" && env.origin.envName !== "base";
+ if (env.reme?.installed)
+ return t(named ? "recommendWhyReme" : "recommendWhyRemeAny");
+ return t(named ? "recommendWhyNamed" : "recommendWhyFallback");
+}
+
+function connectionBadge(
+ t: EnvTranslator,
+ health: HealthState,
+ current: boolean,
+): EnvBadge {
+ const label = (detail: string) => `${t("heroService")} · ${detail}`;
+ if (!current || health.phase === "loading")
+ return { tone: "", text: label(t("checking")) };
+ if (health.phase === "ok")
+ return {
+ tone: "ok",
+ text: label(health.version ? `ReMe ${health.version}` : t("connected")),
+ };
+ return { tone: "bad", text: label(t("disconnected")) };
+}
+
+function interpreterBadge(
+ t: EnvTranslator,
+ env: PythonEnv | undefined,
+ phase: DetectState["phase"],
+): EnvBadge {
+ if (!env)
+ return {
+ tone: "",
+ text: `${t("heroPython")} · ${t(
+ phase === "error" ? "noneDetected" : "noneSelected",
+ )}`,
+ };
+ return {
+ tone: env.ok ? "ok" : "progress",
+ text: `${t("heroPython")} ${env.version || "?"}`,
+ };
+}
+
+function remeSummaryBadge(
+ t: EnvTranslator,
+ check: RemeCheck | undefined,
+ checking: boolean,
+): EnvBadge {
+ if (!check)
+ return {
+ tone: "",
+ text: `${t("heroReme")} · ${t(checking ? "checking" : "noneDetected")}`,
+ };
+ if (!check.installed)
+ return {
+ tone: "progress",
+ text: `${t("heroReme")} · ${t("notInstalled")}`,
+ };
+ return {
+ tone: check.versionOk && check.hasCore ? "ok" : "progress",
+ text: `${t("heroReme")} ${check.version}`,
+ };
+}
+
+function dependencyBadge(
+ t: EnvTranslator,
+ check: RemeCheck | undefined,
+): EnvBadge {
+ if (!check) return { tone: "", text: "" };
+ if (!check.installed) return { tone: "progress", text: t("depMissing") };
+ return check.versionOk && check.hasCore
+ ? { tone: "ok", text: t("depReady") }
+ : { tone: "progress", text: t("depUpgrade") };
+}
+
function elapsedText(job: InstallStatus, now: number): string {
const started = Date.parse(job.startedAt);
if (!Number.isFinite(started) || now < started) return "";
@@ -428,6 +571,10 @@ export function ReMeEnvironmentTab({
lines: "",
logPath: "",
});
+ const [listOpen, setListOpen] = useState(false);
+ const [envQuery, setEnvQuery] = useState("");
+ const [showAllEnvs, setShowAllEnvs] = useState(false);
+ const [paramsOpen, setParamsOpen] = useState(false);
// Derived values must precede hook dependency arrays.
const detected = detection.phase === "ready" ? detection.data : undefined;
@@ -924,29 +1071,88 @@ export function ReMeEnvironmentTab({
}
};
+ const selectedEnv = detected?.envs.find((env) => env.exe === selectedPath);
+ const others = (detected?.envs ?? []).filter(
+ (env) => env.exe !== selectedPath,
+ );
+ const filter = envQuery.trim().toLowerCase();
+ const matches = filter
+ ? others.filter((env) =>
+ `${env.version} ${env.exe} ${originLabel(t, env.origin)} ${env.source}`
+ .toLowerCase()
+ .includes(filter),
+ )
+ : others;
+ const visibleEnvs = showAllEnvs ? matches : matches.slice(0, ENV_PAGE);
+ const hiddenCount = matches.length - visibleEnvs.length;
+ const remeCount = (detected?.envs ?? []).filter(
+ (env) => env.reme?.installed === true,
+ ).length;
+ const scannedAt = detected?.scannedAt
+ ? Date.parse(detected.scannedAt)
+ : Number.NaN;
+ const scannedTime = Number.isFinite(scannedAt)
+ ? new Date(scannedAt).toLocaleTimeString()
+ : "";
+ const ready =
+ healthCurrent &&
+ health.phase === "ok" &&
+ checkData?.installed === true &&
+ checkData.versionOk;
+ const llmModel = envValue("LLM_MODEL_NAME").trim();
+ const envSummaryLine =
+ env.phase === "ready" && env.data
+ ? `${env.data.exists ? env.data.envPath : t("envMissing")}${
+ llmModel ? ` · ${llmModel}` : ""
+ }`
+ : t("envLoading");
+ const { tone: healthTone, text: healthText } = connectionBadge(
+ t,
+ health,
+ healthCurrent,
+ );
+ const { tone: pythonTone, text: pythonText } = interpreterBadge(
+ t,
+ selectedEnv,
+ detection.phase,
+ );
+ const { tone: remeTone, text: remeText } = remeSummaryBadge(
+ t,
+ checkData,
+ checking,
+ );
+ const { tone: depTone, text: depText } = dependencyBadge(t, checkData);
+
return (
-
- {t("connectionTitle")}
-
-
{t("endpointLabel")}
-
{endpoint}
- {!healthCurrent ? (
-
-
- {t("checking")}
-
- ) : health.phase === "ok" ? (
-
-
- {health.version ? `ReMe ${health.version}` : t("connected")}
-
- ) : (
-
-
- {t("disconnected")}
-
- )}
+
+
+
+ RE
+
+
+
{t("heroEyebrow")}
+
{t(ready ? "heroReady" : "heroSetup")}
+
+ {endpoint}
+
+
+
+
+
+
+ {healthText}
+
+
+
+ {pythonText}
+
+
+
+ {remeText}
+
+
+
+ {local ? (
+
+ ) : null}
+ {scannedTime ? (
+
+ {t("scannedAt").replace("{time}", scannedTime)}
+
+ ) : null}
- {healthCurrent && health.phase === "fail" && health.error ? (
- {health.error}
- ) : null}
+ {healthCurrent && health.phase === "fail" && health.error ? (
+
+ {health.error}
+
+ ) : null}
{!local ? (
{t("remoteNotice")}
) : (
<>
-
- {t("pythonTitle")}
- {t("pythonHint")}
-
- {detection.phase === "loading" ? (
- {t("detecting")}
- ) : null}
-
-
- {detection.phase === "error" ? (
-
- {t("detectFailed")}
- {detection.error ? `: ${detection.error}` : ""}
-
- ) : null}
- {detection.phase === "error" ||
- (detected && detected.envs.length === 0) ? (
-
-
{t("emptyTitle")}
-
{t("emptyHint")}
-
- {t("miniconda")}
-
+
+
+
+ 1
+
+
+
{t("pythonTitle")}
+
{t("pythonHint")}
- ) : null}
- {detected && detected.envs.length > 0 ? (
-
- {detected.envs.map((env) => (
-
- ))}
-
- ) : null}
- {detected && detected.errors.length > 0 ? (
-
- {t("errorsTitle")}
- {detected.errors.join("\n")}
-
- ) : null}
-
-
- {t("dependencyTitle")}
- {t("dependencyHint")}
-
- {t("interpreterLabel")}
-
- {selectedPath || t("noInterpreter")}
-
-
-
- {!selectedPath ? (
-
- {t("selectInterpreterFirst")}
-
- ) : checking ? (
- {t("checkingReme")}
- ) : null}
- {checkError ? (
-
- {t("checkRemeFailed")}
- {checkError ? `: ${checkError}` : ""}
-
- ) : null}
- {checkData ? (
-
-
- {t("remeLabel")}
- {checkData.installed ? (
-
-
- {`reme-ai ${checkData.version}`}
- {checkData.versionOk
- ? ""
- : ` · ${t("upgradeRecommended")}`}
-
- ) : (
-
-
- {t("notInstalled")}
-
- )}
-
-
- {t("coreLabel")}
- {checkData.installed ? (
-
-
- {checkData.hasCore ? t("coreOk") : t("coreMissing")}
-
- ) : (
- —
- )}
-
-
- {t("cliLabel")}
- {checkData.installed ? (
-
-
- {checkData.cliPath || t("cliFallback")}
-
- ) : (
- —
- )}
-
-
- ) : null}
- {checkData && install.phase !== "running" ? (
-
-
- {confirming ? (
-
-
- {t("installConfirm").replace(
- "{path}",
- selectedPath || "",
- )}
-
-
-
-
- ) : checkData.installed && checkData.versionOk ? (
-
- ) : checkData.installed ? (
-
- ) : (
-
- )}
-
- {t("condaHint")}
-
-
- ) : null}
- {install.phase === "running" && install.job ? (
-
-
-
-
- {t("installing")}
- {elapsedText(install.job, now || Date.now())
- ? ` · ${elapsedText(install.job, now || Date.now())}`
+ {detected && detected.envs.length > 0 ? (
+
+ ) : null}
+
+
+ {detection.phase === "loading" ? (
+
{t("detecting")}
+ ) : null}
+ {detection.phase === "error" ? (
+
+ {t("detectFailed")}
+ {detection.error ? `: ${detection.error}` : ""}
+
+ ) : null}
+ {detection.phase === "error" ||
+ (detected && detected.envs.length === 0) ? (
+
-
- {install.job.logTail || t("waitingForOutput")}
-
+ ) : null}
+ {selectedEnv ? (
+
+
+
+
+ {`Python ${selectedEnv.version || "?"}`}
+
+ {originLabel(t, selectedEnv.origin)}
+
+ {selectedEnv.reme?.installed ? (
+ {`ReMe ${selectedEnv.reme.version}`}
+ ) : null}
+ {selectedEnv.exe === detected?.suggested ? (
+
+ {t("recommended")}
+
+ ) : null}
+ {!selectedEnv.ok ? (
+ {t("tooOld")}
+ ) : null}
+
+
+ {selectedEnv.exe}
+
+
+ {savingExe === selectedEnv.exe ? (
+
+ {t("savingSelection")}
+
+ ) : null}
+
+ ) : selectedExe ? (
+
+
+
+
+ {t("currentInterpreter")}
+
+ {t("notInDetection")}
+
+
+
+ {selectedExe}
+
+
+
+ ) : null}
+ {!selectedExe && suggestedExe ? (
+
+ {t("autoSelected")}
+
+ ) : null}
+ {others.length > 0 ? (
+
+
+
+ {listOpen ? (
+ {
+ setEnvQuery(event.target.value);
+ setShowAllEnvs(false);
+ }}
+ />
+ ) : (
+
+ {t("envOthers").replace(
+ "{count}",
+ String(others.length),
+ )}
+
+ )}
+
+ {listOpen ? (
+ <>
+ {visibleEnvs.length > 0 ? (
+
+ {visibleEnvs.map((env) => (
+
+ ))}
+
+ ) : (
+
{t("noMatch")}
+ )}
+ {hiddenCount > 0 ? (
+
+ ) : matches.length > ENV_PAGE ? (
+
+ ) : null}
+ >
+ ) : null}
+
+ ) : null}
+ {detected && detected.errors.length > 0 ? (
+
+ {t("errorsTitle")}
+ {detected.errors.join("\n")}
+
+ ) : null}
+
+
+
+
+
+ 2
+
+
+
{t("dependencyTitle")}
+
{t("dependencyHint")}
- ) : null}
- {install.phase === "done" ? (
-
- {install.job?.phase === "succeeded" ? (
-
- {t("installSucceeded")}
-
- ) : null}
- {install.job?.phase === "cancelled" ? (
-
{t("installCancelled")}
- ) : null}
- {install.job?.phase === "failed" ? (
- <>
-
{t("installFailed")}
- {install.job.logTail ? (
-
{install.job.logTail}
- ) : null}
-
{install.job.logPath}
- >
- ) : null}
- {install.error ? (
-
{install.error}
- ) : null}
+ {depTone ? (
+
+ ) : null}
+
+
+
+
+ {t("interpreterLabel")}
+
+
+ {selectedPath || t("noInterpreter")}
+
- ) : null}
-
-
- {t("configTitle")}
- {t("configHint")}
-
-
-
- setWorkspaceInput(event.target.value)}
- />
-
-
-
-
- {env.phase === "loading" ? (
- {t("envLoading")}
- ) : null}
- {env.phase === "error" ? (
-
- {t("saveEnvFailed")}
- {env.error ? `: ${env.error}` : ""}
-
- ) : null}
- {env.phase === "ready" && env.data ? (
- <>
+ {!selectedPath ? (
- {env.data.exists
- ? `${t("envPath")}: ${env.data.envPath}`
- : t("envMissing")}
+ {t("selectInterpreterFirst")}
-
-
{t("llmGroup")}
-
-