feat(json-renderer): 添加从 markdown 代码块中提取 JSON 的功能
- 新增 extractJsonFromMarkdown 函数,支持以下格式:
* ```json {...} ``` 代码块
* ``` {...} ``` 代码块
* 直接的 JSON 字符串
- 修改 data 处理逻辑,支持字符串类型输入并自动提取 JSON
- 支持对象和数组形式的 JSON spec
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user