Ver código fonte

feat(json-renderer): 添加从 markdown 代码块中提取 JSON 的功能

- 新增 extractJsonFromMarkdown 函数,支持以下格式:
  * ```json {...} ``` 代码块
  * ``` {...} ``` 代码块
  * 直接的 JSON 字符串
- 修改 data 处理逻辑,支持字符串类型输入并自动提取 JSON
- 支持对象和数组形式的 JSON spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude AI 1 dia atrás
pai
commit
5034cfe6fa
1 arquivos alterados com 72 adições e 5 exclusões
  1. 72 5
      frontend-v2/components/JsonRenderer.tsx

+ 72 - 5
frontend-v2/components/JsonRenderer.tsx

@@ -3,17 +3,60 @@
  *
  * 基于 Vercel Labs json-render 的生成式 UI 渲染器
  * 接收 AI 生成的 spec 并渲染为 React 组件
+ * 支持组件交互事件(通过 ActionProvider 传递 emit 函数)
  */
 
 'use client';
 
-import { useState, useMemo } from 'react';
+import React, { useState, useMemo, useContext } from 'react';
 import { jsonRenderRegistry } from '@/lib/json-render-registry';
 import type { ComponentSpec } from '@/lib/json-render-catalog';
+import { ActionContext } from '@/lib/action-context'; // 导入 ActionContext
 
 // 避免直接导入 json-render 类型,使用 any 来处理类型兼容性
 type AnySpec = Record<string, any>;
 
+/**
+ * 从 markdown 代码块中提取 JSON
+ *
+ * 支持以下格式:
+ * - ```json {...} ```
+ * - ``` {...} ```
+ * - 直接的 JSON 字符串
+ */
+function extractJsonFromMarkdown(text: string): any | null {
+  if (typeof text !== 'string') {
+    return null;
+  }
+
+  // 1. 尝试匹配 ```json ... ``` 或 ``` ... ``` 代码块
+  const codeBlockMatch = text.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
+  if (codeBlockMatch) {
+    try {
+      return JSON.parse(codeBlockMatch[1]);
+    } catch {
+      return null;
+    }
+  }
+
+  // 2. 尝试匹配数组形式的代码块
+  const arrayBlockMatch = text.match(/```(?:json)?\s*(\[[\s\S]*?\])\s*```/);
+  if (arrayBlockMatch) {
+    try {
+      return JSON.parse(arrayBlockMatch[1]);
+    } catch {
+      return null;
+    }
+  }
+
+  // 3. 尝试直接解析整个文本
+  try {
+    return JSON.parse(text);
+  } catch {
+    return null;
+  }
+}
+
 interface JsonRendererProps {
   // 单个组件 spec
   spec?: ComponentSpec | null;
@@ -56,9 +99,25 @@ export default function JsonRenderer({
 
     // 处理原始 data
     if (data) {
-      // 尝试直接作为 spec
-      if (typeof data === 'object' && data !== null && 'type' in data) {
-        result.push(data as AnySpec);
+      let parsedData: any = null;
+
+      // 如果 data 是字符串,尝试从 markdown 中提取 JSON
+      if (typeof data === 'string') {
+        parsedData = extractJsonFromMarkdown(data);
+      } else if (typeof data === 'object' && data !== null && 'type' in data) {
+        // 直接作为 spec
+        parsedData = data;
+      }
+
+      // 如果成功解析了数据
+      if (parsedData) {
+        if (Array.isArray(parsedData)) {
+          // 如果是数组,添加所有元素
+          result.push(...parsedData);
+        } else if (parsedData.type) {
+          // 如果是单个 spec,添加它
+          result.push(parsedData);
+        }
       }
     }
 
@@ -108,6 +167,9 @@ export default function JsonRenderer({
     return null;
   }
 
+  // 获取 ActionContext 中的 emit 函数
+  const actionContext = useContext(ActionContext);
+
   // 渲染组件
   return (
     <div className={`json-renderer space-y-4 ${className}`}>
@@ -118,7 +180,12 @@ export default function JsonRenderer({
         const CustomComponent = (jsonRenderRegistry as any)[componentType];
 
         if (CustomComponent) {
-          return <CustomComponent key={index} {...componentSpec} />;
+          // 传递 emit 函数给组件(如果存在 ActionContext)
+          const props = {
+            ...componentSpec,
+            ...(actionContext && { emit: actionContext.emit }),
+          };
+          return <CustomComponent key={index} {...props} />;
         }
 
         // Fallback