page.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /**
  2. * json-render 组件测试页面
  3. *
  4. * 测试所有 15 个 json-render 组件的渲染效果
  5. */
  6. 'use client';
  7. import { useState } from 'react';
  8. import JsonRenderer from '@/components/JsonRenderer';
  9. import type { ComponentSpec } from '@/lib/json-render-catalog';
  10. // ============ 基础组件示例数据 ============
  11. const cardSpec: ComponentSpec = {
  12. type: 'card',
  13. title: '卡片标题',
  14. children: [
  15. {
  16. type: 'text',
  17. content: '这是一个卡片组件示例,用于包装和展示相关内容。',
  18. variant: 'body',
  19. },
  20. ],
  21. };
  22. const stackSpec: ComponentSpec = {
  23. type: 'stack',
  24. direction: 'row',
  25. spacing: 4,
  26. align: 'center',
  27. children: [
  28. {
  29. type: 'badge',
  30. text: '项目 A',
  31. variant: 'info',
  32. },
  33. {
  34. type: 'badge',
  35. text: '项目 B',
  36. variant: 'success',
  37. },
  38. {
  39. type: 'badge',
  40. text: '项目 C',
  41. variant: 'warning',
  42. },
  43. ],
  44. };
  45. const headingSpecs: ComponentSpec[] = [
  46. { type: 'heading', level: 'h1', text: 'Heading Level 1' },
  47. { type: 'heading', level: 'h2', text: 'Heading Level 2' },
  48. { type: 'heading', level: 'h3', text: 'Heading Level 3' },
  49. { type: 'heading', level: 'h4', text: 'Heading Level 4' },
  50. { type: 'heading', level: 'h5', text: 'Heading Level 5' },
  51. { type: 'heading', level: 'h6', text: 'Heading Level 6' },
  52. ];
  53. const textSpecs: ComponentSpec[] = [
  54. { type: 'text', content: '这是默认样式的正文文本,用于主要内容展示。', variant: 'body' },
  55. { type: 'text', content: '这是静音样式的文本,用于次要信息。', variant: 'muted' },
  56. { type: 'text', content: 'const code = "这是代码样式的文本";', variant: 'code' },
  57. ];
  58. const buttonSpecs: ComponentSpec[] = [
  59. { type: 'button', label: 'Default Button', variant: 'default' },
  60. { type: 'button', label: 'Primary Button', variant: 'primary' },
  61. { type: 'button', label: 'Secondary Button', variant: 'secondary' },
  62. { type: 'button', label: 'Ghost Button', variant: 'ghost' },
  63. { type: 'button', label: 'Danger Button', variant: 'danger' },
  64. { type: 'button', label: 'Disabled Button', variant: 'default', disabled: true },
  65. ];
  66. const inputSpecs: ComponentSpec[] = [
  67. { type: 'input', placeholder: '请输入用户名...' },
  68. { type: 'input', placeholder: '禁用状态', value: '固定值', disabled: true },
  69. ];
  70. const badgeSpecs: ComponentSpec[] = [
  71. { type: 'badge', text: 'Default', variant: 'default' },
  72. { type: 'badge', text: 'Success', variant: 'success' },
  73. { type: 'badge', text: 'Warning', variant: 'warning' },
  74. { type: 'badge', text: 'Error', variant: 'error' },
  75. { type: 'badge', text: 'Info', variant: 'info' },
  76. ];
  77. const separatorSpecs: ComponentSpec[] = [
  78. { type: 'separator', orientation: 'horizontal' },
  79. { type: 'separator', orientation: 'vertical' },
  80. ];
  81. // ============ MCP 组件示例数据 ============
  82. const translationResultSpec: ComponentSpec = {
  83. type: 'translation-result',
  84. translated: 'Lin Feng is an outer disciple of the Spirit Sect, possessing a mysterious cultivation technique that allows him to absorb the energy of heaven and earth.',
  85. termsUsed: ['Lin Feng', 'outer disciple', 'Spirit Sect', 'cultivation technique'],
  86. };
  87. const novelListSpec: ComponentSpec = {
  88. type: 'novel-list',
  89. novels: [
  90. {
  91. id: '1',
  92. title: '修仙之王',
  93. author: '张三',
  94. description: '一个凡人逆天改命,最终成就仙道的故事。',
  95. chapterCount: 1500,
  96. tags: ['修仙', '热血', '爽文'],
  97. },
  98. {
  99. id: '2',
  100. title: '都市医仙',
  101. author: '李四',
  102. description: '一代医仙重回都市,悬壶济世。',
  103. chapterCount: 800,
  104. tags: ['都市', '医术', '重生'],
  105. },
  106. {
  107. id: '3',
  108. title: '星际霸主',
  109. author: '王五',
  110. description: '银河系战神,征服星辰大海。',
  111. chapterCount: 2000,
  112. tags: ['科幻', '星际', '战争'],
  113. },
  114. ],
  115. };
  116. const chapterReaderSpec: ComponentSpec = {
  117. type: 'chapter-reader',
  118. novelTitle: '修仙之王',
  119. chapterTitle: '第一章: 重生',
  120. content: `林峰睁开眼睛,发现自己躺在一张简陋的木床上。
  121. 周围是熟悉的茅草屋,空气中弥漫着淡淡的药草味。他猛地坐起身来,看着自己瘦弱的手臂,难以置信地喃喃自语:"我...我竟然重生了?"
  122. 上一世,他是修仙界赫赫有名的丹尊,却因渡劫失败而陨落。没想到,竟然回到了少年时期,那个他还只是外门弟子的时候。
  123. "既然上天给我重来的机会,这一世,我定要登临仙道巅峰!"`,
  124. chapterNumber: 1,
  125. totalChapters: 1500,
  126. };
  127. const mcpToolCallSpecs: ComponentSpec[] = [
  128. {
  129. type: 'mcp-tool-call',
  130. tool: 'translate_text',
  131. status: 'pending',
  132. args: { text: 'Hello, world!', target_lang: 'zh' },
  133. timestamp: new Date().toISOString(),
  134. },
  135. {
  136. type: 'mcp-tool-call',
  137. tool: 'get_novels',
  138. status: 'running',
  139. args: { page: 1, limit: 10 },
  140. timestamp: new Date().toISOString(),
  141. },
  142. {
  143. type: 'mcp-tool-call',
  144. tool: 'translate_text',
  145. status: 'success',
  146. args: { text: 'Hello' },
  147. result: { translated: '你好' },
  148. timestamp: new Date().toISOString(),
  149. },
  150. {
  151. type: 'mcp-tool-call',
  152. tool: 'get_chapter',
  153. status: 'error',
  154. error: 'Chapter not found',
  155. timestamp: new Date().toISOString(),
  156. },
  157. ];
  158. const loginPanelSpec: ComponentSpec = {
  159. type: 'login-panel',
  160. server: 'novel-platform-user',
  161. };
  162. const codeBlockSpecs: ComponentSpec[] = [
  163. {
  164. type: 'code-block',
  165. code: 'function greet(name: string) {\n return `Hello, ${name}!`;\n}',
  166. language: 'typescript',
  167. },
  168. {
  169. type: 'code-block',
  170. code: 'SELECT * FROM users WHERE active = true;',
  171. language: 'sql',
  172. },
  173. {
  174. type: 'code-block',
  175. code: 'const inline = true;',
  176. language: 'javascript',
  177. inline: true,
  178. },
  179. ];
  180. const dataTableSpec: ComponentSpec = {
  181. type: 'data-table',
  182. columns: [
  183. { key: 'id', label: 'ID', sortable: true },
  184. { key: 'name', label: '名称', sortable: true },
  185. { key: 'status', label: '状态', sortable: true },
  186. { key: 'updated', label: '更新时间', sortable: true },
  187. ],
  188. rows: [
  189. { id: '1', name: '小说 A', status: '已完结', updated: '2026-03-15' },
  190. { id: '2', name: '小说 B', status: '连载中', updated: '2026-03-16' },
  191. { id: '3', name: '小说 C', status: '已完结', updated: '2026-03-17' },
  192. { id: '4', name: '小说 D', status: '连载中', updated: '2026-03-18' },
  193. ],
  194. sortable: true,
  195. };
  196. // ============ 测试页面组件 ============
  197. export default function TestPage() {
  198. const [selectedTab, setSelectedTab] = useState<'basic' | 'mcp'>('basic');
  199. return (
  200. <div className="min-h-screen bg-gray-50 dark:bg-gray-900">
  201. {/* 页面头部 */}
  202. <header className="bg-white dark:bg-gray-800 shadow-sm">
  203. <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
  204. <h1 className="text-3xl font-bold text-gray-900 dark:text-white">
  205. json-render 组件测试页面
  206. </h1>
  207. <p className="mt-2 text-gray-600 dark:text-gray-400">
  208. 测试所有 15 个 json-render 组件的渲染效果
  209. </p>
  210. </div>
  211. </header>
  212. {/* Tab 导航 */}
  213. <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-6">
  214. <div className="border-b border-gray-200 dark:border-gray-700">
  215. <nav className="-mb-px flex space-x-8">
  216. <button
  217. onClick={() => setSelectedTab('basic')}
  218. className={`${
  219. selectedTab === 'basic'
  220. ? 'border-blue-500 text-blue-600 dark:text-blue-400'
  221. : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
  222. } whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors`}
  223. >
  224. 基础组件 (8)
  225. </button>
  226. <button
  227. onClick={() => setSelectedTab('mcp')}
  228. className={`${
  229. selectedTab === 'mcp'
  230. ? 'border-blue-500 text-blue-600 dark:text-blue-400'
  231. : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
  232. } whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors`}
  233. >
  234. MCP 组件 (7)
  235. </button>
  236. </nav>
  237. </div>
  238. </div>
  239. {/* 内容区域 */}
  240. <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
  241. {selectedTab === 'basic' ? (
  242. <div className="space-y-12">
  243. {/* Card 组件 */}
  244. <section>
  245. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  246. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">1</span>
  247. Card - 卡片容器
  248. </h2>
  249. <JsonRenderer spec={cardSpec} />
  250. </section>
  251. {/* Stack 组件 */}
  252. <section>
  253. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  254. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">2</span>
  255. Stack - 弹性布局容器
  256. </h2>
  257. <JsonRenderer spec={stackSpec} />
  258. </section>
  259. {/* Heading 组件 */}
  260. <section>
  261. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  262. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">3</span>
  263. Heading - 标题 (h1-h6)
  264. </h2>
  265. <div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
  266. <JsonRenderer specs={headingSpecs} />
  267. </div>
  268. </section>
  269. {/* Text 组件 */}
  270. <section>
  271. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  272. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">4</span>
  273. Text - 文本内容
  274. </h2>
  275. <div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
  276. <JsonRenderer specs={textSpecs} />
  277. </div>
  278. </section>
  279. {/* Button 组件 */}
  280. <section>
  281. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  282. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">5</span>
  283. Button - 按钮
  284. </h2>
  285. <div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
  286. <div className="flex flex-wrap gap-3">
  287. <JsonRenderer specs={buttonSpecs} />
  288. </div>
  289. </div>
  290. </section>
  291. {/* Input 组件 */}
  292. <section>
  293. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  294. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">6</span>
  295. Input - 输入框
  296. </h2>
  297. <div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm space-y-3">
  298. <JsonRenderer specs={inputSpecs} />
  299. </div>
  300. </section>
  301. {/* Badge 组件 */}
  302. <section>
  303. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  304. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">7</span>
  305. Badge - 徽章标签
  306. </h2>
  307. <div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
  308. <div className="flex flex-wrap gap-3">
  309. <JsonRenderer specs={badgeSpecs} />
  310. </div>
  311. </div>
  312. </section>
  313. {/* Separator 组件 */}
  314. <section>
  315. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  316. <span className="w-6 h-6 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 rounded-full flex items-center justify-center text-xs">8</span>
  317. Separator - 分隔线
  318. </h2>
  319. <div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
  320. <div className="flex items-center gap-4">
  321. <span className="text-gray-600 dark:text-gray-400">左侧内容</span>
  322. <JsonRenderer specs={separatorSpecs} />
  323. <span className="text-gray-600 dark:text-gray-400">右侧内容</span>
  324. </div>
  325. <div className="mt-4">
  326. <JsonRenderer spec={separatorSpecs[0]} />
  327. </div>
  328. </div>
  329. </section>
  330. </div>
  331. ) : (
  332. <div className="space-y-12">
  333. {/* TranslationResult 组件 */}
  334. <section>
  335. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  336. <span className="w-6 h-6 bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 rounded-full flex items-center justify-center text-xs">1</span>
  337. TranslationResult - 翻译结果展示
  338. </h2>
  339. <JsonRenderer spec={translationResultSpec} />
  340. </section>
  341. {/* NovelList 组件 */}
  342. <section>
  343. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  344. <span className="w-6 h-6 bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 rounded-full flex items-center justify-center text-xs">2</span>
  345. NovelList - 小说列表
  346. </h2>
  347. <JsonRenderer spec={novelListSpec} />
  348. </section>
  349. {/* ChapterReader 组件 */}
  350. <section>
  351. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  352. <span className="w-6 h-6 bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 rounded-full flex items-center justify-center text-xs">3</span>
  353. ChapterReader - 章节阅读器
  354. </h2>
  355. <JsonRenderer spec={chapterReaderSpec} />
  356. </section>
  357. {/* McpToolCall 组件 */}
  358. <section>
  359. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  360. <span className="w-6 h-6 bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 rounded-full flex items-center justify-center text-xs">4</span>
  361. McpToolCall - 工具调用可视化
  362. </h2>
  363. <div className="space-y-3">
  364. <JsonRenderer specs={mcpToolCallSpecs} />
  365. </div>
  366. </section>
  367. {/* LoginPanel 组件 */}
  368. <section>
  369. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  370. <span className="w-6 h-6 bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 rounded-full flex items-center justify-center text-xs">5</span>
  371. LoginPanel - 登录面板
  372. </h2>
  373. <JsonRenderer spec={loginPanelSpec} />
  374. </section>
  375. {/* CodeBlock 组件 */}
  376. <section>
  377. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  378. <span className="w-6 h-6 bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 rounded-full flex items-center justify-center text-xs">6</span>
  379. CodeBlock - 代码块
  380. </h2>
  381. <div className="space-y-4">
  382. <JsonRenderer specs={codeBlockSpecs} />
  383. </div>
  384. </section>
  385. {/* DataTable 组件 */}
  386. <section>
  387. <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
  388. <span className="w-6 h-6 bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 rounded-full flex items-center justify-center text-xs">7</span>
  389. DataTable - 数据表格
  390. </h2>
  391. <JsonRenderer spec={dataTableSpec} />
  392. </section>
  393. </div>
  394. )}
  395. </main>
  396. {/* 页脚 */}
  397. <footer className="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12">
  398. <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
  399. <p className="text-sm text-gray-500 dark:text-gray-400">
  400. json-render 组件测试页面 | AI MCP Web UI - 模板 240
  401. </p>
  402. </div>
  403. </footer>
  404. </div>
  405. );
  406. }