|
|
@@ -1,336 +0,0 @@
|
|
|
-/**
|
|
|
- * json-render 组件目录
|
|
|
- *
|
|
|
- * 定义 MCP 工具专用组件和基础 shadcn 组件
|
|
|
- * 用于生成式 UI (Generative UI) 渲染
|
|
|
- */
|
|
|
-
|
|
|
-'use client';
|
|
|
-
|
|
|
-import { z } from 'zod';
|
|
|
-
|
|
|
-// ============ 基础组件 Schema 定义 ============
|
|
|
-
|
|
|
-// Card 组件
|
|
|
-export const CardSchema = z.object({
|
|
|
- type: z.literal('card'),
|
|
|
- title: z.string().optional(),
|
|
|
- children: z.array(z.any()).optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// Stack 组件
|
|
|
-export const StackSchema = z.object({
|
|
|
- type: z.literal('stack'),
|
|
|
- direction: z.enum(['row', 'column']).optional(),
|
|
|
- spacing: z.number().optional(),
|
|
|
- align: z.enum(['start', 'center', 'end', 'stretch']).optional(),
|
|
|
- children: z.array(z.any()).optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// Heading 组件
|
|
|
-export const HeadingSchema = z.object({
|
|
|
- type: z.literal('heading'),
|
|
|
- level: z.enum(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']).optional(),
|
|
|
- text: z.string(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// Text 组件
|
|
|
-export const TextSchema = z.object({
|
|
|
- type: z.literal('text'),
|
|
|
- content: z.string(),
|
|
|
- variant: z.enum(['body', 'muted', 'code']).optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// Button 组件
|
|
|
-export const ButtonSchema = z.object({
|
|
|
- type: z.literal('button'),
|
|
|
- label: z.string(),
|
|
|
- variant: z.enum(['default', 'primary', 'secondary', 'ghost', 'danger']).optional(),
|
|
|
- onClick: z.string().optional(), // JavaScript 代码字符串
|
|
|
- disabled: z.boolean().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// Input 组件
|
|
|
-export const InputSchema = z.object({
|
|
|
- type: z.literal('input'),
|
|
|
- placeholder: z.string().optional(),
|
|
|
- value: z.string().optional(),
|
|
|
- onChange: z.string().optional(), // JavaScript 代码字符串
|
|
|
- disabled: z.boolean().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// Badge 组件
|
|
|
-export const BadgeSchema = z.object({
|
|
|
- type: z.literal('badge'),
|
|
|
- text: z.string(),
|
|
|
- variant: z.enum(['default', 'success', 'warning', 'error', 'info']).optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// Separator 组件
|
|
|
-export const SeparatorSchema = z.object({
|
|
|
- type: z.literal('separator'),
|
|
|
- orientation: z.enum(['horizontal', 'vertical']).optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// ============ MCP 专用组件 Schema 定义 ============
|
|
|
-
|
|
|
-// TranslationResult 组件 - 翻译结果展示
|
|
|
-export const TranslationResultSchema = z.object({
|
|
|
- type: z.literal('translation-result'),
|
|
|
- original: z.string(),
|
|
|
- translated: z.string(),
|
|
|
- sourceLang: z.string().optional(),
|
|
|
- targetLang: z.string().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// NovelList 组件 - 小说列表
|
|
|
-export const NovelListSchema = z.object({
|
|
|
- type: z.literal('novel-list'),
|
|
|
- novels: z.array(z.object({
|
|
|
- id: z.string(),
|
|
|
- title: z.string(),
|
|
|
- author: z.string().optional(),
|
|
|
- description: z.string().optional(),
|
|
|
- chapterCount: z.number().optional(),
|
|
|
- tags: z.array(z.string()).optional(),
|
|
|
- })),
|
|
|
- onSelect: z.string().optional(), // 回调函数名
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// ChapterReader 组件 - 章节阅读器
|
|
|
-export const ChapterReaderSchema = z.object({
|
|
|
- type: z.literal('chapter-reader'),
|
|
|
- novelTitle: z.string(),
|
|
|
- chapterTitle: z.string(),
|
|
|
- content: z.string(),
|
|
|
- chapterNumber: z.number().optional(),
|
|
|
- totalChapters: z.number().optional(),
|
|
|
- onPrev: z.string().optional(),
|
|
|
- onNext: z.string().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// McpToolCall 组件 - 工具调用过程展示
|
|
|
-export const McpToolCallSchema = z.object({
|
|
|
- type: z.literal('mcp-tool-call'),
|
|
|
- tool: z.string(),
|
|
|
- status: z.enum(['pending', 'running', 'success', 'error']),
|
|
|
- args: z.any().optional(),
|
|
|
- result: z.any().optional(),
|
|
|
- error: z.string().optional(),
|
|
|
- timestamp: z.string().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// LoginPanel 组件 - Platform MCP 登录面板
|
|
|
-export const LoginPanelSchema = z.object({
|
|
|
- type: z.literal('login-panel'),
|
|
|
- server: z.string(),
|
|
|
- email: z.string().optional(),
|
|
|
- onLogin: z.string().optional(),
|
|
|
- loading: z.boolean().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// CodeBlock 组件 - 代码块展示
|
|
|
-export const CodeBlockSchema = z.object({
|
|
|
- type: z.literal('code-block'),
|
|
|
- code: z.string(),
|
|
|
- language: z.string().optional(),
|
|
|
- inline: z.boolean().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// DataTable 组件 - 数据表格
|
|
|
-export const DataTableSchema = z.object({
|
|
|
- type: z.literal('data-table'),
|
|
|
- columns: z.array(z.object({
|
|
|
- key: z.string(),
|
|
|
- label: z.string(),
|
|
|
- sortable: z.boolean().optional(),
|
|
|
- })),
|
|
|
- rows: z.array(z.record(z.string(), z.any())),
|
|
|
- sortable: z.boolean().optional(),
|
|
|
- onSort: z.string().optional(),
|
|
|
- className: z.string().optional(),
|
|
|
-});
|
|
|
-
|
|
|
-// ============ 导出联合类型 ============
|
|
|
-
|
|
|
-export const ComponentSchema = z.discriminatedUnion('type', [
|
|
|
- CardSchema,
|
|
|
- StackSchema,
|
|
|
- HeadingSchema,
|
|
|
- TextSchema,
|
|
|
- ButtonSchema,
|
|
|
- InputSchema,
|
|
|
- BadgeSchema,
|
|
|
- SeparatorSchema,
|
|
|
- TranslationResultSchema,
|
|
|
- NovelListSchema,
|
|
|
- ChapterReaderSchema,
|
|
|
- McpToolCallSchema,
|
|
|
- LoginPanelSchema,
|
|
|
- CodeBlockSchema,
|
|
|
- DataTableSchema,
|
|
|
-]);
|
|
|
-
|
|
|
-export type ComponentSpec = z.infer<typeof ComponentSchema>;
|
|
|
-
|
|
|
-// ============ 工具函数 ============
|
|
|
-
|
|
|
-/**
|
|
|
- * 验证 JSON spec 是否为有效的组件定义
|
|
|
- */
|
|
|
-export function validateComponentSpec(data: unknown): ComponentSpec | null {
|
|
|
- const result = ComponentSchema.safeParse(data);
|
|
|
- return result.success ? result.data : null;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 从 MCP 工具调用结果生成组件 spec
|
|
|
- */
|
|
|
-export function specFromToolCall(tool: string, data: unknown): ComponentSpec | null {
|
|
|
- console.log('[specFromToolCall] Input:', { tool, data });
|
|
|
- const dataObj = data as Record<string, unknown>;
|
|
|
-
|
|
|
- switch (tool) {
|
|
|
- case 'translate_text':
|
|
|
- // 解析 result 字段(可能是嵌套的 JSON 字符串)
|
|
|
- let translatedText = '';
|
|
|
- let originalText = (dataObj.original as string) || '';
|
|
|
- let sourceLang = dataObj.source_lang as string || 'auto';
|
|
|
- let targetLang = dataObj.target_lang as string || 'en';
|
|
|
-
|
|
|
- // 从 args 获取原始文本和语言设置
|
|
|
- if (dataObj.args && typeof dataObj.args === 'object') {
|
|
|
- const args = dataObj.args as Record<string, unknown>;
|
|
|
- originalText = (args.text as string) || originalText;
|
|
|
- sourceLang = (args.src_lang as string) || sourceLang;
|
|
|
- targetLang = (args.tgt_lang as string) || targetLang;
|
|
|
- }
|
|
|
-
|
|
|
- console.log('[specFromToolCall] translate_text - Starting parse:', {
|
|
|
- originalText,
|
|
|
- sourceLang,
|
|
|
- targetLang,
|
|
|
- resultType: typeof dataObj.result,
|
|
|
- result: dataObj.result
|
|
|
- });
|
|
|
-
|
|
|
- // 解析嵌套的 result 字段
|
|
|
- // 结构可能是: { result: { result: "JSON string" } } 或 { result: "JSON string" }
|
|
|
- let resultData = dataObj.result;
|
|
|
-
|
|
|
- // 处理嵌套结构: { result: { result: "..." } }
|
|
|
- if (typeof resultData === 'object' && resultData !== null) {
|
|
|
- const resultObj = resultData as Record<string, unknown>;
|
|
|
- console.log('[specFromToolCall] result is object, keys:', Object.keys(resultObj));
|
|
|
- if (typeof resultObj.result === 'string') {
|
|
|
- resultData = resultObj.result;
|
|
|
- console.log('[specFromToolCall] Extracted nested result string');
|
|
|
- } else if (typeof resultObj.result === 'object' && resultObj.result !== null) {
|
|
|
- // 更深层嵌套: { result: { result: { translated: "..." } } }
|
|
|
- const nestedResult = resultObj.result as Record<string, unknown>;
|
|
|
- if (typeof nestedResult.translated === 'string') {
|
|
|
- translatedText = nestedResult.translated;
|
|
|
- console.log('[specFromToolCall] Found translated in nested object:', translatedText);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果还是字符串,尝试解析 JSON
|
|
|
- if (!translatedText && typeof resultData === 'string') {
|
|
|
- try {
|
|
|
- const parsedResult = JSON.parse(resultData);
|
|
|
- translatedText = parsedResult.translated || '';
|
|
|
- console.log('[specFromToolCall] Parsed JSON string, translated:', translatedText);
|
|
|
- } catch {
|
|
|
- // 解析失败,尝试直接使用
|
|
|
- translatedText = resultData;
|
|
|
- console.log('[specFromToolCall] JSON parse failed, using raw string');
|
|
|
- }
|
|
|
- } else if (!translatedText && typeof resultData === 'object' && resultData !== null) {
|
|
|
- translatedText = (resultData as Record<string, unknown>).translated as string || '';
|
|
|
- console.log('[specFromToolCall] Got translated from object:', translatedText);
|
|
|
- }
|
|
|
-
|
|
|
- console.log('[specFromToolCall] translate_text - Final result:', {
|
|
|
- originalText,
|
|
|
- translatedText,
|
|
|
- sourceLang,
|
|
|
- targetLang
|
|
|
- });
|
|
|
-
|
|
|
- return {
|
|
|
- type: 'translation-result',
|
|
|
- original: originalText,
|
|
|
- translated: translatedText,
|
|
|
- sourceLang: sourceLang === 'auto' ? 'zh' : sourceLang, // 推断源语言
|
|
|
- targetLang: targetLang,
|
|
|
- };
|
|
|
-
|
|
|
- case 'get_novels':
|
|
|
- return {
|
|
|
- type: 'novel-list',
|
|
|
- novels: (dataObj.novels as Array<any>) || [],
|
|
|
- };
|
|
|
-
|
|
|
- case 'get_chapter':
|
|
|
- return {
|
|
|
- type: 'chapter-reader',
|
|
|
- novelTitle: (dataObj.novel_title as string) || 'Unknown Novel',
|
|
|
- chapterTitle: (dataObj.chapter_title as string) || `Chapter ${dataObj.chapter_number || 1}`,
|
|
|
- content: (dataObj.content as string) || '',
|
|
|
- chapterNumber: dataObj.chapter_number as number,
|
|
|
- totalChapters: dataObj.total_chapters as number,
|
|
|
- };
|
|
|
-
|
|
|
- case 'mcp_tool_call':
|
|
|
- return {
|
|
|
- type: 'mcp-tool-call',
|
|
|
- tool: (dataObj.tool as string) || tool,
|
|
|
- status: (dataObj.status as any) || 'success',
|
|
|
- args: dataObj.args,
|
|
|
- result: dataObj.result,
|
|
|
- error: dataObj.error as string,
|
|
|
- timestamp: dataObj.timestamp as string,
|
|
|
- };
|
|
|
-
|
|
|
- case 'login':
|
|
|
- return {
|
|
|
- type: 'login-panel',
|
|
|
- server: (dataObj.server as string) || 'Novel Platform',
|
|
|
- email: dataObj.email as string,
|
|
|
- };
|
|
|
-
|
|
|
- default:
|
|
|
- // 默认返回代码块组件显示原始数据
|
|
|
- return {
|
|
|
- type: 'code-block',
|
|
|
- code: JSON.stringify(data, null, 2),
|
|
|
- language: 'json',
|
|
|
- };
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 从多个工具调用结果生成组件列表
|
|
|
- */
|
|
|
-export function specsFromToolCalls(
|
|
|
- toolCalls: Array<{ tool: string; result: unknown }>
|
|
|
-): ComponentSpec[] {
|
|
|
- return toolCalls
|
|
|
- .map(({ tool, result }) => specFromToolCall(tool, result))
|
|
|
- .filter((spec): spec is ComponentSpec => spec !== null);
|
|
|
-}
|