feat: add high vip model visibility control
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-22 01:43:06 +08:00
parent 2e837b3769
commit fbb2e186b9
8 changed files with 55 additions and 6 deletions

View File

@ -98,6 +98,7 @@ type User = {
name: string | null;
department: string | null;
role: "guest" | "regular" | "admin";
isHighVip: boolean;
isActive: boolean;
monthlyImageLimit: number | null;
pointsBalance: number;
@ -2286,6 +2287,9 @@ export function AdminSettingsClient() {
<div className="text-sm text-muted-foreground">
角色: {ROLE_LABELS[user.role] ?? user.role} | 状态: {user.isActive ? "已激活" : "未激活"}
</div>
<div className="text-sm text-muted-foreground">
标签: {user.isHighVip ? "高级VIP" : "普通"}
</div>
<div className="text-sm text-muted-foreground">
月度出图限额: {user.monthlyImageLimit ? `${user.monthlyImageLimit} 张/月` : "无限制"}
</div>
@ -2342,6 +2346,13 @@ export function AdminSettingsClient() {
>
{user.isActive ? "禁用" : "激活"}
</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">
<Input
type="number"

View File

@ -3,6 +3,7 @@ import {
getAgentAiModelWithProvider,
getDefaultAiModel,
getEnabledAiModelsWithProvider,
getUserById,
} from "@/lib/db/queries";
import { ChatSDKError } from "@/lib/errors";
import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags";
@ -29,13 +30,21 @@ export async function GET() {
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 自动选择
const modelsWithProvider = await getEnabledAiModelsWithProvider(false, false);
const modelsWithProvider = await getEnabledAiModelsWithProvider(false, true);
const agentModelWithProvider = await getAgentAiModelWithProvider();
const defaultModel = await getDefaultAiModel();
const visibleModelsWithProvider = sortModelsForDisplay(
modelsWithProvider
.filter(({ model }) => !isAiModelAgentAuto(model) && !model.isHidden)
.filter(
({ model }) =>
!isAiModelAgentAuto(model) &&
(allowHiddenModels || !model.isHidden)
)
.map((item) => item.model)
);
const visibleModelIdSet = new Set(visibleModelsWithProvider.map((m) => m.id));

View File

@ -9,6 +9,7 @@ import { auth } from "../(auth)/auth";
import {
getDefaultAiModel,
getEnabledAiModelsWithProvider,
getUserById,
getSiteSettings,
getSystemSetting,
} from "@/lib/db/queries";
@ -79,13 +80,25 @@ async function NewChatPage() {
// 尝试获取默认模型
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;
} else {
// 如果没有设置默认模型,获取第一个启用的模型
const enabledModels = await getEnabledAiModelsWithProvider(false, false);
const enabledModels = await getEnabledAiModelsWithProvider(false, true);
const visibleModels = enabledModels.filter(
({ model }) => !isAiModelAgentAuto(model) && !model.isHidden
({ model }) =>
!isAiModelAgentAuto(model) &&
(allowHiddenModels || !model.isHidden)
);
if (visibleModels.length > 0) {
initialModelId = visibleModels[0].model.id;

View File

@ -11,6 +11,7 @@ import { ChatSDKError } from "@/lib/errors";
const updateUserSchema = z.object({
id: z.string().uuid(),
role: z.enum(["guest", "regular", "admin"]).optional(),
isHighVip: z.boolean().optional(),
isActive: z.boolean().optional(),
email: z.string().email().optional(),
monthlyImageLimit: z.number().int().min(0).nullable().optional(),
@ -53,7 +54,7 @@ export async function PATCH(request: Request) {
await requireAdmin();
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);
if (giftPoints !== undefined && deductPoints !== undefined) {
@ -77,6 +78,7 @@ export async function PATCH(request: Request) {
const result = await updateUser({
id,
role,
isHighVip,
isActive,
email,
monthlyImageLimit,

View File

@ -0,0 +1,2 @@
ALTER TABLE "User"
ADD COLUMN IF NOT EXISTS "isHighVip" boolean NOT NULL DEFAULT false;

View File

@ -190,6 +190,13 @@
"when": 1771718400000,
"tag": "0040_add_canvas_detail_default_model",
"breakpoints": true
},
{
"idx": 27,
"version": "7",
"when": 1772265600000,
"tag": "0041_add_user_high_vip",
"breakpoints": true
}
]
}

View File

@ -766,6 +766,7 @@ export async function getAllUsers({
department: user.department,
dingtalkUserId: user.dingtalkUserId,
role: user.role,
isHighVip: user.isHighVip,
isActive: user.isActive,
monthlyImageLimit: user.monthlyImageLimit,
pointsBalance: user.pointsBalance,
@ -807,6 +808,7 @@ export async function updateUser({
id,
email,
role,
isHighVip,
isActive,
monthlyImageLimit,
pointsBalance,
@ -818,6 +820,7 @@ export async function updateUser({
id: string;
email?: string;
role?: "guest" | "regular" | "admin";
isHighVip?: boolean;
isActive?: boolean;
monthlyImageLimit?: number | null;
pointsBalance?: number | null;
@ -838,6 +841,7 @@ export async function updateUser({
const updateData: Record<string, unknown> = { updatedAt: new Date() };
if (email !== undefined) updateData.email = email;
if (role !== undefined) updateData.role = role;
if (isHighVip !== undefined) updateData.isHighVip = isHighVip;
if (isActive !== undefined) updateData.isActive = isActive;
if (monthlyImageLimit !== undefined) {
// 0 或空转为 null(表示无限制)

View File

@ -24,6 +24,7 @@ export const user = pgTable("User", {
role: varchar("role", { enum: ["guest", "regular", "admin"] })
.notNull()
.default("regular"),
isHighVip: boolean("isHighVip").notNull().default(false),
isActive: boolean("isActive").notNull().default(true),
monthlyImageLimit: integer("monthlyImageLimit"), // 每月出图限额,NULL或0表示无限制
pointsBalance: integer("pointsBalance").notNull().default(0), // 当前剩余积分