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:
@@ -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 ? (
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user