feat: support custom canvas skill tag title
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-04 01:09:29 +08:00
parent c01e0a0cb8
commit 26b5e55ee8
4 changed files with 50 additions and 10 deletions

View File

@ -91,6 +91,7 @@ import {
} from "@/lib/payment/dulupay-config";
import {
DEFAULT_CANVAS_SKILL_TAGS,
DEFAULT_CANVAS_SKILL_TAGS_TITLE,
normalizeCanvasSkillTags,
type CanvasSkillTag,
} from "@/lib/canvas-skill-tags";
@ -1002,6 +1003,9 @@ export function AdminSettingsClient() {
// 快捷短语状态
const [questions, setQuestions] = useState<string[]>([]);
const { data: questionsData, mutate: mutateQuestions } = useSWR<string[]>("/api/starter-questions", fetcher);
const [canvasSkillTagsTitle, setCanvasSkillTagsTitle] = useState(
DEFAULT_CANVAS_SKILL_TAGS_TITLE
);
const [canvasSkillTagsEnabled, setCanvasSkillTagsEnabled] = useState(true);
const [canvasSkillTags, setCanvasSkillTags] = useState<CanvasSkillTag[]>(
DEFAULT_CANVAS_SKILL_TAGS
@ -1009,6 +1013,7 @@ export function AdminSettingsClient() {
const [canvasSkillTagUploadingId, setCanvasSkillTagUploadingId] = useState<string | null>(null);
const { data: canvasSkillTagsData, mutate: mutateCanvasSkillTags } = useSWR<{
enabled?: boolean;
title?: string;
tags?: CanvasSkillTag[];
}>("/api/canvas-skill-tags", fetcher);
@ -1054,6 +1059,9 @@ export function AdminSettingsClient() {
useEffect(() => {
if (canvasSkillTagsData) {
setCanvasSkillTagsEnabled(canvasSkillTagsData.enabled !== false);
setCanvasSkillTagsTitle(
String(canvasSkillTagsData.title || "").trim() || DEFAULT_CANVAS_SKILL_TAGS_TITLE
);
setCanvasSkillTags(normalizeCanvasSkillTags(canvasSkillTagsData.tags));
}
}, [canvasSkillTagsData]);
@ -1151,12 +1159,20 @@ export function AdminSettingsClient() {
const res = await fetch("/api/canvas-skill-tags", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: canvasSkillTagsEnabled, tags: payload }),
body: JSON.stringify({
enabled: canvasSkillTagsEnabled,
title: canvasSkillTagsTitle,
tags: payload,
}),
});
if (!res.ok) throw new Error("Failed");
const data = await res.json().catch(() => ({}));
setCanvasSkillTagsEnabled(data?.enabled !== false);
setCanvasSkillTagsTitle(
String(data?.title || canvasSkillTagsTitle || "").trim() ||
DEFAULT_CANVAS_SKILL_TAGS_TITLE
);
setCanvasSkillTags(normalizeCanvasSkillTags(data?.tags ?? payload));
toast.success("快捷标签已更新");
mutateCanvasSkillTags();
@ -5763,6 +5779,14 @@ export function AdminSettingsClient() {
onCheckedChange={setCanvasSkillTagsEnabled}
/>
</div>
<div className="space-y-2">
<Label>标签区标题</Label>
<Input
value={canvasSkillTagsTitle}
onChange={(event) => setCanvasSkillTagsTitle(event.target.value)}
placeholder={DEFAULT_CANVAS_SKILL_TAGS_TITLE}
/>
</div>
{canvasSkillTags.map((tag, index) => {
const TagIcon = getCanvasSkillIconComponent(tag.iconName);
return (

View File

@ -6,7 +6,6 @@ import { systemSettings } from "@/lib/db/schema";
import {
CANVAS_SKILL_TAGS_SETTING_KEY,
normalizeCanvasSkillTagSettings,
normalizeCanvasSkillTags,
} from "@/lib/canvas-skill-tags";
export async function GET() {
@ -33,10 +32,11 @@ export async function PUT(request: NextRequest) {
}
const body = await request.json();
const rawTags = Array.isArray(body) ? body : body?.tags;
const tags = normalizeCanvasSkillTags(rawTags);
const enabled = Array.isArray(body) ? true : body?.enabled !== false;
const nextValue = { enabled, tags };
const normalized = normalizeCanvasSkillTagSettings(body);
const enabled = normalized.enabled;
const title = normalized.title;
const tags = normalized.tags;
const nextValue = { enabled, title, tags };
const [existing] = await db
.select()
@ -61,7 +61,7 @@ export async function PUT(request: NextRequest) {
});
}
return NextResponse.json({ success: true, enabled, tags });
return NextResponse.json({ success: true, enabled, title, tags });
} catch (error) {
console.error("[CanvasSkillTags API] Update error:", error);
return NextResponse.json({ error: "Failed to update" }, { status: 500 });

View File

@ -48,7 +48,11 @@ import {
import { buildImageProxyUrl } from "@/lib/image-delivery";
import type { Attachment, ChatMessage } from "@/lib/types";
import { type CanvasLaunchPayload, getCanvasLaunchStorageKey } from "@/lib/canvas-launch";
import { normalizeCanvasSkillTags, type CanvasSkillTag } from "@/lib/canvas-skill-tags";
import {
DEFAULT_CANVAS_SKILL_TAGS_TITLE,
normalizeCanvasSkillTags,
type CanvasSkillTag,
} from "@/lib/canvas-skill-tags";
import { getCanvasSkillIconComponent } from "@/components/canvas/canvas-skill-icons";
import { ImageParamsBar, type ImageGenerationParams } from "@/components/image-params-bar";
import { VideoParamsBar, type VideoGenerationParams } from "@/components/video-params-bar";
@ -2250,7 +2254,11 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
canvasAutoModeDefaultModelId?: string | null;
canvasDetailDefaultModelId?: string | null;
}>("/api/site-settings", fetcher);
const { data: canvasSkillTagsData } = useSWR<{ enabled?: boolean; tags?: CanvasSkillTag[] }>(
const { data: canvasSkillTagsData } = useSWR<{
enabled?: boolean;
title?: string;
tags?: CanvasSkillTag[];
}>(
"/api/canvas-skill-tags",
fetcher
);
@ -2287,6 +2295,8 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
const totalGeneratedImages = Number(userImageStats?.totalImages ?? NaN);
const aiDetailGenerationEnabled = siteSettings?.aiDetailGenerationEnabled === true;
const canvasSkillTagsEnabled = canvasSkillTagsData?.enabled !== false;
const canvasSkillTagsTitle =
String(canvasSkillTagsData?.title || "").trim() || DEFAULT_CANVAS_SKILL_TAGS_TITLE;
const canvasSkillTags = useMemo(
() => (canvasSkillTagsEnabled ? normalizeCanvasSkillTags(canvasSkillTagsData?.tags) : []),
[canvasSkillTagsData?.tags, canvasSkillTagsEnabled]
@ -6362,7 +6372,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
{canvasSkillTags.length > 0 && (
<div className="mb-8 rounded-3xl border border-[#eceef2] bg-white/90 p-4">
<div className="mb-3 text-center">
<h3 className="text-[16px] font-semibold text-[#1f2937]">试试这些 Skills</h3>
<h3 className="text-[16px] font-semibold text-[#1f2937]">{canvasSkillTagsTitle}</h3>
</div>
<div className="grid grid-cols-3 gap-2">
{canvasSkillTags.map((tag) => {

View File

@ -1,7 +1,9 @@
export const CANVAS_SKILL_TAGS_SETTING_KEY = "canvas_skill_tags";
export const DEFAULT_CANVAS_SKILL_TAGS_TITLE = "试试这些 Skills";
export type CanvasSkillTagSettings = {
enabled: boolean;
title: string;
tags: CanvasSkillTag[];
};
@ -120,20 +122,24 @@ export function normalizeCanvasSkillTagSettings(
if (Array.isArray(value)) {
return {
enabled: true,
title: DEFAULT_CANVAS_SKILL_TAGS_TITLE,
tags: normalizeCanvasSkillTags(value),
};
}
if (value && typeof value === "object") {
const row = value as Record<string, unknown>;
const title = normalizeString(row.title) || DEFAULT_CANVAS_SKILL_TAGS_TITLE;
return {
enabled: row.enabled !== false,
title,
tags: normalizeCanvasSkillTags(row.tags),
};
}
return {
enabled: true,
title: DEFAULT_CANVAS_SKILL_TAGS_TITLE,
tags: normalizeCanvasSkillTags(null),
};
}