From ee2d5e90ea83b1be270d46a08d7a2759098b858b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Dec 2025 00:24:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E9=99=90=E9=A2=9D=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/admin-settings-client.tsx | 203 +++++++++++++++--- app/(chat)/api/chat/route.ts | 8 +- app/api/admin/users/batch-limit/route.ts | 120 +++++++++++ 3 files changed, 304 insertions(+), 27 deletions(-) create mode 100644 app/api/admin/users/batch-limit/route.ts diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 122553b..83ba0bb 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -244,6 +244,12 @@ export function AdminSettingsClient() { const [userSearch, setUserSearch] = useState(""); const [userPage, setUserPage] = useState(1); const [userLimit] = useState(10); + + // 批量设置出图限额状态 + const [showBatchLimitDialog, setShowBatchLimitDialog] = useState(false); + const [batchLimitType, setBatchLimitType] = useState<"all" | "department" | "no_department">("all"); + const [batchLimitValue, setBatchLimitValue] = useState(""); + const [batchLimitDepartment, setBatchLimitDepartment] = useState(""); // 🆕 生图记录状态 const [imageSearch, setImageSearch] = useState(""); @@ -270,6 +276,12 @@ export function AdminSettingsClient() { `/api/admin/users?page=${userPage}&limit=${userLimit}&search=${encodeURIComponent(userSearch)}`, fetcher ); + + // 获取部门列表(用于批量设置) + const { data: departmentsData } = useSWR<{ departments: string[] }>( + "/api/admin/users/batch-limit", + fetcher + ); const { data: providersData, mutate: mutateProviders } = useSWR<{ providers: AiProvider[] }>("/api/admin/ai-providers", fetcher); const { data: modelsData, mutate: mutateModels } = useSWR<{ models: AiModel[] }>("/api/admin/ai-models", fetcher); const { data: storageData, mutate: mutateStorage } = useSWR<{ configs: StorageConfig[] }>("/api/admin/storage", fetcher); @@ -561,6 +573,35 @@ export function AdminSettingsClient() { } }; + // 批量设置出图限额 + const handleBatchSetLimit = async () => { + try { + const response = await fetch("/api/admin/users/batch-limit", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + type: batchLimitType, + limit: batchLimitValue === "" ? null : parseInt(batchLimitValue, 10), + department: batchLimitDepartment, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "批量设置失败"); + } + + toast.success(data.message); + setShowBatchLimitDialog(false); + setBatchLimitValue(""); + setBatchLimitDepartment(""); + mutateUsers(); + } catch (error) { + toast.error(error instanceof Error ? error.message : "批量设置失败"); + } + }; + // --- 网站设置逻辑 --- const handleSaveSiteSettings = async () => { try { @@ -940,33 +981,44 @@ export function AdminSettingsClient() { onChange={(e) => handleUserSearch(e.target.value)} className="max-w-md" /> - + + }} + className="flex items-center gap-2" + > + + 同步钉钉用户 + + {/* 用户列表 */} @@ -2769,6 +2821,105 @@ export function AdminSettingsClient() { + + {/* 批量设置出图限额对话框 */} + + + + 批量设置出图限额 + +
+ {/* 设置类型选择 */} +
+ + +
+ + {/* 部门选择(仅在按部门设置时显示) */} + {batchLimitType === "department" && ( +
+ + + {departmentsData?.departments?.length === 0 && ( +

暂无部门数据,请先同步钉钉用户

+ )} +
+ )} + + {/* 限额输入 */} +
+ +
+ setBatchLimitValue(e.target.value)} + className="flex-1" + /> + 张/月 +
+

+ 设置为空或0表示无限制 +

+
+ + {/* 预览信息 */} +
+

即将执行:

+

+ {batchLimitType === "all" && "将为所有用户设置出图限额"} + {batchLimitType === "department" && batchLimitDepartment && `将为部门「${batchLimitDepartment}」的用户设置出图限额`} + {batchLimitType === "department" && !batchLimitDepartment && "请先选择部门"} + {batchLimitType === "no_department" && "将为所有无部门用户设置出图限额"} + {" → "} + {batchLimitValue ? `${batchLimitValue} 张/月` : "无限制"} +

+
+
+ + + + +
+
); } diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index c34d667..e1f1c48 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -590,8 +590,14 @@ export async function POST(request: Request) { const geminiStream = createUIMessageStream({ execute: async ({ writer }) => { try { + // 🆕 只传递最后一条用户消息,避免上下文历史干扰生图效果 + // 用户反馈:带上下文记录会影响生图效果 + const lastUserMessage = uiMessages.filter((m) => m.role === "user").pop(); + const messagesForGemini = lastUserMessage ? [lastUserMessage] : uiMessages; + console.log("[Chat API] Gemini: Using only last user message (no history context)"); + // 转换消息,Gemini 需要 base64 格式的图片 - const modelMessages = await convertMessagesWithImages(uiMessages, "gemini"); + const modelMessages = await convertMessagesWithImages(messagesForGemini, "gemini"); // 调用 doGenerate 获取图片 const result = await (languageModel as any).doGenerate({ diff --git a/app/api/admin/users/batch-limit/route.ts b/app/api/admin/users/batch-limit/route.ts new file mode 100644 index 0000000..6513d31 --- /dev/null +++ b/app/api/admin/users/batch-limit/route.ts @@ -0,0 +1,120 @@ +import { NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { user } from "@/lib/db/schema"; +import { eq, isNull, or, sql } from "drizzle-orm"; +import { auth } from "@/app/(auth)/auth"; + +// 获取所有部门列表 +export async function GET() { + try { + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + // 检查是否为管理员 + const currentUser = await db.query.user.findFirst({ + where: eq(user.id, session.user.id), + }); + + if (currentUser?.role !== "admin") { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + // 获取所有不重复的部门列表 + const departments = await db + .selectDistinct({ department: user.department }) + .from(user) + .where(sql`${user.department} IS NOT NULL AND ${user.department} != ''`) + .orderBy(user.department); + + return NextResponse.json({ + departments: departments.map(d => d.department).filter(Boolean), + }); + } catch (error) { + console.error("[Batch Limit API] Error:", error); + return NextResponse.json({ error: "获取部门列表失败" }, { status: 500 }); + } +} + +// 批量设置出图限制 +export async function POST(request: Request) { + try { + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + // 检查是否为管理员 + const currentUser = await db.query.user.findFirst({ + where: eq(user.id, session.user.id), + }); + + if (currentUser?.role !== "admin") { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + const body = await request.json(); + const { type, limit, department } = body; + + // limit 为 null 表示无限制,为数字表示限制张数 + const limitValue = limit === null || limit === "" || limit === undefined ? null : parseInt(limit, 10); + + let updatedCount = 0; + + if (type === "all") { + // 批量设置所有用户 + const result = await db + .update(user) + .set({ monthlyImageLimit: limitValue }); + + // 获取更新的用户数 + const allUsers = await db.select({ id: user.id }).from(user); + updatedCount = allUsers.length; + + console.log(`[Batch Limit] 批量设置所有用户限额: ${limitValue ?? "无限制"}, 影响 ${updatedCount} 个用户`); + } else if (type === "department" && department) { + // 按部门批量设置 + await db + .update(user) + .set({ monthlyImageLimit: limitValue }) + .where(eq(user.department, department)); + + // 获取更新的用户数 + const deptUsers = await db.select({ id: user.id }).from(user).where(eq(user.department, department)); + updatedCount = deptUsers.length; + + console.log(`[Batch Limit] 批量设置部门 "${department}" 限额: ${limitValue ?? "无限制"}, 影响 ${updatedCount} 个用户`); + } else if (type === "no_department") { + // 设置无部门用户 + await db + .update(user) + .set({ monthlyImageLimit: limitValue }) + .where(or(isNull(user.department), eq(user.department, ""))); + + // 获取更新的用户数 + const noDeptUsers = await db + .select({ id: user.id }) + .from(user) + .where(or(isNull(user.department), eq(user.department, ""))); + updatedCount = noDeptUsers.length; + + console.log(`[Batch Limit] 批量设置无部门用户限额: ${limitValue ?? "无限制"}, 影响 ${updatedCount} 个用户`); + } else { + return NextResponse.json({ error: "无效的操作类型" }, { status: 400 }); + } + + return NextResponse.json({ + success: true, + message: `成功更新 ${updatedCount} 个用户的出图限额`, + updatedCount, + limit: limitValue, + }); + } catch (error) { + console.error("[Batch Limit API] Error:", error); + return NextResponse.json({ error: "批量设置失败" }, { status: 500 }); + } +} +