component-registry.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /**
  2. * 组件注册表 - 动态组件定义
  3. *
  4. * 前端维护所有可用的 json-render 组件定义
  5. * 发送给后端,让 AI 了解可用的 UI 组件
  6. *
  7. * ## 架构说明
  8. *
  9. * 1. 前端在这里维护所有组件定义(组件类型、描述、Schema)
  10. * 2. 通过 generateComponentsPrompt() 生成发送给后端的提示词
  11. * 3. 后端使用这些提示词构建 SYSTEM_PROMPT,无需手动修改后端代码
  12. * 4. 添加新组件时,只需在这里添加定义即可
  13. */
  14. 'use client';
  15. /**
  16. * 组件配置接口
  17. */
  18. interface ComponentConfig {
  19. /** 组件类型标识 */
  20. type: string;
  21. /** 组件描述 */
  22. description: string;
  23. /** Schema 字段说明 */
  24. schema: Record<string, string>;
  25. /** 示例 JSON */
  26. example?: Record<string, unknown>;
  27. /** 使用场景提示 */
  28. usageHint?: string;
  29. }
  30. /**
  31. * 所有可用的 json-render 组件定义
  32. *
  33. * 添加新组件:
  34. * 1. 在此对象中添加组件定义
  35. * 2. 无需修改后端代码
  36. */
  37. export const AVAILABLE_COMPONENTS: Record<string, ComponentConfig> = {
  38. // ============ MCP 专用组件 ============
  39. 'translation-result': {
  40. type: 'translation-result',
  41. description: '翻译结果卡片,展示译文和使用的术语',
  42. schema: {
  43. translated: '译文文本',
  44. termsUsed: '使用的术语列表(可选)'
  45. },
  46. usageHint: '调用 translate_text 工具后使用此组件展示结果',
  47. example: {
  48. type: 'translation-result',
  49. translated: '这是一个示例翻译',
  50. termsUsed: ['术语1', '术语2']
  51. }
  52. },
  53. 'novel-list': {
  54. type: 'novel-list',
  55. description: '小说列表卡片,展示多部小说的摘要信息',
  56. schema: {
  57. novels: '小说数组,每个小说包含 id, title, author, description, chapterCount, tags'
  58. },
  59. usageHint: '调用 get_novels 工具后使用此组件展示列表',
  60. example: {
  61. type: 'novel-list',
  62. novels: [
  63. {
  64. id: '1',
  65. title: '示例小说',
  66. author: '作者名',
  67. description: '这是一本精彩的小说',
  68. chapterCount: 100,
  69. tags: ['玄幻', '冒险']
  70. }
  71. ]
  72. }
  73. },
  74. 'novel-detail': {
  75. type: 'novel-detail',
  76. description: '小说详情卡片,展示单部小说的完整信息',
  77. schema: {
  78. novel: '小说对象,包含 id, title, author, category, description, status, chapterCount, wordCount, viewCount, isVip'
  79. },
  80. usageHint: '调用 get_novel_detail 工具或用户点击小说卡片后使用此组件',
  81. example: {
  82. type: 'novel-detail',
  83. novel: {
  84. id: '1',
  85. title: '修真世界',
  86. author: '方想',
  87. category: '玄幻',
  88. description: '一个热血少年的修真之路...',
  89. status: '已完结',
  90. chapterCount: 1200,
  91. wordCount: 3500000,
  92. viewCount: 500000,
  93. isVip: false
  94. }
  95. }
  96. },
  97. 'chapter-reader': {
  98. type: 'chapter-reader',
  99. description: '章节阅读器,展示小说章节内容',
  100. schema: {
  101. novelTitle: '小说标题',
  102. chapterTitle: '章节标题',
  103. content: '章节内容',
  104. chapterNumber: '当前章节号(可选)',
  105. totalChapters: '总章节数(可选)'
  106. },
  107. usageHint: '调用 get_chapter 工具后使用此组件展示章节内容',
  108. example: {
  109. type: 'chapter-reader',
  110. novelTitle: '修真世界',
  111. chapterTitle: '第一章:开始',
  112. content: '章节内容...',
  113. chapterNumber: 1,
  114. totalChapters: 1200
  115. }
  116. },
  117. 'suggestion-buttons': {
  118. type: 'suggestion-buttons',
  119. description: '建议操作按钮组,提供可点击的建议操作',
  120. schema: {
  121. suggestions: '建议数组,每个建议包含 label(显示文本), message(发送的消息), icon(图标,可选)'
  122. },
  123. usageHint: '在展示列表、详情后,给出操作建议时使用',
  124. example: {
  125. type: 'suggestion-buttons',
  126. suggestions: [
  127. { label: '下一页', icon: '➡️', message: '显示下一页小说' },
  128. { label: '返回列表', icon: '🔙', message: '返回小说列表' }
  129. ]
  130. }
  131. },
  132. 'mcp-tool-call': {
  133. type: 'mcp-tool-call',
  134. description: '工具调用过程展示,显示工具执行的详细状态',
  135. schema: {
  136. tool: '工具名称',
  137. status: '执行状态:pending, running, success, error',
  138. args: '工具参数(可选)',
  139. result: '执行结果(可选)',
  140. error: '错误信息(可选)',
  141. timestamp: '时间戳(可选)'
  142. },
  143. usageHint: '展示工具调用过程时使用',
  144. example: {
  145. type: 'mcp-tool-call',
  146. tool: 'translate_text',
  147. status: 'success',
  148. args: { text: 'Hello' },
  149. result: { translated: '你好' }
  150. }
  151. },
  152. 'login-panel': {
  153. type: 'login-panel',
  154. description: '登录面板,提供用户登录界面',
  155. schema: {
  156. server: '服务器名称',
  157. email: '已登录的邮箱(可选)'
  158. },
  159. usageHint: '需要用户登录 MCP 服务器时使用',
  160. example: {
  161. type: 'login-panel',
  162. server: 'Novel Platform User'
  163. }
  164. },
  165. // ============ 基础组件 ============
  166. 'card': {
  167. type: 'card',
  168. description: '卡片容器,用于包裹其他内容',
  169. schema: {
  170. title: '卡片标题(可选)',
  171. children: '子组件数组(可选)',
  172. className: '自定义样式类名(可选)'
  173. },
  174. usageHint: '需要将内容组织成卡片时使用',
  175. example: {
  176. type: 'card',
  177. title: '卡片标题',
  178. children: [
  179. { type: 'text', content: '内容', variant: 'body' }
  180. ]
  181. }
  182. },
  183. 'stack': {
  184. type: 'stack',
  185. description: '布局容器,用于排列子元素',
  186. schema: {
  187. direction: '排列方向:row(横向)或 column(纵向,默认)',
  188. spacing: '间距(可选)',
  189. align: '对齐方式:start, center, end, stretch(可选)',
  190. children: '子组件数组(可选)',
  191. className: '自定义样式类名(可选)'
  192. },
  193. usageHint: '需要布局多个元素时使用',
  194. example: {
  195. type: 'stack',
  196. direction: 'column',
  197. spacing: 2,
  198. children: [
  199. { type: 'heading', level: 'h3', text: '标题' },
  200. { type: 'text', content: '内容' }
  201. ]
  202. }
  203. },
  204. 'heading': {
  205. type: 'heading',
  206. description: '标题组件,支持 h1-h6 级别',
  207. schema: {
  208. level: '标题级别:h1, h2, h3, h4, h5, h6(可选)',
  209. text: '标题文本',
  210. className: '自定义样式类名(可选)'
  211. },
  212. usageHint: '需要显示标题时使用',
  213. example: {
  214. type: 'heading',
  215. level: 'h2',
  216. text: '这是标题'
  217. }
  218. },
  219. 'text': {
  220. type: 'text',
  221. description: '文本组件,显示正文或次要文本',
  222. schema: {
  223. content: '文本内容',
  224. variant: '文本样式:body(正文), muted(次要文本), code(代码文本)(可选)',
  225. className: '自定义样式类名(可选)'
  226. },
  227. usageHint: '需要显示段落文本时使用',
  228. example: {
  229. type: 'text',
  230. content: '这是正文内容',
  231. variant: 'body'
  232. }
  233. },
  234. 'button': {
  235. type: 'button',
  236. description: '按钮组件,支持点击交互',
  237. schema: {
  238. label: '按钮文本',
  239. variant: '按钮样式:default, primary, secondary, ghost, danger(可选)',
  240. action: '交互事件名,如 sendMessage(可选)',
  241. actionPayload: '事件负载数据(可选)',
  242. disabled: '是否禁用(可选)',
  243. className: '自定义样式类名(可选)'
  244. },
  245. usageHint: '需要用户点击执行操作时使用',
  246. example: {
  247. type: 'button',
  248. label: '点击我',
  249. variant: 'primary',
  250. action: 'sendMessage',
  251. actionPayload: { message: '要发送的消息' }
  252. }
  253. },
  254. 'input': {
  255. type: 'input',
  256. description: '输入框组件,用于用户输入',
  257. schema: {
  258. placeholder: '占位符文本(可选)',
  259. value: '输入值(可选)',
  260. disabled: '是否禁用(可选)',
  261. className: '自定义样式类名(可选)'
  262. },
  263. usageHint: '需要用户输入文本时使用',
  264. example: {
  265. type: 'input',
  266. placeholder: '请输入...'
  267. }
  268. },
  269. 'badge': {
  270. type: 'badge',
  271. description: '徽章标签,显示状态或分类',
  272. schema: {
  273. text: '标签文本',
  274. variant: '标签样式:default, success, warning, error, info(可选)',
  275. className: '自定义样式类名(可选)'
  276. },
  277. usageHint: '需要显示状态或标签时使用',
  278. example: {
  279. type: 'badge',
  280. text: '已完结',
  281. variant: 'success'
  282. }
  283. },
  284. 'code-block': {
  285. type: 'code-block',
  286. description: '代码块展示组件',
  287. schema: {
  288. code: '代码内容',
  289. language: '编程语言(可选)',
  290. inline: '是否行内显示(可选)',
  291. className: '自定义样式类名(可选)'
  292. },
  293. usageHint: '需要显示代码时使用',
  294. example: {
  295. type: 'code-block',
  296. code: 'console.log("Hello");',
  297. language: 'javascript'
  298. }
  299. },
  300. 'data-table': {
  301. type: 'data-table',
  302. description: '数据表格,展示结构化数据',
  303. schema: {
  304. columns: '列定义数组,每列包含 key, label, sortable',
  305. rows: '行数据数组,每行是一个对象',
  306. sortable: '是否支持排序(可选)',
  307. className: '自定义样式类名(可选)'
  308. },
  309. usageHint: '需要展示表格数据时使用',
  310. example: {
  311. type: 'data-table',
  312. columns: [
  313. { key: 'name', label: '名称' },
  314. { key: 'value', label: '值' }
  315. ],
  316. rows: [
  317. { name: '项目1', value: '值1' },
  318. { name: '项目2', value: '值2' }
  319. ]
  320. }
  321. },
  322. 'separator': {
  323. type: 'separator',
  324. description: '分隔线,用于视觉分隔',
  325. schema: {
  326. orientation: '方向:horizontal(水平,默认)或 vertical(垂直)(可选)',
  327. className: '自定义样式类名(可选)'
  328. },
  329. usageHint: '需要在内容间添加分隔时使用',
  330. example: {
  331. type: 'separator',
  332. orientation: 'horizontal'
  333. }
  334. }
  335. };
  336. /**
  337. * 组件类型
  338. */
  339. export type ComponentType = keyof typeof AVAILABLE_COMPONENTS;
  340. /**
  341. * 生成组件使用说明文本
  342. *
  343. * 此函数将所有组件定义转换为发送给后端的提示词格式
  344. * 后端使用这些提示词构建 Claude 的 SYSTEM_PROMPT
  345. *
  346. * @returns 组件说明文本(Markdown 格式)
  347. */
  348. export function generateComponentsPrompt(): string {
  349. const components = Object.entries(AVAILABLE_COMPONENTS);
  350. // 分组:MCP 专用组件 和 基础组件
  351. const mcpComponents = components.filter(([key]) => {
  352. const config = AVAILABLE_COMPONENTS[key];
  353. return [
  354. 'translation-result', 'novel-list', 'novel-detail', 'chapter-reader',
  355. 'suggestion-buttons', 'mcp-tool-call', 'login-panel'
  356. ].includes(config.type);
  357. });
  358. const baseComponents = components.filter(([key]) => {
  359. const config = AVAILABLE_COMPONENTS[key];
  360. return ![
  361. 'translation-result', 'novel-list', 'novel-detail', 'chapter-reader',
  362. 'suggestion-buttons', 'mcp-tool-call', 'login-panel'
  363. ].includes(config.type);
  364. });
  365. let prompt = '## 可用的 json-render 组件\n\n';
  366. prompt += '你可以使用以下组件来展示结构化数据。组件是 JSON 对象,放在回复的 ```json 代码块中。\n\n';
  367. // MCP 专用组件
  368. if (mcpComponents.length > 0) {
  369. prompt += '### MCP 专用组件\n\n';
  370. prompt += '这些组件用于展示 MCP 工具调用的结果:\n\n';
  371. mcpComponents.forEach(([key, config]) => {
  372. prompt += `#### ${config.type}\n`;
  373. prompt += `${config.description}\n`;
  374. if (config.usageHint) {
  375. prompt += `> ${config.usageHint}\n`;
  376. }
  377. prompt += '\n**Schema:**\n';
  378. Object.entries(config.schema).forEach(([fieldName, description]) => {
  379. prompt += `- \`${fieldName}\`: ${description}\n`;
  380. });
  381. prompt += '\n**示例:**\n';
  382. prompt += '```json\n';
  383. prompt += JSON.stringify(config.example || { type: key }, null, 2);
  384. prompt += '\n```\n\n';
  385. });
  386. }
  387. // 基础组件
  388. if (baseComponents.length > 0) {
  389. prompt += '### 基础组件\n\n';
  390. prompt += '这些组件用于构建通用 UI:\n\n';
  391. baseComponents.forEach(([key, config]) => {
  392. prompt += `#### ${config.type}\n`;
  393. prompt += `${config.description}\n`;
  394. if (config.usageHint) {
  395. prompt += `> ${config.usageHint}\n`;
  396. }
  397. prompt += '\n**Schema:**\n';
  398. Object.entries(config.schema).forEach(([fieldName, description]) => {
  399. prompt += `- \`${fieldName}\`: ${description}\n`;
  400. });
  401. prompt += '\n**示例:**\n';
  402. prompt += '```json\n';
  403. prompt += JSON.stringify(config.example || { type: key }, null, 2);
  404. prompt += '\n```\n\n';
  405. });
  406. }
  407. return prompt;
  408. }
  409. /**
  410. * 获取组件定义
  411. */
  412. export function getComponentConfig(type: string): ComponentConfig | undefined {
  413. return AVAILABLE_COMPONENTS[type];
  414. }
  415. /**
  416. * 获取所有组件类型列表
  417. */
  418. export function getComponentTypes(): string[] {
  419. return Object.keys(AVAILABLE_COMPONENTS);
  420. }
  421. /**
  422. * 检查组件类型是否有效
  423. */
  424. export function isValidComponentType(type: string): boolean {
  425. return type in AVAILABLE_COMPONENTS;
  426. }