feat(core,server,web,cli): show version in the web UI with update check and admin self-update (#74)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-27 23:34:06 +08:00
committed by GitHub
parent 033d526f9f
commit e1141ca010
27 changed files with 1375 additions and 40 deletions
@@ -40,8 +40,10 @@ import type {
} from "@prismshadow/penguin-server/api";
import * as api from "../../api/endpoints";
import { S } from "../../lib/strings";
import { formatMonthDay } from "../../lib/format";
import { apiErrorText } from "../../lib/api-error";
import { useAuth } from "../../state/auth";
import { useLocale } from "../../state/locale";
import { agentDisplayName, useProject } from "../../state/project";
import { useSessions } from "../../state/sessions";
import { AgentAvatar } from "../../components/ui/agent-avatar";
@@ -49,6 +51,7 @@ import { Chevron } from "../../components/ui/chevron";
import { Dropdown } from "../../components/ui/dropdown";
import { PenguinLogo } from "../../components/ui/penguin-logo";
import { toastError } from "../../components/ui/toast";
import { useVersionInfo } from "../../lib/use-version-info";
import { ChatInput } from "./chat-input";
import { buildSkillsMessage } from "./skill-use";
import { clearDraft, draftKey, loadDraft, saveDraft } from "./draft-cache";
@@ -510,6 +513,7 @@ export function DraftView({
{S.appName}
</h1>
<p className="mt-2 text-base text-gray-400 dark:text-gray-500">{S.chat.draftSubtitle}</p>
<VersionLine />
</div>
<ChatInput
@@ -665,6 +669,57 @@ export function DraftView({
);
}
/**
* Superscript "new version" pill on the version line (accent-colored, raised via
* align-super). Kept literally identical to the sidebar footer's copy in
* components/layout/sidebar.tsx — the two surfaces must not drift apart.
*/
const versionBadgeClass =
"ml-1.5 inline-block rounded-full bg-[var(--accent-bg)] px-1.5 align-super text-[10px] font-medium leading-4 text-[var(--accent-fg)] transition-opacity duration-150 hover:opacity-80";
/**
* Quiet version line under the brand subtitle: `PenguinHarness vX.Y.Z · 最近更新日期
* 7 月 26 日` / `… · Last updated Jul 26`. The date is the running version's release
* date, stamped into core's BUILD_DATE at build time — displayed as-is, no network;
* dev builds and releases that predate the stamping (v0.1.2 and earlier) carry null
* and show the version alone. When the update check knows a newer release, a small
* superscript badge follows, linking to the release page (this surface's existing
* affordance; the sidebar's badge additionally offers admins the update dialog).
* Fetching starts on mount — useVersionInfo caches at module level, so after the first
* resolution anywhere in the app this renders instantly and never refetches. Nothing
* renders until the version resolves (no placeholder flicker under the brand).
*/
function VersionLine() {
const { locale } = useLocale();
const { version, update } = useVersionInfo(true);
if (version === null) return null;
const date = version.buildDate;
return (
<p className="mt-1.5 text-xs text-gray-400 dark:text-gray-500">
{`PenguinHarness v${version.version}${
date !== null ? ` · ${S.update.lastUpdated(formatMonthDay(date, locale))}` : ""
}`}
{update !== null &&
update.updateAvailable &&
update.latestVersion !== null &&
(update.releaseUrl !== null ? (
<a
href={update.releaseUrl}
target="_blank"
rel="noopener noreferrer"
title={S.update.newVersion(update.latestVersion)}
aria-label={S.update.newVersion(update.latestVersion)}
className={versionBadgeClass}
>
{S.update.newVersionBadge}
</a>
) : (
<span className={versionBadgeClass}>{S.update.newVersionBadge}</span>
))}
</p>
);
}
/** Shared style for pill trigger buttons (ChatGPT project button style: small rounded pill + icon + short name + collapse arrow). */
const pillClass =
"flex max-w-64 items-center gap-1.5 rounded-full border border-gray-300 bg-white py-1 pl-1.5 pr-2 " +