Merge remote-tracking branch 'origin/main' into codex/webui-segmented-transcript-store

This commit is contained in:
Xubin Ren
2026-06-10 19:11:37 +08:00
34 changed files with 1022 additions and 56 deletions
@@ -10,6 +10,7 @@ import {
} from "react";
import {
Activity,
ArrowUpCircle,
Bot,
Brain,
Check,
@@ -22,6 +23,7 @@ import {
Database,
Eye,
EyeOff,
ExternalLink,
Gem,
Globe2,
Grid3X3,
@@ -75,6 +77,7 @@ import {
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
checkVersion,
createModelConfiguration,
fetchSettings,
fetchSettingsUsage,
@@ -1852,6 +1855,104 @@ function OverviewSettings({
/>
</SettingsGroup>
</section>
<section>
<SettingsSectionTitle>{tx("settings.sections.about", "About")}</SettingsSectionTitle>
<SettingsGroup>
<VersionCheckRow currentVersion={settings.version?.current} />
</SettingsGroup>
</section>
</div>
);
}
function VersionCheckRow({ currentVersion }: { currentVersion?: string }) {
const { t } = useTranslation();
const tx = (key: string, fallback: string) => t(key, { defaultValue: fallback });
const { token } = useClient();
const [checking, setChecking] = useState(false);
const [result, setResult] = useState<
| { type: "up-to-date" }
| { type: "update"; latestVersion: string; pypiUrl?: string }
| { type: "error"; message: string }
| null
>(null);
const handleCheck = async () => {
setChecking(true);
setResult(null);
try {
const res = await checkVersion(token);
if (res.updateAvailable) {
setResult({
type: "update",
latestVersion: res.updateAvailable.latestVersion,
pypiUrl: res.updateAvailable.pypiUrl,
});
} else {
setResult({ type: "up-to-date" });
}
} catch (err) {
setResult({ type: "error", message: (err as Error).message });
} finally {
setChecking(false);
}
};
return (
<div className="flex min-h-[62px] flex-col gap-3 px-4 py-3.5 sm:flex-row sm:items-center sm:justify-between sm:px-5">
<div className="min-w-0">
<div className="text-[14px] font-medium leading-5 text-foreground">
{tx("settings.about.version", "Version")}
</div>
<div className="mt-0.5 text-[12px] leading-5 text-muted-foreground">
{currentVersion ? `v${currentVersion}` : "nanobot"}
</div>
</div>
<div className="flex shrink-0 flex-col items-end gap-2">
<Button
size="sm"
variant="outline"
onClick={() => void handleCheck()}
disabled={checking}
className="rounded-full"
>
{checking ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" aria-hidden />
) : (
<ArrowUpCircle className="mr-1.5 h-3.5 w-3.5" aria-hidden />
)}
{checking
? tx("settings.about.checking", "Checking...")
: tx("settings.about.checkForUpdates", "Check for updates")}
</Button>
{result?.type === "up-to-date" ? (
<span className="inline-flex items-center gap-1.5 text-[12px] text-emerald-600 dark:text-emerald-300">
<Check className="h-3 w-3" aria-hidden />
{tx("settings.about.upToDate", "You're up to date")}
</span>
) : null}
{result?.type === "update" ? (
<span className="inline-flex items-center gap-1.5 text-[12px] text-blue-600 dark:text-blue-300">
<ArrowUpCircle className="h-3 w-3" aria-hidden />
{tx("settings.about.updateAvailable", "Update available")}{result.latestVersion && ` v${result.latestVersion}`}
{result.pypiUrl ? (
<a
href={result.pypiUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-0.5 underline-offset-2 hover:underline"
>
PyPI
<ExternalLink className="h-2.5 w-2.5" aria-hidden />
</a>
) : null}
</span>
) : null}
{result?.type === "error" ? (
<span className="text-[12px] text-destructive">{result.message}</span>
) : null}
</div>
</div>
);
}
+20
View File
@@ -244,6 +244,26 @@ export async function fetchSettingsUsage(
);
}
export interface VersionCheckResult {
updateAvailable: {
currentVersion: string;
latestVersion: string;
pypiUrl?: string;
} | null;
}
export async function checkVersion(
token: string,
base: string = "",
): Promise<VersionCheckResult> {
return request<VersionCheckResult>(
`${base}/api/settings/version-check`,
token,
undefined,
10_000,
);
}
export async function fetchWorkspaces(
token: string,
base: string = "",
+4
View File
@@ -480,10 +480,14 @@ export interface SettingsPayload {
mcp_server_count: number;
exec_enabled: boolean;
exec_sandbox?: string | null;
exec_path_prepend_set: boolean;
exec_path_append_set: boolean;
};
requires_restart: boolean;
restart_required_sections?: Array<"runtime" | "browser" | "image">;
version?: {
current: string;
};
}
export interface AppPackageRef {
+3
View File
@@ -125,6 +125,7 @@ function baseSettingsPayload() {
mcp_server_count: 0,
exec_enabled: true,
exec_sandbox: null,
exec_path_prepend_set: false,
exec_path_append_set: false,
},
requires_restart: false,
@@ -1023,6 +1024,7 @@ describe("App layout", () => {
mcp_server_count: 0,
exec_enabled: true,
exec_sandbox: null,
exec_path_prepend_set: false,
exec_path_append_set: false,
},
requires_restart: false,
@@ -1349,6 +1351,7 @@ describe("App layout", () => {
mcp_server_count: 0,
exec_enabled: true,
exec_sandbox: null,
exec_path_prepend_set: false,
exec_path_append_set: false,
},
requires_restart: false,
+1
View File
@@ -93,6 +93,7 @@ function settingsPayload(): SettingsPayload {
mcp_server_count: 0,
exec_enabled: true,
exec_sandbox: null,
exec_path_prepend_set: false,
exec_path_append_set: false,
},
requires_restart: false,
+1
View File
@@ -212,6 +212,7 @@ function modelSettings(model: string, provider: string): SettingsPayload {
mcp_server_count: 0,
exec_enabled: true,
exec_sandbox: null,
exec_path_prepend_set: false,
exec_path_append_set: false,
},
requires_restart: false,