You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

81 lines
4.5 KiB

"use client";
import { useCopilotAction } from "@copilotkit/react-core";
import { useState } from "react";
export default function UniversalModuleRenderer() {
const [currentSchema, setCurrentSchema] = useState<any>(null);
// 核心:这个 Action 让 AI 能够在聊天流中不仅回复文字,更能直接把这段 React 树“扔”到前端界面里!
useCopilotAction({
name: "renderDynamicForm",
description: "在页面上为一个新的业务模块渲染基于 JSON Schema 的动态表单,使用在对话中。",
parameters: [
{ name: "entityName", type: "string", description: "表单或业务实体的中文名称,如'客户拜访记录'" },
{ name: "entityCode", type: "string", description: "该业务的唯一英文编码,如'customer_visit'" },
{ name: "jsonSchema", type: "string", description: "完全按照刚才设计的 JSON Schema 标准化字符串" }
],
handler: async ({ entityName, entityCode, jsonSchema }) => {
// 这个动作代表 AI 的渲染指令已经送达前端
return "UI 渲染通道已打开并呈现给用户。";
},
render: ({ status, args }) => {
// 状态机:大模型还在努力生成字的时候显示打草稿状态
if (status === "inProgress") {
return (
<div className="p-4 border-2 border-dashed border-blue-200 rounded-lg bg-blue-50/50 animate-pulse text-sm text-blue-600">
<strong>{args.entityName || '未知模块'}</strong> (Generative UI IN PROGRESS)...
</div>
);
}
// 状态机:大模型生成完毕
if (!args.jsonSchema) return null;
try {
// 安全地解析大模型幻觉可能导致的脏 JSON
const schemaObj = JSON.parse(args.jsonSchema);
// 动态构建出完整的可填报表单
return (
<div className="p-6 border rounded-xl shadow-lg bg-white mt-4 transition-all duration-300 transform scale-100">
<div className="flex items-center justify-between mb-6 border-b pb-4">
<h3 className="text-xl font-bold text-gray-800">{args.entityName}</h3>
<span className="text-xs bg-zinc-100 px-2 py-1 rounded text-zinc-500">{args.entityCode}</span>
</div>
<div className="space-y-5">
{Object.entries(schemaObj.properties || {}).map(([key, field]: [string, any]) => (
<div key={key} className="flex flex-col space-y-1.5">
<label className="text-sm font-semibold text-gray-700">{field.title || key}</label>
<input
type={field.type === 'number' ? 'number' : 'text'}
className="w-full border border-gray-300 p-2.5 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all shadow-sm text-sm"
placeholder={`请输入 ${field.title || key}`}
/>
</div>
))}
</div>
<div className="mt-8 pt-4 border-t flex justify-end">
<button
className="px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium shadow-md transition-colors"
onClick={() => alert('这里将调用 Spring Boot 的 /api/dynamic 接口入库!')}
>
JSONB
</button>
</div>
</div>
);
} catch (e) {
// 回退 / 降级 UI (Fallback UI)
return (
<div className="p-4 text-red-500 bg-red-50 border border-red-200 rounded-lg mt-4 text-sm">
<p className="font-bold mb-2"></p>
<p className="text-xs break-all">{args.jsonSchema}</p>
</div>
);
}
}
});
return <div className="hidden" aria-hidden="true" />;
}