diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f2a3ba4..aacecb0b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
### 修复
+- 隐藏无应用更新权限用户头像菜单中的“检查更新”入口
- 插件工具使用中文等非 ASCII 名称时 LLM 调用失败:主流 API 要求工具名匹配 `^[a-zA-Z0-9_-]{1,64}$`,现自动将非法名称转写为合法拼音名(`pypinyin` 缺失时退回下划线替换),冲突追加 `_2`/`_3` 后缀,并在工具描述前缀 `[原名: …]` 保留原名映射;`config_json.plugins` 配置键与插件内部仍使用原始名称,路由不受影响
- 修复聊天页在"生成中"时于输入框持续打字导致消息列表上下轻微抖动的问题:输入框高度测量改为在离屏克隆节点上进行,不再瞬态改变页面布局
diff --git a/dashboard/src/components/AvatarDropdown.test.tsx b/dashboard/src/components/AvatarDropdown.test.tsx
new file mode 100644
index 00000000..5731f6d0
--- /dev/null
+++ b/dashboard/src/components/AvatarDropdown.test.tsx
@@ -0,0 +1,53 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { MemoryRouter } from "react-router-dom";
+import { describe, expect, it, vi } from "vitest";
+import type { OctopUser } from "../api/modules/auth";
+import { CurrentUserProvider } from "../hooks/useCurrentUser";
+import AvatarDropdown from "./AvatarDropdown";
+
+function buildUser(
+ role: OctopUser["role"],
+ permissions: string[] = [],
+): OctopUser {
+ return {
+ id: 1,
+ username: role,
+ role,
+ display_name: role === "admin" ? "Admin User" : "Regular User",
+ locale: "en",
+ permissions,
+ };
+}
+
+async function openAccountMenu(user: OctopUser) {
+ const pointer = userEvent.setup();
+ render(
+
+
+
+
+ ,
+ );
+
+ await pointer.click(
+ screen.getByRole("button", { name: new RegExp(user.display_name!) }),
+ );
+}
+
+describe(" update access", () => {
+ it("hides Check for updates from users without update permission", async () => {
+ await openAccountMenu(buildUser("user"));
+
+ expect(screen.queryByText("account.checkUpdates")).not.toBeInTheDocument();
+ });
+
+ it.each([
+ ["an admin", buildUser("admin")],
+ ["an explicitly authorized user", buildUser("user", ["update"])],
+ ])("shows Check for updates to %s", async (_label, user) => {
+ await openAccountMenu(user);
+
+ expect(await screen.findByText("account.checkUpdates")).toBeInTheDocument();
+ });
+});
diff --git a/dashboard/src/components/AvatarDropdown.tsx b/dashboard/src/components/AvatarDropdown.tsx
index c1095955..319f23ed 100644
--- a/dashboard/src/components/AvatarDropdown.tsx
+++ b/dashboard/src/components/AvatarDropdown.tsx
@@ -32,6 +32,7 @@ import { preferencesApi } from "../api/modules/preferences";
import { clearAuthToken } from "../api/request";
import { applyGuestLocale, applyUserLocale } from "../utils/locale";
import { apiErrorMessage } from "../utils/apiError";
+import { userCan } from "../utils/permissions";
import {
MIN_PASSWORD_LENGTH,
passwordPolicyIssue,
@@ -256,17 +257,19 @@ export default function AvatarDropdown({
{t("account.changePassword")}
-
+ {userCan(user, "update") && (
+
+ )}