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:
@@ -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 " +
|
||||
|
||||
Reference in New Issue
Block a user