Clarify Hint pack save behavior

Keep manual editing while making save the only user action and applying changes immediately without a separate publish workflow.
This commit is contained in:
Rocky
2026-08-21 15:37:13 +08:00
parent 454ba8ddc5
commit b25f5ae6e9
8 changed files with 32 additions and 10 deletions
+1
View File
@@ -17,6 +17,7 @@ export type AdminAuditAction =
| "CONTENT_SKILL_ENABLED" | "CONTENT_SKILL_ENABLED"
| "CONTENT_SKILL_DISABLED" | "CONTENT_SKILL_DISABLED"
| "CONTENT_HINT_PACK_PUBLISHED" | "CONTENT_HINT_PACK_PUBLISHED"
| "CONTENT_HINT_PACK_SAVED"
| "CONTENT_HINT_FEED_SETTINGS_UPDATED" | "CONTENT_HINT_FEED_SETTINGS_UPDATED"
| "CONTENT_HINT_FEED_GENERATED"; | "CONTENT_HINT_FEED_GENERATED";
@@ -113,7 +113,7 @@ export function ContentPage() {
const saved = await adminApi.updateContentHintPack(locale, payload); const saved = await adminApi.updateContentHintPack(locale, payload);
setHintVersion(saved.version); setHintVersion(saved.version);
setHintText(formatHintPack(saved)); setHintText(formatHintPack(saved));
toast.success(`${locale} Hint pack 已发布`); toast.success(`${locale} Hint pack 已保存并生效`);
} catch (requestError) { } catch (requestError) {
toast.error(errorMessage(requestError, "Hint pack 保存失败")); toast.error(errorMessage(requestError, "Hint pack 保存失败"));
} finally { } finally {
@@ -129,7 +129,7 @@ export function ContentPage() {
<PageHeader <PageHeader
eyebrow="Official Content" eyebrow="Official Content"
title="内容管理" title="内容管理"
description="维护客户端官方 Skill、AI Hint 自动生成与 zh/en 手动发布包。所有变更都会写入审计。" description="维护客户端官方 Skill、AI Hint 自动生成与 zh/en 内容。所有变更都会写入审计。"
actions={ actions={
canEdit ? ( canEdit ? (
<Button onClick={() => setEditingSkill("new")}> <Button onClick={() => setEditingSkill("new")}>
@@ -221,7 +221,7 @@ export function ContentPage() {
AI Hint packs AI Hint packs
</h2> </h2>
<p className="mt-1 text-sm text-muted"> <p className="mt-1 text-sm text-muted">
JSON AIHintPack version AIHintPack version
</p> </p>
</div> </div>
<Card className="overflow-hidden"> <Card className="overflow-hidden">
@@ -263,7 +263,7 @@ export function ContentPage() {
{canEdit ? ( {canEdit ? (
<div className="mt-4 flex justify-end"> <div className="mt-4 flex justify-end">
<Button loading={hintSaving} onClick={() => void saveHint()}> <Button loading={hintSaving} onClick={() => void saveHint()}>
</Button> </Button>
</div> </div>
) : null} ) : null}
@@ -239,7 +239,7 @@ export function HintAutoSection({ canEdit }: HintAutoSectionProps) {
<p className="text-xs text-muted"> <p className="text-xs text-muted">
TopHub API Key TopHub API Key
JSON JSON
</p> </p>
{canEdit ? ( {canEdit ? (
+20 -1
View File
@@ -26,7 +26,7 @@ describe("内容管理", () => {
expect(screen.queryByRole("button", { name: "新增 Skill" })).toBeNull(); expect(screen.queryByRole("button", { name: "新增 Skill" })).toBeNull();
const editor = await screen.findByLabelText("zh JSON"); const editor = await screen.findByLabelText("zh JSON");
expect(editor).toHaveProperty("readOnly", true); expect(editor).toHaveProperty("readOnly", true);
expect(screen.queryByRole("button", { name: "保存并立即发布" })).toBeNull(); expect(screen.queryByRole("button", { name: "保存" })).toBeNull();
expect(screen.queryByRole("button", { name: "立即生成" })).toBeNull(); expect(screen.queryByRole("button", { name: "立即生成" })).toBeNull();
expect(screen.queryByRole("button", { name: "保存生成设置" })).toBeNull(); expect(screen.queryByRole("button", { name: "保存生成设置" })).toBeNull();
}); });
@@ -90,6 +90,25 @@ describe("内容管理", () => {
await waitFor(() => expect(regenerate).toHaveBeenCalledOnce()); await waitFor(() => expect(regenerate).toHaveBeenCalledOnce());
}); });
it("SUPER_ADMIN 可编辑并保存当前生效的 Hint pack", async () => {
mockSession("SUPER_ADMIN");
mockContent();
const save = vi.spyOn(adminApi, "updateContentHintPack").mockResolvedValue({
locale: "zh",
generatedAt: "2026-08-21T06:00:00Z",
intervalHours: 12,
version: 3,
cards: [],
});
window.location.hash = "#/content";
render(<App />);
await userEvent.click(await screen.findByRole("button", { name: "保存" }));
await waitFor(() => expect(save).toHaveBeenCalledOnce());
expect(await screen.findByText("version 3")).toBeTruthy();
});
}); });
function mockSession(role: AdminRole) { function mockSession(role: AdminRole) {
+1 -1
View File
@@ -34,7 +34,7 @@ The migrated generator runs independently inside OSGAccountServer and publishes:
## Rollback ## Rollback
Disable `HINT_FEED_ENABLED` to stop scheduled generation. Published packs remain Disable `HINT_FEED_ENABLED` to stop scheduled generation. Published packs remain
available from MySQL and manual publishing remains available. No rollback step available from MySQL and manual editing/saving remains available. No rollback step
depends on or modifies `key.osglab.com`. depends on or modifies `key.osglab.com`.
Provider credentials such as `TOPHUB_API_KEY` stay in environment-backed secret Provider credentials such as `TOPHUB_API_KEY` stay in environment-backed secret
+3 -2
View File
@@ -808,7 +808,7 @@ paths:
security: security:
- adminMtls: [] - adminMtls: []
adminSession: [] adminSession: []
summary: Immediately publish a locale Hint pack and increment its version summary: Save and immediately apply edits to a locale Hint pack
parameters: parameters:
- $ref: "#/components/parameters/HintLocale" - $ref: "#/components/parameters/HintLocale"
- $ref: "#/components/parameters/AdminCsrf" - $ref: "#/components/parameters/AdminCsrf"
@@ -819,7 +819,7 @@ paths:
schema: { $ref: "#/components/schemas/UpdateAIHintPackRequest" } schema: { $ref: "#/components/schemas/UpdateAIHintPackRequest" }
responses: responses:
"200": "200":
description: Published pack description: Saved active pack
content: content:
application/json: application/json:
schema: { $ref: "#/components/schemas/AdminAIHintPack" } schema: { $ref: "#/components/schemas/AdminAIHintPack" }
@@ -1249,6 +1249,7 @@ paths:
- CONTENT_SKILL_ENABLED - CONTENT_SKILL_ENABLED
- CONTENT_SKILL_DISABLED - CONTENT_SKILL_DISABLED
- CONTENT_HINT_PACK_PUBLISHED - CONTENT_HINT_PACK_PUBLISHED
- CONTENT_HINT_PACK_SAVED
- CONTENT_HINT_FEED_SETTINGS_UPDATED - CONTENT_HINT_FEED_SETTINGS_UPDATED
- CONTENT_HINT_FEED_GENERATED - CONTENT_HINT_FEED_GENERATED
- name: result - name: result
@@ -116,6 +116,7 @@ enum class AdminAuditAction {
CONTENT_SKILL_ENABLED, CONTENT_SKILL_ENABLED,
CONTENT_SKILL_DISABLED, CONTENT_SKILL_DISABLED,
CONTENT_HINT_PACK_PUBLISHED, CONTENT_HINT_PACK_PUBLISHED,
CONTENT_HINT_PACK_SAVED,
CONTENT_HINT_FEED_SETTINGS_UPDATED, CONTENT_HINT_FEED_SETTINGS_UPDATED,
CONTENT_HINT_FEED_GENERATED, CONTENT_HINT_FEED_GENERATED,
} }
@@ -215,7 +215,7 @@ class ContentService(
now, now,
audit( audit(
actor, actor,
AdminAuditAction.CONTENT_HINT_PACK_PUBLISHED, AdminAuditAction.CONTENT_HINT_PACK_SAVED,
"OFFICIAL_HINT_PACK", "OFFICIAL_HINT_PACK",
locale, locale,
requestId, requestId,