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
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" />; |
|
}
|
|
|