Files
ttstd-infinite-canvas/web/src/lib/inspirations/resolve.ts
地球观察员 dc2cc71058 feat(create): 创作首页与灵感库 - 纵深画廊、灵感页面、外部提示词池 (#708)
* feat(create): 创作首页与灵感库 - 纵深画廊、灵感页面、外部提示词池

创作首页空态换成直线纵深图片画廊:11 张横版卡片沿 Z 轴前后排队,当前一张
在最前最大,滚轮、左右按钮或自动轮播切换,卡片带玻璃描边和"行动点 / 标题 /
描述"三段信息,跟随输入框的创作类型联动换队。队列是随机取样并记住最近出现过的
条目,走完一轮自动换批,不再反复看到同样那几张。

左侧「创作」下方新增灵感页:1341 条提示词按来源、分类和创作类型筛选,
支持搜索、收藏,以及自建灵感的新建、编辑、删除和 JSON 导入导出。

新增 scripts/import-inspirations.mjs,从 awesome-seedance、好好学 AI 和
YouMind 两个姊妹库同步条目,并为画廊池生成本地 webp 缩略图(560px / q72,
约 35KB 一张)。上游封面平均 400KB~700KB,直接引用一屏 11 张要拉 5MB 以上,
换成本地缩略图后约 400KB。

画廊池改走 public/inspirations/gallery-pool.json 运行时拉取:几百条提示词正文
压在首屏 JS 上是白背的,而这份数据只服务画廊一处。缩略图等距抽取,横跨各来源
的全部分类;只收描述非空的条目,保证卡片三行版式不缺角。

* docs(create): 功能清单与地图 - 补充纵深画廊与灵感库

功能清单新增「创作首页纵深画廊与灵感库」一节,并在功能地图里记下画廊、
灵感页、运行时目录加载和导入脚本各自的代码入口;待测试清单记下浏览器验收结果
与仍未确认的边界(弱网退回、窄屏、文本条目偏少、外部来源署名)。
2026-10-08 19:55:04 +08:00

59 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 灵感目录的合成规则:内置目录是只读的,用户的增删改以「覆盖 / 隐藏 / 新增」三种形式叠在上面。
* 三个集合都不改内置数据本身,这样外部目录重新导入后用户的改动仍然对得上 id。
*/
import { catalogIdOf, type CreationInspiration } from "./catalog";
/** 用户可以改写的字段。id 与 sourceId 是归属标识,不参与改写。 */
export type InspirationOverride = Partial<Pick<CreationInspiration, "title" | "description" | "image" | "mode" | "prompt" | "promptZh" | "category" | "tags" | "ratio">>;
export type InspirationOverlay = {
/** 用户新建的条目,id 一定是自定义前缀。 */
custom: CreationInspiration[];
/** 按 id 覆盖内置条目。 */
overrides: Record<string, InspirationOverride>;
/** 被用户隐藏的内置条目 id。 */
hidden: string[];
};
export const EMPTY_OVERLAY: InspirationOverlay = { custom: [], overrides: {}, hidden: [] };
/** 自定义条目的 id 前缀,用来区分「可以真删」和「只能隐藏」。 */
export const CUSTOM_ID_PREFIX = "custom:";
export function isCustomInspiration(id: string): boolean {
return id.startsWith(CUSTOM_ID_PREFIX);
}
function withoutUndefined<T extends object>(value: T): T {
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== "")) as T;
}
/**
* 把用户的改动叠到内置目录上,并按用户顺序把新建条目插在最前。
* 隐藏只对内置条目生效:新建条目直接删除,没必要留一个隐藏态。
*/
export function resolveInspirations(builtIn: CreationInspiration[], overlay: InspirationOverlay): CreationInspiration[] {
const hidden = new Set(overlay.hidden.filter((id) => !isCustomInspiration(id)));
const resolved = builtIn.flatMap((item) => {
const id = catalogIdOf(item);
if (hidden.has(id)) return [];
const override = overlay.overrides[id];
return [override ? { ...item, ...withoutUndefined(override) } : item];
});
return [...overlay.custom, ...resolved];
}
/** 当前生效条目里属于某个来源的部分,用于灵感页按来源筛选。 */
export function itemsBySource(items: CreationInspiration[], sourceId: string): CreationInspiration[] {
return items.filter((item) => (item.sourceId ?? "original") === sourceId);
}
/**
* 一键填入创作时优先用中文正文:外部图片提示词的原文是英文,
* 中文界面下把英文整段塞进编辑器是坏体验,双语来源就用中文那版。
*/
export function fillablePrompt(item: CreationInspiration): string {
return (item.promptZh || item.prompt || "").trim();
}