diff --git a/.antigravity/BASELINE.md b/.antigravity/BASELINE.md index 734a08e..ea22a3e 100644 --- a/.antigravity/BASELINE.md +++ b/.antigravity/BASELINE.md @@ -5,8 +5,9 @@ - **核心理念**:通过 AI 驱动的消息流实现动态 UI 生成和业务处理。 - **技术栈**:Next.js 15 (App Router) + Tailwind CSS + Lucide Icons。 - **关键组件**: - - `UniversalModuleRenderer.tsx`: 后端 AI 下发 UI 指令的监听与执行枢纽,负责实时绘制界面。 - - `DynamicForm.tsx`: 基于 JSON Schema 的动态表单渲染器,支持实体关联选择 (`x-link-entity`)。 + - `UniversalModuleRenderer.tsx`: 后端 AI 下发 UI 指令的监听与执行枢纽。 + - `CopilotProvider.tsx`: **已更新**,取消本地 AI 运行时,改为直连后端 `/api/copilotkit` 端口。 + - `DynamicForm.tsx`: 基于 JSON Schema 的动态表单渲染器。 - `EntityDataPage`: 动态列表展示页面,通过 AI 代理执行编辑和删除操作。 ## 💼 业务逻辑 (Business Logic) @@ -16,17 +17,16 @@ ## 📈 当前开发进度 (Current Progress) - [x] **AI 动态 UI 渲染**:`UniversalModuleRenderer` 基础架构就绪。 - [x] **动态表单与关联**:支持枚举、模式匹配及实体级联选择。 -- [x] **会话隔离适配**:前端已实现 Header 注入逻辑。 -- [x] **CORS 联调**:已完成与后端的跨域联调。 +- [x] **安全优化**:移除了前端 API Key,所有 AI 请求通过后端转发。 +- [x] **业务模块共享**:适配后端变更,侧边栏现可按公共定义渲染所有活跃模块。 ## ⚠️ 尚未解决的隐患 (Known Issues/Risks) +- **共享冲突提示**:缺乏对公共模块并发编辑的 UI 级冲突提示。 - **状态同步**:多组件间由于 AI 异步返回导致的 UI 闪烁问题。 -- **离线能力**:目前完全依赖实时 AI 交互,缺乏离线暂存机制。 ## 🚀 下一步计划 (Next Steps) -1. **AI 智能搜索界面**:对接后端的 AI 增强搜索功能。 -2. **动态看板 (Dashboard)**:实现可视化图表的 AI 渲染支持。 -3. **引导流程优化**:提升用户通过对话创建新模块的体验。 +1. **数据权限展示**:在 UI 层面区分公共数据与私人数据。 +2. **AI 智能搜索界面**:对接后端的 AI 增强搜索功能。 --- *Last Updated: 2026-03-25 by Antigravity* diff --git a/src/app/api/copilotkit/route.ts b/src/app/api/copilotkit/route.ts deleted file mode 100644 index 866b517..0000000 --- a/src/app/api/copilotkit/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - CopilotRuntime, - GoogleGenerativeAIAdapter, - copilotRuntimeNextJSAppRouterEndpoint -} from "@copilotkit/runtime"; -import { GoogleGenerativeAI } from "@google/generative-ai"; - -export const POST = async (req: Request) => { - // 1. 初始化 Google Gemini 客户端 - const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY || ""); - - // 2. 选择模型 (使用 3.1 Pro) - const model = genAI.getGenerativeModel({ - model: "gemini-3.1-pro", - // 如果需要启用思维模型等高级参数,可以在这里或 adapter 层级配置 - }); - - const runtime = new CopilotRuntime(); - - // 3. 使用 GoogleGenerativeAIAdapter - const serviceAdapter = new GoogleGenerativeAIAdapter({ - model: "gemini-1.5-pro" - }); - - const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ - runtime, - serviceAdapter, - endpoint: "/api/copilotkit", - }); - - return handleRequest(req); -}; \ No newline at end of file diff --git a/src/app/data/[entityCode]/page.tsx b/src/app/data/[entityCode]/page.tsx index 8bbad81..773faba 100644 --- a/src/app/data/[entityCode]/page.tsx +++ b/src/app/data/[entityCode]/page.tsx @@ -123,27 +123,32 @@ export default function EntityDataPage({ params: paramsPromise }: DataPageProps) }, [entityCode]); const handleDelete = async (id: string) => { - if (!confirm("确定要物理删除这条数据吗?该操作不可撤销,将由 AI 执行。")) return; + if (!confirm("确定要删除这条数据吗?该操作不可撤销。")) return; try { const userId = localStorage.getItem("crm_user_id") || "anonymous"; const res = await fetch(`/api/agent/chat?sessionId=${userId}_OP`, { method: 'POST', headers: { 'X-Creator-Id': userId }, - body: `请执行核心物理删除指令:删除记录 ID 为 ${id} 的数据。` + body: `请执行删除操作:物理删除记录 ID 为 ${id} 的数据。` }); if (res.ok) { - alert("✅ AI 已同步执行删除操作。"); - loadData(); + const result = await res.json(); + if (result.code === 200) { + loadData(); + } else { + alert(`删除失败: ${result.msg || "未知错误"}`); + } + } else { + alert("删除失败: 请求异常"); } } catch (err) { - alert("删除失败"); + alert("删除失败: 网络故障"); } }; const handleEdit = (row: any) => { - // 排除系统字段 - const { _id, _createdAt, ...businessData } = row; - setEditingData(businessData); + const { _createdAt, ...editData } = row; + setEditingData(editData); setIsModalOpen(true); }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c08be0b..fc14fef 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,6 +15,7 @@ const geistMono = Geist_Mono({ import Sidebar from "@/components/Sidebar"; import CopilotProvider from "@/components/CopilotProvider"; import AuthGuard from "@/components/AuthGuard"; +import MainLayout from "@/components/MainLayout"; export const metadata: Metadata = { title: "AI-Native CRM", @@ -33,12 +34,9 @@ export default function RootLayout({ > - - -
- {children} -
-
+ + {children} +
diff --git a/src/app/page.tsx b/src/app/page.tsx index 07464f9..922129a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -17,7 +17,6 @@ export default function Home() {

👋 欢迎来到您的智能 AI 业务助理

-

测试模式,不能保存到后端! 以后发布后再测试吧

无需复杂的配置,直接告诉 AI 您想管理的内容。点击右下角的 Copilot 智能助手 并输入您的需求, 例如:“我想创建一个员工入职登记表,包含姓名、职位和入职时间”,系统将立即为您自动生成定制化的业务单据! diff --git a/src/components/CopilotProvider.tsx b/src/components/CopilotProvider.tsx index 5e125cd..ef5f4ac 100644 --- a/src/components/CopilotProvider.tsx +++ b/src/components/CopilotProvider.tsx @@ -6,7 +6,7 @@ import "@copilotkit/react-ui/styles.css"; export default function CopilotProvider({ children }: { children: React.ReactNode }) { return ( - + {children} void; } -/** - * 通用动态表单组件 (支持关联与业务逻辑) - */ export default function DynamicForm({ entityName, entityCode, jsonSchema, initialData, onSuccess }: DynamicFormProps) { - const [formData, setFormData] = useState>(initialData || {}); + const recordId = initialData?._id; + const isEdit = !!recordId; + + const buildInitial = () => { + if (!initialData) return {}; + const { _id, ...rest } = initialData; + return rest; + }; + + const [formData, setFormData] = useState>(buildInitial); const [isSaving, setIsSaving] = useState(false); const schemaObj = useMemo(() => { @@ -82,11 +88,19 @@ export default function DynamicForm({ entityName, entityCode, jsonSchema, initia setIsSaving(true); try { const userId = localStorage.getItem("crm_user_id") || "anonymous"; - const aiMessage = `【业务提交报文】\n` + - `当前模块:${entityName} (${entityCode})\n` + - `Schema 结构:${jsonSchema}\n` + - `录入事实数据:${JSON.stringify(formData)}\n\n` + - `请 AI 自动调度工具完成此模块的校验注册与数据入库工作。`; + + const aiMessage = isEdit + ? `【业务更新报文】\n` + + `操作类型:更新已有记录\n` + + `记录 ID:${recordId}\n` + + `当前模块:${entityName} (${entityCode})\n` + + `更新后数据:${JSON.stringify(formData)}\n\n` + + `请调用 updateDynamicData 工具,使用上述 ID 和数据完成更新。` + : `【业务提交报文】\n` + + `操作类型:新增记录\n` + + `当前模块:${entityName} (${entityCode})\n` + + `录入数据:${JSON.stringify(formData)}\n\n` + + `请调用 insertDynamicData 工具完成数据入库。`; const response = await fetch(`/api/agent/chat?sessionId=${userId}_SESSION_${entityCode}`, { method: 'POST', @@ -98,17 +112,20 @@ export default function DynamicForm({ entityName, entityCode, jsonSchema, initia }); if (response.ok) { - const resultText = await response.text(); - alert(`✅ AI 已成功处理!\n反馈: ${resultText}`); - window.dispatchEvent(new CustomEvent('ENTITY_CREATED')); - if (onSuccess) onSuccess(); - setFormData({}); + const result = await response.json(); + if (result.code === 200) { + window.dispatchEvent(new CustomEvent('ENTITY_CREATED')); + if (onSuccess) onSuccess(); + setFormData({}); + } else { + alert(`操作失败: ${result.msg || "未知错误"}`); + } } else { - const errorMsg = await response.text(); - alert(`❌ 处理失败: ${errorMsg}`); + const error = await response.json().catch(() => ({ msg: "请求失败" })); + alert(`操作失败: ${error.msg || "未知错误"}`); } } catch (err) { - alert("⚠️ 网络故障,请确保后端服务正常。"); + alert("网络故障,请确保后端服务正常。"); } finally { setIsSaving(false); } @@ -117,8 +134,8 @@ export default function DynamicForm({ entityName, entityCode, jsonSchema, initia return (

- - 录入: {entityName} + {isEdit ? "✏️" : "✨"} + {isEdit ? "编辑" : "录入"}: {entityName}

@@ -173,7 +190,7 @@ export default function DynamicForm({ entityName, entityCode, jsonSchema, initia disabled={isSaving} className="w-full mt-10 p-5 bg-gradient-to-br from-indigo-600 to-blue-700 text-white rounded-2xl font-black text-lg shadow-xl" > - {isSaving ? "AI 处理中..." : "🚀 提交业务记录"} + {isSaving ? "AI 处理中..." : (isEdit ? "💾 保存修改" : "🚀 提交业务记录")}
); diff --git a/src/components/MainLayout.tsx b/src/components/MainLayout.tsx new file mode 100644 index 0000000..6c0373e --- /dev/null +++ b/src/components/MainLayout.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import Sidebar from "./Sidebar"; +import CopilotProvider from "./CopilotProvider"; + +/** + * 主布局组件,根据当前路径决定是否渲染侧边栏和 AI 助理 + */ +export default function MainLayout({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + const isLoginPage = pathname === "/login"; + + if (isLoginPage) { + return
{children}
; + } + + return ( + + +
+ {children} +
+
+ ); +}