feat(models): add frontend hide toggle and compact image param chips
This commit is contained in:
parent
a1691fbd39
commit
8fc880fe9e
@ -151,6 +151,7 @@ type AiModel = {
|
||||
supportsVision: boolean;
|
||||
supportsImageGen: boolean;
|
||||
isEnabled: boolean;
|
||||
isHidden: boolean;
|
||||
isDefault: boolean;
|
||||
sortOrder: number;
|
||||
isAgentModel?: boolean;
|
||||
@ -998,6 +999,7 @@ export function AdminSettingsClient() {
|
||||
supportsVision: false,
|
||||
supportsImageGen: false,
|
||||
isEnabled: true,
|
||||
isHidden: false,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
isMultimodal: false,
|
||||
@ -1856,6 +1858,7 @@ export function AdminSettingsClient() {
|
||||
supportsVision: false,
|
||||
supportsImageGen: false,
|
||||
isEnabled: true,
|
||||
isHidden: false,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
isMultimodal: false,
|
||||
@ -5820,6 +5823,13 @@ export function AdminSettingsClient() {
|
||||
/>
|
||||
<Label>启用</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={modelForm.isHidden}
|
||||
onCheckedChange={(checked) => setModelForm({ ...modelForm, isHidden: checked })}
|
||||
/>
|
||||
<Label>前台隐藏(仅 Agent/故障迁移可用)</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={modelForm.isDefault}
|
||||
@ -6007,6 +6017,7 @@ export function AdminSettingsClient() {
|
||||
{model.isDefault && <span className="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded">默认</span>}
|
||||
{model.isAgentModel && <span className="text-xs bg-violet-100 text-violet-800 px-2 py-0.5 rounded">Agent</span>}
|
||||
{model.isMultimodal && <span className="text-xs bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded">多模态</span>}
|
||||
{model.isHidden && <span className="text-xs bg-amber-100 text-amber-800 px-2 py-0.5 rounded">前台隐藏</span>}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground truncate">
|
||||
ID: {model.modelId} | 提供商: {provider?.name ?? "未知"}
|
||||
@ -6025,6 +6036,7 @@ export function AdminSettingsClient() {
|
||||
supportsVision: model.supportsVision,
|
||||
supportsImageGen: model.supportsImageGen,
|
||||
isEnabled: model.isEnabled,
|
||||
isHidden: model.isHidden || false,
|
||||
isDefault: model.isDefault,
|
||||
isAgentModel: model.isAgentModel || false,
|
||||
isMultimodal: model.isMultimodal || false,
|
||||
|
||||
@ -30,12 +30,12 @@ export async function GET() {
|
||||
}
|
||||
|
||||
// 只获取主模型(不包括子模型),子模型由后端根据 videoParams.mode 自动选择
|
||||
const modelsWithProvider = await getEnabledAiModelsWithProvider(false);
|
||||
const modelsWithProvider = await getEnabledAiModelsWithProvider(false, false);
|
||||
const agentModelWithProvider = await getAgentAiModelWithProvider();
|
||||
const defaultModel = await getDefaultAiModel();
|
||||
const visibleModelsWithProvider = sortModelsForDisplay(
|
||||
modelsWithProvider
|
||||
.filter(({ model }) => !isAiModelAgentAuto(model))
|
||||
.filter(({ model }) => !isAiModelAgentAuto(model) && !model.isHidden)
|
||||
.map((item) => item.model)
|
||||
);
|
||||
const visibleModelIdSet = new Set(visibleModelsWithProvider.map((m) => m.id));
|
||||
|
||||
@ -79,13 +79,13 @@ async function NewChatPage() {
|
||||
// 尝试获取默认模型
|
||||
const defaultModel = await getDefaultAiModel();
|
||||
|
||||
if (defaultModel && !isAiModelAgentAuto(defaultModel)) {
|
||||
if (defaultModel && !isAiModelAgentAuto(defaultModel) && !defaultModel.isHidden) {
|
||||
initialModelId = defaultModel.id;
|
||||
} else {
|
||||
// 如果没有设置默认模型,获取第一个启用的模型
|
||||
const enabledModels = await getEnabledAiModelsWithProvider();
|
||||
const enabledModels = await getEnabledAiModelsWithProvider(false, false);
|
||||
const visibleModels = enabledModels.filter(
|
||||
({ model }) => !isAiModelAgentAuto(model)
|
||||
({ model }) => !isAiModelAgentAuto(model) && !model.isHidden
|
||||
);
|
||||
if (visibleModels.length > 0) {
|
||||
initialModelId = visibleModels[0].model.id;
|
||||
|
||||
@ -34,6 +34,7 @@ const createModelSchema = z.object({
|
||||
supportsVision: z.boolean().optional(),
|
||||
supportsImageGen: z.boolean().optional(),
|
||||
isEnabled: z.boolean().optional(),
|
||||
isHidden: z.boolean().optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
sortOrder: z.number().optional(),
|
||||
fallbackModelId1: z.string().max(128).optional(),
|
||||
@ -53,6 +54,7 @@ const updateModelSchema = z.object({
|
||||
supportsVision: z.boolean().optional(),
|
||||
supportsImageGen: z.boolean().optional(),
|
||||
isEnabled: z.boolean().optional(),
|
||||
isHidden: z.boolean().optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
sortOrder: z.number().optional(),
|
||||
fallbackModelId1: z.string().max(128).optional(),
|
||||
|
||||
@ -808,7 +808,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
const [videoSlotUploading, setVideoSlotUploading] = useState<number | null>(null);
|
||||
const [lastImageModelId, setLastImageModelId] = useState<string | null>(null);
|
||||
const [lastVideoModelId, setLastVideoModelId] = useState<string | null>(null);
|
||||
const [deepThinkingEnabled, setDeepThinkingEnabled] = useState(true);
|
||||
const [deepThinkingEnabled, setDeepThinkingEnabled] = useState(false);
|
||||
const imageParamsRef = useRef(imageParams);
|
||||
const videoParamsRef = useRef(videoParams);
|
||||
const deepThinkingEnabledRef = useRef(deepThinkingEnabled);
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Check, Image, Ratio, Monitor, Images, SlidersHorizontal, Ban } from "lucide-react";
|
||||
import { Check, SlidersHorizontal } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
@ -183,10 +183,10 @@ export function ImageParamsBar({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-2 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||
className="h-7 min-w-[46px] rounded-md border border-[#d5dae2] bg-white px-2 text-[11px] font-medium text-[#374151] hover:bg-[#f7f8fa] focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||
title={`比例:${params.aspectRatio}`}
|
||||
>
|
||||
<Ratio size={14} />
|
||||
{params.aspectRatio === "auto" ? "自动" : params.aspectRatio}
|
||||
<span className="sr-only">{`比例:${params.aspectRatio}`}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@ -212,10 +212,10 @@ export function ImageParamsBar({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-2 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||
className="h-7 min-w-[42px] rounded-md border border-[#d5dae2] bg-white px-2 text-[11px] font-medium text-[#374151] hover:bg-[#f7f8fa] focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||
title={`分辨率:${params.imageSize}`}
|
||||
>
|
||||
<Monitor size={14} />
|
||||
{params.imageSize}
|
||||
<span className="sr-only">{`分辨率:${params.imageSize}`}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@ -238,10 +238,10 @@ export function ImageParamsBar({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-2 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||
className="h-7 min-w-[42px] rounded-md border border-[#d5dae2] bg-white px-2 text-[11px] font-medium text-[#374151] hover:bg-[#f7f8fa] focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||
title={`数量:${params.imageCount || 1}张`}
|
||||
>
|
||||
<Images size={14} />
|
||||
{params.imageCount || 1}张
|
||||
<span className="sr-only">{`数量:${params.imageCount || 1}张`}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
2
lib/db/migrations/0039_add_ai_model_hidden.sql
Normal file
2
lib/db/migrations/0039_add_ai_model_hidden.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE "AiModel"
|
||||
ADD COLUMN IF NOT EXISTS "isHidden" boolean NOT NULL DEFAULT false;
|
||||
@ -176,6 +176,13 @@
|
||||
"when": 1771502400000,
|
||||
"tag": "0038_add_canvas_detail_archives",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 25,
|
||||
"version": "7",
|
||||
"when": 1771588800000,
|
||||
"tag": "0039_add_ai_model_hidden",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1352,6 +1352,7 @@ export async function createAiModel({
|
||||
supportsVision,
|
||||
supportsImageGen,
|
||||
isEnabled,
|
||||
isHidden,
|
||||
isDefault,
|
||||
sortOrder,
|
||||
fallbackModelId1,
|
||||
@ -1368,6 +1369,7 @@ export async function createAiModel({
|
||||
supportsVision?: boolean;
|
||||
supportsImageGen?: boolean;
|
||||
isEnabled?: boolean;
|
||||
isHidden?: boolean;
|
||||
isDefault?: boolean;
|
||||
sortOrder?: number;
|
||||
fallbackModelId1?: string;
|
||||
@ -1396,6 +1398,7 @@ export async function createAiModel({
|
||||
supportsVision: supportsVision ?? false,
|
||||
supportsImageGen: supportsImageGen ?? false,
|
||||
isEnabled: isEnabled ?? true,
|
||||
isHidden: isHidden ?? false,
|
||||
isDefault: isDefault ?? false,
|
||||
sortOrder: sortOrder ?? 0,
|
||||
fallbackModelId1: fallbackModelId1 || null,
|
||||
@ -1429,6 +1432,7 @@ export async function updateAiModel({
|
||||
supportsVision,
|
||||
supportsImageGen,
|
||||
isEnabled,
|
||||
isHidden,
|
||||
isDefault,
|
||||
sortOrder,
|
||||
fallbackModelId1,
|
||||
@ -1446,6 +1450,7 @@ export async function updateAiModel({
|
||||
supportsVision?: boolean;
|
||||
supportsImageGen?: boolean;
|
||||
isEnabled?: boolean;
|
||||
isHidden?: boolean;
|
||||
isDefault?: boolean;
|
||||
sortOrder?: number;
|
||||
fallbackModelId1?: string;
|
||||
@ -1473,6 +1478,7 @@ export async function updateAiModel({
|
||||
if (supportsImageGen !== undefined)
|
||||
updateData.supportsImageGen = supportsImageGen;
|
||||
if (isEnabled !== undefined) updateData.isEnabled = isEnabled;
|
||||
if (isHidden !== undefined) updateData.isHidden = isHidden;
|
||||
if (isDefault !== undefined) updateData.isDefault = isDefault;
|
||||
if (sortOrder !== undefined) updateData.sortOrder = sortOrder;
|
||||
// 备用模型 ID(空字符串转为 null)
|
||||
@ -1726,7 +1732,10 @@ export async function deleteStorageConfig({ id }: { id: string }) {
|
||||
}
|
||||
|
||||
// 获取启用的 AI 模型及其提供商信息(用于前端展示)
|
||||
export async function getEnabledAiModelsWithProvider(includeSubModels: boolean = false) {
|
||||
export async function getEnabledAiModelsWithProvider(
|
||||
includeSubModels: boolean = false,
|
||||
includeHidden: boolean = true
|
||||
) {
|
||||
try {
|
||||
const conditions = [
|
||||
eq(aiModel.isEnabled, true),
|
||||
@ -1737,6 +1746,9 @@ export async function getEnabledAiModelsWithProvider(includeSubModels: boolean =
|
||||
if (!includeSubModels) {
|
||||
conditions.push(eq(aiModel.isSubModel, false));
|
||||
}
|
||||
if (!includeHidden) {
|
||||
conditions.push(eq(aiModel.isHidden, false));
|
||||
}
|
||||
|
||||
const models = await db
|
||||
.select({
|
||||
|
||||
@ -284,6 +284,7 @@ export const aiModel = pgTable("AiModel", {
|
||||
supportsVision: boolean("supportsVision").notNull().default(false), // 是否支持图片输入
|
||||
supportsImageGen: boolean("supportsImageGen").notNull().default(false), // 是否支持图片生成
|
||||
isEnabled: boolean("isEnabled").notNull().default(true), // 是否启用
|
||||
isHidden: boolean("isHidden").notNull().default(false), // 是否在前端模型选择中隐藏
|
||||
isDefault: boolean("isDefault").notNull().default(false), // 是否为默认模型
|
||||
sortOrder: integer("sortOrder").notNull().default(0), // 排序顺序
|
||||
capabilities: jsonb("capabilities"), // 🆕 模型能力配置(用于视频模型)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user