feat: add high vip model visibility control
This commit is contained in:
parent
2e837b3769
commit
fbb2e186b9
@ -98,6 +98,7 @@ type User = {
|
|||||||
name: string | null;
|
name: string | null;
|
||||||
department: string | null;
|
department: string | null;
|
||||||
role: "guest" | "regular" | "admin";
|
role: "guest" | "regular" | "admin";
|
||||||
|
isHighVip: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
monthlyImageLimit: number | null;
|
monthlyImageLimit: number | null;
|
||||||
pointsBalance: number;
|
pointsBalance: number;
|
||||||
@ -2286,6 +2287,9 @@ export function AdminSettingsClient() {
|
|||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
角色: {ROLE_LABELS[user.role] ?? user.role} | 状态: {user.isActive ? "已激活" : "未激活"}
|
角色: {ROLE_LABELS[user.role] ?? user.role} | 状态: {user.isActive ? "已激活" : "未激活"}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
标签: {user.isHighVip ? "高级VIP" : "普通"}
|
||||||
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
月度出图限额: {user.monthlyImageLimit ? `${user.monthlyImageLimit} 张/月` : "无限制"}
|
月度出图限额: {user.monthlyImageLimit ? `${user.monthlyImageLimit} 张/月` : "无限制"}
|
||||||
</div>
|
</div>
|
||||||
@ -2342,6 +2346,13 @@ export function AdminSettingsClient() {
|
|||||||
>
|
>
|
||||||
{user.isActive ? "禁用" : "激活"}
|
{user.isActive ? "禁用" : "激活"}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={user.isHighVip ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleUpdateUser(user.id, { isHighVip: !user.isHighVip })}
|
||||||
|
>
|
||||||
|
{user.isHighVip ? "VIP开" : "VIP关"}
|
||||||
|
</Button>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
getAgentAiModelWithProvider,
|
getAgentAiModelWithProvider,
|
||||||
getDefaultAiModel,
|
getDefaultAiModel,
|
||||||
getEnabledAiModelsWithProvider,
|
getEnabledAiModelsWithProvider,
|
||||||
|
getUserById,
|
||||||
} from "@/lib/db/queries";
|
} from "@/lib/db/queries";
|
||||||
import { ChatSDKError } from "@/lib/errors";
|
import { ChatSDKError } from "@/lib/errors";
|
||||||
import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags";
|
import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags";
|
||||||
@ -29,13 +30,21 @@ export async function GET() {
|
|||||||
return new ChatSDKError("unauthorized:chat").toResponse();
|
return new ChatSDKError("unauthorized:chat").toResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentUser = await getUserById({ id: session.user.id });
|
||||||
|
const allowHiddenModels =
|
||||||
|
session.user.type === "admin" || Boolean(currentUser?.isHighVip);
|
||||||
|
|
||||||
// 只获取主模型(不包括子模型),子模型由后端根据 videoParams.mode 自动选择
|
// 只获取主模型(不包括子模型),子模型由后端根据 videoParams.mode 自动选择
|
||||||
const modelsWithProvider = await getEnabledAiModelsWithProvider(false, false);
|
const modelsWithProvider = await getEnabledAiModelsWithProvider(false, true);
|
||||||
const agentModelWithProvider = await getAgentAiModelWithProvider();
|
const agentModelWithProvider = await getAgentAiModelWithProvider();
|
||||||
const defaultModel = await getDefaultAiModel();
|
const defaultModel = await getDefaultAiModel();
|
||||||
const visibleModelsWithProvider = sortModelsForDisplay(
|
const visibleModelsWithProvider = sortModelsForDisplay(
|
||||||
modelsWithProvider
|
modelsWithProvider
|
||||||
.filter(({ model }) => !isAiModelAgentAuto(model) && !model.isHidden)
|
.filter(
|
||||||
|
({ model }) =>
|
||||||
|
!isAiModelAgentAuto(model) &&
|
||||||
|
(allowHiddenModels || !model.isHidden)
|
||||||
|
)
|
||||||
.map((item) => item.model)
|
.map((item) => item.model)
|
||||||
);
|
);
|
||||||
const visibleModelIdSet = new Set(visibleModelsWithProvider.map((m) => m.id));
|
const visibleModelIdSet = new Set(visibleModelsWithProvider.map((m) => m.id));
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { auth } from "../(auth)/auth";
|
|||||||
import {
|
import {
|
||||||
getDefaultAiModel,
|
getDefaultAiModel,
|
||||||
getEnabledAiModelsWithProvider,
|
getEnabledAiModelsWithProvider,
|
||||||
|
getUserById,
|
||||||
getSiteSettings,
|
getSiteSettings,
|
||||||
getSystemSetting,
|
getSystemSetting,
|
||||||
} from "@/lib/db/queries";
|
} from "@/lib/db/queries";
|
||||||
@ -79,13 +80,25 @@ async function NewChatPage() {
|
|||||||
// 尝试获取默认模型
|
// 尝试获取默认模型
|
||||||
const defaultModel = await getDefaultAiModel();
|
const defaultModel = await getDefaultAiModel();
|
||||||
|
|
||||||
if (defaultModel && !isAiModelAgentAuto(defaultModel) && !defaultModel.isHidden) {
|
const currentUser = session?.user?.id
|
||||||
|
? await getUserById({ id: session.user.id }).catch(() => undefined)
|
||||||
|
: undefined;
|
||||||
|
const allowHiddenModels =
|
||||||
|
session?.user?.type === "admin" || Boolean(currentUser?.isHighVip);
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultModel &&
|
||||||
|
!isAiModelAgentAuto(defaultModel) &&
|
||||||
|
(allowHiddenModels || !defaultModel.isHidden)
|
||||||
|
) {
|
||||||
initialModelId = defaultModel.id;
|
initialModelId = defaultModel.id;
|
||||||
} else {
|
} else {
|
||||||
// 如果没有设置默认模型,获取第一个启用的模型
|
// 如果没有设置默认模型,获取第一个启用的模型
|
||||||
const enabledModels = await getEnabledAiModelsWithProvider(false, false);
|
const enabledModels = await getEnabledAiModelsWithProvider(false, true);
|
||||||
const visibleModels = enabledModels.filter(
|
const visibleModels = enabledModels.filter(
|
||||||
({ model }) => !isAiModelAgentAuto(model) && !model.isHidden
|
({ model }) =>
|
||||||
|
!isAiModelAgentAuto(model) &&
|
||||||
|
(allowHiddenModels || !model.isHidden)
|
||||||
);
|
);
|
||||||
if (visibleModels.length > 0) {
|
if (visibleModels.length > 0) {
|
||||||
initialModelId = visibleModels[0].model.id;
|
initialModelId = visibleModels[0].model.id;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { ChatSDKError } from "@/lib/errors";
|
|||||||
const updateUserSchema = z.object({
|
const updateUserSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
role: z.enum(["guest", "regular", "admin"]).optional(),
|
role: z.enum(["guest", "regular", "admin"]).optional(),
|
||||||
|
isHighVip: z.boolean().optional(),
|
||||||
isActive: z.boolean().optional(),
|
isActive: z.boolean().optional(),
|
||||||
email: z.string().email().optional(),
|
email: z.string().email().optional(),
|
||||||
monthlyImageLimit: z.number().int().min(0).nullable().optional(),
|
monthlyImageLimit: z.number().int().min(0).nullable().optional(),
|
||||||
@ -53,7 +54,7 @@ export async function PATCH(request: Request) {
|
|||||||
await requireAdmin();
|
await requireAdmin();
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { id, role, isActive, email, monthlyImageLimit, pointsBalance, giftPoints, deductPoints, pointsNote } =
|
const { id, role, isHighVip, isActive, email, monthlyImageLimit, pointsBalance, giftPoints, deductPoints, pointsNote } =
|
||||||
updateUserSchema.parse(body);
|
updateUserSchema.parse(body);
|
||||||
|
|
||||||
if (giftPoints !== undefined && deductPoints !== undefined) {
|
if (giftPoints !== undefined && deductPoints !== undefined) {
|
||||||
@ -77,6 +78,7 @@ export async function PATCH(request: Request) {
|
|||||||
const result = await updateUser({
|
const result = await updateUser({
|
||||||
id,
|
id,
|
||||||
role,
|
role,
|
||||||
|
isHighVip,
|
||||||
isActive,
|
isActive,
|
||||||
email,
|
email,
|
||||||
monthlyImageLimit,
|
monthlyImageLimit,
|
||||||
|
|||||||
2
lib/db/migrations/0041_add_user_high_vip.sql
Normal file
2
lib/db/migrations/0041_add_user_high_vip.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "User"
|
||||||
|
ADD COLUMN IF NOT EXISTS "isHighVip" boolean NOT NULL DEFAULT false;
|
||||||
@ -190,6 +190,13 @@
|
|||||||
"when": 1771718400000,
|
"when": 1771718400000,
|
||||||
"tag": "0040_add_canvas_detail_default_model",
|
"tag": "0040_add_canvas_detail_default_model",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 27,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1772265600000,
|
||||||
|
"tag": "0041_add_user_high_vip",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -766,6 +766,7 @@ export async function getAllUsers({
|
|||||||
department: user.department,
|
department: user.department,
|
||||||
dingtalkUserId: user.dingtalkUserId,
|
dingtalkUserId: user.dingtalkUserId,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
|
isHighVip: user.isHighVip,
|
||||||
isActive: user.isActive,
|
isActive: user.isActive,
|
||||||
monthlyImageLimit: user.monthlyImageLimit,
|
monthlyImageLimit: user.monthlyImageLimit,
|
||||||
pointsBalance: user.pointsBalance,
|
pointsBalance: user.pointsBalance,
|
||||||
@ -807,6 +808,7 @@ export async function updateUser({
|
|||||||
id,
|
id,
|
||||||
email,
|
email,
|
||||||
role,
|
role,
|
||||||
|
isHighVip,
|
||||||
isActive,
|
isActive,
|
||||||
monthlyImageLimit,
|
monthlyImageLimit,
|
||||||
pointsBalance,
|
pointsBalance,
|
||||||
@ -818,6 +820,7 @@ export async function updateUser({
|
|||||||
id: string;
|
id: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
role?: "guest" | "regular" | "admin";
|
role?: "guest" | "regular" | "admin";
|
||||||
|
isHighVip?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
monthlyImageLimit?: number | null;
|
monthlyImageLimit?: number | null;
|
||||||
pointsBalance?: number | null;
|
pointsBalance?: number | null;
|
||||||
@ -838,6 +841,7 @@ export async function updateUser({
|
|||||||
const updateData: Record<string, unknown> = { updatedAt: new Date() };
|
const updateData: Record<string, unknown> = { updatedAt: new Date() };
|
||||||
if (email !== undefined) updateData.email = email;
|
if (email !== undefined) updateData.email = email;
|
||||||
if (role !== undefined) updateData.role = role;
|
if (role !== undefined) updateData.role = role;
|
||||||
|
if (isHighVip !== undefined) updateData.isHighVip = isHighVip;
|
||||||
if (isActive !== undefined) updateData.isActive = isActive;
|
if (isActive !== undefined) updateData.isActive = isActive;
|
||||||
if (monthlyImageLimit !== undefined) {
|
if (monthlyImageLimit !== undefined) {
|
||||||
// 0 或空转为 null(表示无限制)
|
// 0 或空转为 null(表示无限制)
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export const user = pgTable("User", {
|
|||||||
role: varchar("role", { enum: ["guest", "regular", "admin"] })
|
role: varchar("role", { enum: ["guest", "regular", "admin"] })
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("regular"),
|
.default("regular"),
|
||||||
|
isHighVip: boolean("isHighVip").notNull().default(false),
|
||||||
isActive: boolean("isActive").notNull().default(true),
|
isActive: boolean("isActive").notNull().default(true),
|
||||||
monthlyImageLimit: integer("monthlyImageLimit"), // 每月出图限额,NULL或0表示无限制
|
monthlyImageLimit: integer("monthlyImageLimit"), // 每月出图限额,NULL或0表示无限制
|
||||||
pointsBalance: integer("pointsBalance").notNull().default(0), // 当前剩余积分
|
pointsBalance: integer("pointsBalance").notNull().default(0), // 当前剩余积分
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user