@wenxin_like/spec-render
v0.1.0
Published
JSON-spec driven UI renderer — turn flat { root, elements } trees into React components.
Maintainers
Readme
@wenxin_like/spec-render
把扁平 JSON Spec({ root, elements })渲染为 React UI 的库。内置 6 种专用渲染器(角色展示、关键帧视频、草图画廊、剧集拆分、脚本概览、长篇故事)+ 通用树渲染兜底。
原始代码从 DramaClaw / schat 项目抽取,解耦了 store / presign / 弹窗回调,改由 Context 注入。
安装
npm install @wenxin_like/spec-render
# peer deps
npm install react react-dom framer-motion lucide-react @radix-ui/react-dialog最小用法
import { SpecRenderer, SpecRendererProvider } from "@wenxin_like/spec-render";
import "@wenxin_like/spec-render/style.css";
export function App() {
return (
<SpecRendererProvider
// 把内部路径(如 /static/xxx/image.png)解析为可访问 URL
resolveMediaUrl={async (src) => await presign(src)}
// 判断哪些 URL 需要解析(返回 null 表示无需处理)
parseMediaUrl={(src) => /^(?:https?:\/\/[^/]+)?\/static\//.test(src) ? src : null}
// 用户点候选图时的回调(可选)
onCandidateSelect={(candidate) => sendWs(`我选择了「${candidate.label}」的图片`)}
// toast 回调(可选)
onToast={(msg, level) => toast[level](msg)}
>
<SpecRenderer spec={spec} mediaMaxWidth={720} />
</SpecRendererProvider>
);
}样式前置条件
组件用了 Tailwind class + 一份 jr-* 前缀的 CSS。消费端需要:
- 有 Tailwind。在
tailwind.config.*的content里加上包路径:content: [ "./node_modules/@wenxin_like/spec-render/dist/**/*.{js,mjs,cjs}", // ...你自己的路径 ] - 引入样式:
import "@wenxin_like/spec-render/style.css". - 定义 CSS 变量(shadcn 风格):
--background,--foreground,--border,--muted,--muted-foreground,--card,--primary,--secondary,--secondary-foreground,--radius等。直接用 shadcn 默认主题即可。
导出
// 入口组件
export { SpecRenderer, SpecRendererProvider, useSpecRendererContext };
// 类型
export type { Spec, UIElement, ContentSegment };
export type { RenderContext, RootRendererFn, ComponentFn, Props };
export type { ImageCandidate, VideoDetailSection };
// 标准化 & 提取
export { normalizeSpec, flattenNestedSpec, normalizeSpecType };
// 底层渲染(可单独使用)
export { DefaultRootRenderer, renderElement, renderRootWithOverrides, renderElementWithOverrides };
// 专用渲染器注册表
export { SPEC_RENDERERS };
// 内部弹窗(调用方可直接用)
export { ImageDetailModal, VideoDetailModal };Context API
type SpecRendererContextValue = {
resolveMediaUrl?: (src: string) => Promise<string>;
parseMediaUrl?: (src: string) => string | null;
onCandidateSelect?: (candidate: ImageCandidate) => void;
onToast?: (message: string, level: "success" | "error" | "info") => void;
loadingVideoUrl?: string; // 占位视频 URL,未提供时用纯 CSS 闪烁
};自动类型检测
当 spec.type 未指定或不在 SPEC_RENDERERS 中时,按规则自动推断:
- Video ≥1 →
keyframe_video - Image ≥2 + Stack 根 / overlay 属性 →
character_showcase - Image ≥2 + Card ≥2 →
sketch_gallery - List + Card →
episode_breakdown - Card ≥3 →
script_overview - Text ≥3 + 无媒体 →
longform_story - 都不匹配 →
DefaultRootRenderer(通用树渲染)
已知限制
- Tailwind 强绑定,暂不支持非 Tailwind 消费方
framer-motion/lucide-react/@radix-ui/react-dialog是 peer dep,消费端需自己安装- 图片裁剪/编辑 UI 依赖 shadcn CSS 变量
