diff --git a/app/api/codegen/create/route.ts b/app/api/codegen/create/route.ts new file mode 100644 index 00000000..a49d3015 --- /dev/null +++ b/app/api/codegen/create/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server" +import { connectToDatabase } from "@/lib/db/mongo" +import { createCodegen } from "@/lib/db/codegen/mutaitons" +import { validateSession } from "@/lib/auth/middleware" + +export async function POST(req: NextRequest) { + try { + const authError = await validateSession() + if (authError) return authError + await connectToDatabase() + const body = await req.json() + if (!body || typeof body !== "object") { + return NextResponse.json({ error: "Invalid body" }, { status: 400 }) + } + await createCodegen(body) + return NextResponse.json({ success: true }) + } catch (error) { + return NextResponse.json({ error: (error as Error).message }, { status: 500 }) + } +} + +export const dynamic = "force-dynamic" \ No newline at end of file diff --git a/app/api/codegen/delete/route.ts b/app/api/codegen/delete/route.ts new file mode 100644 index 00000000..26071784 --- /dev/null +++ b/app/api/codegen/delete/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server" +import { connectToDatabase } from "@/lib/db/mongo" +import { CodegenModel } from "@/lib/db/codegen/schema" +import { validateSession } from "@/lib/auth/middleware" + +export async function POST(req: NextRequest) { + try { + const authError = await validateSession() + if (authError) return authError + await connectToDatabase() + const body = await req.json() + if (!body || typeof body !== "object" || !body._id) { + return NextResponse.json({ error: "Missing _id in body" }, { status: 400 }) + } + await CodegenModel.findByIdAndDelete(body._id) + return NextResponse.json({ success: true }) + } catch (error) { + return NextResponse.json({ error: (error as Error).message }, { status: 500 }) + } +} + +export const dynamic = "force-dynamic" \ No newline at end of file diff --git a/app/api/codegen/edit/route.ts b/app/api/codegen/edit/route.ts new file mode 100644 index 00000000..db49e018 --- /dev/null +++ b/app/api/codegen/edit/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from "next/server" +import { connectToDatabase } from "@/lib/db/mongo" +import { upsertCodegen } from "@/lib/db/codegen/mutaitons" +import { validateSession } from "@/lib/auth/middleware" + +export async function POST(req: NextRequest) { + try { + const authError = await validateSession() + if (authError) return authError + await connectToDatabase() + const body = await req.json() + + // 验证请求体和_id + if (!body || typeof body !== "object") { + return NextResponse.json({ error: "无效的请求体" }, { status: 400 }) + } + + if (!body._id) { + return NextResponse.json({ error: "缺少必要的_id字段" }, { status: 400 }) + } + + // 确保_id是有效的MongoDB ObjectId格式 + if (!/^[0-9a-fA-F]{24}$/.test(body._id)) { + return NextResponse.json({ error: "无效的_id格式" }, { status: 400 }) + } + + const result = await upsertCodegen(body) + + if (!result) { + return NextResponse.json({ error: "更新失败,未找到对应ID的记录" }, { status: 404 }) + } + + return NextResponse.json({ + success: true, + data: result, + message: "Codegen更新成功" + }) + } catch (error) { + console.error("编辑Codegen失败:", error) + return NextResponse.json({ + error: (error as Error).message || "服务器内部错误", + message: "Codegen更新失败" + }, { status: 500 }) + } +} + +export const dynamic = "force-dynamic" \ No newline at end of file diff --git a/app/main/layout.tsx b/app/main/layout.tsx index cb67806f..7c6f63cf 100644 --- a/app/main/layout.tsx +++ b/app/main/layout.tsx @@ -1,6 +1,6 @@ "use client" -import React, { useCallback } from "react" +import React from "react" import { Loading } from "@/components/biz/Loading" import useRoutes from "@/hooks/use-routes" import { useSession, signOut } from "next-auth/react" @@ -8,7 +8,6 @@ import { redirect } from "next/navigation" import { useEffect } from "react" import { AppSidebarLayout } from "@/components/biz/AppSidebarLayout" import { - useProviderModelModal, ProviderModelModalProvider, } from "@/app/commons/ProviderModelModal" import { type NavMainItem } from "@/components/biz/AppSidebarLayout/interface" @@ -26,29 +25,12 @@ function MainLayoutContent({ } }) { const routes = useRoutes() - const { openModal } = useProviderModelModal() // now it's safe to call - - // handle nav item click - const handleNavItemClick = useCallback( - (url: string) => { - // if click is settings item - if (url === "/main/settings") { - openModal() // call method to open modal - return true // return true means handled, no need to navigate - } - - // return false means not handled, need to navigate normally - return false - }, - [openModal], - ) return ( {children} diff --git a/app/main/settings/CodegenTable.tsx b/app/main/settings/CodegenTable.tsx new file mode 100644 index 00000000..a97ff73a --- /dev/null +++ b/app/main/settings/CodegenTable.tsx @@ -0,0 +1,229 @@ +import { useEffect, useState } from "react" +import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from "@/artifacts/shadcn-ui-renderer/components/ui/table" +import { Button } from "@/components/ui/button" +import { getCodegenList, getCodegenDetail, createCodegen, editCodegen, deleteCodegen } from "@/app/services/codegen/codegen.service" +import CodegenEditDialog from "./components/CodegenEditDialog" +import DeleteConfirmDialog from "./components/DeleteConfirmDialog" +import { Codegen, CodegenListItem } from "./components/types" + +export default function CodegenTable() { + const [list, setList] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [open, setOpen] = useState(false) + const [editing, setEditing] = useState(null) + const [form, setForm] = useState>({ + title: "", + description: "", + fullStack: "React", + guides: [], + model: "", + codeRendererUrl: "", + rules: [] + }) + const [formLoading, setFormLoading] = useState(false) + const [formError, setFormError] = useState(null) + // 删除确认相关状态 + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false) + const [itemToDelete, setItemToDelete] = useState(undefined) + const [deleteLoading, setDeleteLoading] = useState(false) + + // Codegen 列表 + const load = async () => { + try { + setLoading(true) + setError(null) + const res = await getCodegenList({ page: 1, pageSize: 100 }) + // 类型转换以满足TypeScript要求 + setList(res.data || []) + } catch (err) { + console.error("加载Codegen列表失败:", err) + setError("加载数据失败,请稍后重试") + } finally { + setLoading(false) + } + } + + useEffect(() => { load() }, []) + + // 打开弹窗 + const handleEdit = async (item: CodegenListItem | null) => { + if (!item) { + // 新增时使用默认值 + setEditing(null) + setForm({ + title: "", + description: "", + fullStack: "React", + guides: [], + model: "", + codeRendererUrl: "", + rules: [] + }) + setOpen(true) + return + } + + try { + // 编辑时通过detail接口获取完整信息 + setFormLoading(true) + setFormError(null) + const result = await getCodegenDetail({ id: item._id || "" }) + if (result && result.data) { + const detailData = result.data as Partial + setEditing(item) + setForm({ + ...detailData, + guides: detailData.guides || [], + model: detailData.model || "", + rules: detailData.rules || [] + }) + } else { + throw new Error("获取详情失败") + } + } catch (error) { + console.error("获取Codegen详情失败:", error) + setFormError("获取详情失败,请重试") + // 降级使用列表数据 + setEditing(item) + setForm({ + ...item, + guides: item.guides || [], + model: "", + rules: item.rules || [] + }) + } finally { + setFormLoading(false) + setOpen(true) + } + } + + // 提交 + const handleSubmit = async () => { + try { + // 确保所有必需字段都有值 + if (!form.title || !form.description || !form.model || !form.codeRendererUrl) { + setFormError("请填写所有必填字段:标题、描述、模型和渲染器URL") + return + } + + // 准备提交数据,确保guides和rules字段是数组 + const submitData = { + ...form, + guides: Array.isArray(form.guides) ? form.guides : [], + rules: Array.isArray(form.rules) ? form.rules : [] + } + + if (editing && editing._id) { + await editCodegen({ ...submitData, _id: editing._id }) + } else { + await createCodegen(submitData) + } + setOpen(false) + load() + } catch (err) { + console.error("保存Codegen失败:", err) + setFormError((err as Error).message || "保存失败,请重试") + } + } + + // 打开删除确认对话框 + const openDeleteConfirm = (item?: CodegenListItem) => { + if (!item || !item._id) { + console.error("删除失败: 缺少ID或项目信息") + return + } + setItemToDelete(item._id) + setDeleteConfirmOpen(true) + } + + // 执行删除操作 + const confirmDelete = async () => { + if (!itemToDelete) return + + try { + setDeleteLoading(true) + await deleteCodegen(itemToDelete) + setDeleteConfirmOpen(false) + setItemToDelete(undefined) + await load() // 重新加载列表 + } catch (err) { + console.error("删除Codegen失败:", err) + // 这里可以添加错误提示UI + } finally { + setDeleteLoading(false) + } + } + + if (loading) { + return
加载中...
+ } + + if (error) { + return ( +
+ {error} + +
+ ) + } + + return ( +
+
+ +
+ + + + 标题 + 描述 + 类型 + 操作 + + + + {list.length === 0 ? ( + + 暂无数据 + + ) : ( + list.map(item => ( + + {item.title} + {item.description} + {item.fullStack} + + + + + + )) + )} + +
+ + {/* 使用抽离的编辑对话框组件 */} + handleEdit(editing)} + /> + + {/* 使用抽离的删除确认对话框组件 */} + item._id === itemToDelete)?.title} + /> +
+ ) +} \ No newline at end of file diff --git a/app/main/settings/components/CodegenEditDialog.tsx b/app/main/settings/components/CodegenEditDialog.tsx new file mode 100644 index 00000000..75b4b2a2 --- /dev/null +++ b/app/main/settings/components/CodegenEditDialog.tsx @@ -0,0 +1,237 @@ +import React from "react" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Textarea } from "@/components/ui/textarea" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import GuidesConfig from "./GuidesConfig" +import RulesConfig from "./RulesConfig" +import { Codegen, CodegenListItem } from "./types" +import { Loader2 } from "lucide-react" + +interface CodegenEditDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + editing: CodegenListItem | null + formData: Partial + formLoading: boolean + formError: string | null + onFormChange: (form: Partial) => void + onSubmit: () => Promise + onRetry: () => void +} + +export default function CodegenEditDialog({ + open, + onOpenChange, + editing, + formData, + formLoading, + formError, + onFormChange, + onSubmit, + onRetry +}: CodegenEditDialogProps) { + // 选项卡状态 + const [activeTab, setActiveTab] = React.useState("basic") + + // 表单字段更新处理函数 + const handleInputChange = (field: string, value: any) => { + onFormChange({ ...formData, [field]: value }) + } + + // 表单验证 + const isFormValid = () => { + return !!( + formData.title && + formData.description && + formData.codeRendererUrl + ) + } + + // 关闭时重置选项卡 + React.useEffect(() => { + if (!open) { + // 延迟重置,确保动画结束后再重置 + const timer = setTimeout(() => { + setActiveTab("basic") + }, 300) + return () => clearTimeout(timer) + } + }, [open]) + + return ( + + + {/* 固定头部 */} + +
+ + {editing ? "编辑" : "新增"} Codegen + + + {editing ? "修改现有的Codegen配置" : "添加新的Codegen配置并设置相关参数"} + +
+
+ + {/* 可滚动的内容区域 */} +
+ {formLoading ? ( +
+ +

正在加载详情...

+
+ ) : formError ? ( +
+
+

{formError}

+

请重试或检查网络连接

+
+ +
+ ) : ( +
+ + + 基本信息 + 指南配置 + 规则配置 + + + +
+
+

标题 *

+ handleInputChange("title", e.target.value)} + /> +
+
+

框架类型 *

+ +
+
+ +
+

描述 *

+