mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
feat: add model theme policy and chat model selector enhancements
This commit is contained in:
@@ -15,12 +15,26 @@ import { isAliasProvider } from "@/lib/constants/providerMappings";
|
|||||||
import { providerPoolApi } from "@/lib/api/providerPool";
|
import { providerPoolApi } from "@/lib/api/providerPool";
|
||||||
import { apiKeyProviderApi } from "@/lib/api/apiKeyProvider";
|
import { apiKeyProviderApi } from "@/lib/api/apiKeyProvider";
|
||||||
import { emitProviderDataChanged } from "@/lib/providerDataEvents";
|
import { emitProviderDataChanged } from "@/lib/providerDataEvents";
|
||||||
|
import { filterModelsByTheme } from "../utils/modelThemePolicy";
|
||||||
|
|
||||||
|
const THEME_LABEL_MAP: Record<string, string> = {
|
||||||
|
general: "通用对话",
|
||||||
|
"social-media": "社媒内容",
|
||||||
|
poster: "图文海报",
|
||||||
|
knowledge: "知识探索",
|
||||||
|
planning: "计划规划",
|
||||||
|
document: "办公文档",
|
||||||
|
video: "短视频",
|
||||||
|
music: "歌词曲谱",
|
||||||
|
novel: "小说创作",
|
||||||
|
};
|
||||||
|
|
||||||
interface ChatModelSelectorProps {
|
interface ChatModelSelectorProps {
|
||||||
providerType: string;
|
providerType: string;
|
||||||
setProviderType: (type: string) => void;
|
setProviderType: (type: string) => void;
|
||||||
model: string;
|
model: string;
|
||||||
setModel: (model: string) => void;
|
setModel: (model: string) => void;
|
||||||
|
activeTheme?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
compactTrigger?: boolean;
|
compactTrigger?: boolean;
|
||||||
onManageProviders?: () => void;
|
onManageProviders?: () => void;
|
||||||
@@ -32,6 +46,7 @@ export const ChatModelSelector: React.FC<ChatModelSelectorProps> = ({
|
|||||||
setProviderType,
|
setProviderType,
|
||||||
model,
|
model,
|
||||||
setModel,
|
setModel,
|
||||||
|
activeTheme,
|
||||||
className,
|
className,
|
||||||
compactTrigger = false,
|
compactTrigger = false,
|
||||||
onManageProviders,
|
onManageProviders,
|
||||||
@@ -50,8 +65,18 @@ export const ChatModelSelector: React.FC<ChatModelSelectorProps> = ({
|
|||||||
);
|
);
|
||||||
}, [configuredProviders, providerType]);
|
}, [configuredProviders, providerType]);
|
||||||
|
|
||||||
const { modelIds: currentModels, loading: modelsLoading } =
|
const { models: providerModels, loading: modelsLoading } = useProviderModels(
|
||||||
useProviderModels(selectedProvider);
|
selectedProvider,
|
||||||
|
{ returnFullMetadata: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredResult = useMemo(() => {
|
||||||
|
return filterModelsByTheme(activeTheme, providerModels);
|
||||||
|
}, [activeTheme, providerModels]);
|
||||||
|
|
||||||
|
const currentModels = useMemo(() => {
|
||||||
|
return filteredResult.models.map((item) => item.id);
|
||||||
|
}, [filteredResult.models]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasInitialized.current) return;
|
if (hasInitialized.current) return;
|
||||||
@@ -82,6 +107,29 @@ export const ChatModelSelector: React.FC<ChatModelSelectorProps> = ({
|
|||||||
}
|
}
|
||||||
}, [currentModels, modelsLoading, selectedProvider, setModel]);
|
}, [currentModels, modelsLoading, selectedProvider, setModel]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!import.meta.env.DEV) return;
|
||||||
|
if (!selectedProvider) return;
|
||||||
|
if (!activeTheme) return;
|
||||||
|
if (!filteredResult.usedFallback && filteredResult.filteredOutCount === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug("[ChatModelSelector] 主题模型过滤结果", {
|
||||||
|
theme: activeTheme,
|
||||||
|
provider: selectedProvider.key,
|
||||||
|
policyName: filteredResult.policyName,
|
||||||
|
filteredOutCount: filteredResult.filteredOutCount,
|
||||||
|
usedFallback: filteredResult.usedFallback,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
activeTheme,
|
||||||
|
filteredResult.policyName,
|
||||||
|
filteredResult.filteredOutCount,
|
||||||
|
filteredResult.usedFallback,
|
||||||
|
selectedProvider,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
|
|
||||||
@@ -114,6 +162,14 @@ export const ChatModelSelector: React.FC<ChatModelSelectorProps> = ({
|
|||||||
selectedProvider?.key || providerType || "proxycast-hub";
|
selectedProvider?.key || providerType || "proxycast-hub";
|
||||||
const compactProviderLabel =
|
const compactProviderLabel =
|
||||||
selectedProvider?.label || providerType || "ProxyCast Hub";
|
selectedProvider?.label || providerType || "ProxyCast Hub";
|
||||||
|
const normalizedTheme = (activeTheme || "").toLowerCase();
|
||||||
|
const activeThemeLabel =
|
||||||
|
THEME_LABEL_MAP[normalizedTheme] || activeTheme || "当前主题";
|
||||||
|
const showThemeFilterHint =
|
||||||
|
normalizedTheme !== "" &&
|
||||||
|
normalizedTheme !== "general" &&
|
||||||
|
!filteredResult.usedFallback &&
|
||||||
|
filteredResult.filteredOutCount > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex items-center", className)}>
|
<div className={cn("flex items-center", className)}>
|
||||||
@@ -211,6 +267,16 @@ export const ChatModelSelector: React.FC<ChatModelSelectorProps> = ({
|
|||||||
<div className="text-xs font-semibold text-muted-foreground px-2 py-1.5 mb-1">
|
<div className="text-xs font-semibold text-muted-foreground px-2 py-1.5 mb-1">
|
||||||
Models
|
Models
|
||||||
</div>
|
</div>
|
||||||
|
{showThemeFilterHint && (
|
||||||
|
<div className="text-[11px] text-muted-foreground px-2 pb-1">
|
||||||
|
已按 {activeThemeLabel} 主题筛选模型
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{normalizedTheme !== "general" && filteredResult.usedFallback && (
|
||||||
|
<div className="text-[11px] text-amber-600 px-2 pb-1">
|
||||||
|
{activeThemeLabel} 未匹配到主题模型,已展示全部模型
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
<div className="space-y-1 p-1">
|
<div className="space-y-1 p-1">
|
||||||
|
|||||||
@@ -918,6 +918,7 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
|
|||||||
setProviderType={setProviderType}
|
setProviderType={setProviderType}
|
||||||
model={model}
|
model={model}
|
||||||
setModel={setModel}
|
setModel={setModel}
|
||||||
|
activeTheme={activeTheme}
|
||||||
compactTrigger
|
compactTrigger
|
||||||
popoverSide="top"
|
popoverSide="top"
|
||||||
onManageProviders={onManageProviders}
|
onManageProviders={onManageProviders}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ interface InputbarProps {
|
|||||||
setProviderType?: (type: string) => void;
|
setProviderType?: (type: string) => void;
|
||||||
model?: string;
|
model?: string;
|
||||||
setModel?: (model: string) => void;
|
setModel?: (model: string) => void;
|
||||||
|
activeTheme?: string;
|
||||||
onManageProviders?: () => void;
|
onManageProviders?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +129,7 @@ export const Inputbar: React.FC<InputbarProps> = ({
|
|||||||
setProviderType,
|
setProviderType,
|
||||||
model,
|
model,
|
||||||
setModel,
|
setModel,
|
||||||
|
activeTheme,
|
||||||
onManageProviders,
|
onManageProviders,
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTools, setActiveTools] = useState<Record<string, boolean>>({});
|
const [activeTools, setActiveTools] = useState<Record<string, boolean>>({});
|
||||||
@@ -381,6 +383,7 @@ export const Inputbar: React.FC<InputbarProps> = ({
|
|||||||
setProviderType={setProviderType}
|
setProviderType={setProviderType}
|
||||||
model={model}
|
model={model}
|
||||||
setModel={setModel}
|
setModel={setModel}
|
||||||
|
activeTheme={activeTheme}
|
||||||
compactTrigger
|
compactTrigger
|
||||||
popoverSide="top"
|
popoverSide="top"
|
||||||
onManageProviders={onManageProviders}
|
onManageProviders={onManageProviders}
|
||||||
|
|||||||
@@ -1707,6 +1707,7 @@ export function AgentChatPage({
|
|||||||
setProviderType={setProviderType}
|
setProviderType={setProviderType}
|
||||||
model={model}
|
model={model}
|
||||||
setModel={setModel}
|
setModel={setModel}
|
||||||
|
activeTheme={activeTheme}
|
||||||
onManageProviders={handleManageProviders}
|
onManageProviders={handleManageProviders}
|
||||||
disabled={!projectId}
|
disabled={!projectId}
|
||||||
onClearMessages={handleClearMessages}
|
onClearMessages={handleClearMessages}
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { EnhancedModelMetadata } from "@/lib/types/modelRegistry";
|
||||||
|
import { filterModelsByTheme } from "./modelThemePolicy";
|
||||||
|
|
||||||
|
function createModel(
|
||||||
|
id: string,
|
||||||
|
overrides: Partial<EnhancedModelMetadata> = {},
|
||||||
|
): EnhancedModelMetadata {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
display_name: id,
|
||||||
|
provider_id: "test-provider",
|
||||||
|
provider_name: "Test Provider",
|
||||||
|
family: null,
|
||||||
|
tier: "pro",
|
||||||
|
capabilities: {
|
||||||
|
vision: false,
|
||||||
|
tools: true,
|
||||||
|
streaming: true,
|
||||||
|
json_mode: true,
|
||||||
|
function_calling: true,
|
||||||
|
reasoning: false,
|
||||||
|
},
|
||||||
|
pricing: null,
|
||||||
|
limits: {
|
||||||
|
context_length: null,
|
||||||
|
max_output_tokens: null,
|
||||||
|
requests_per_minute: null,
|
||||||
|
tokens_per_minute: null,
|
||||||
|
},
|
||||||
|
status: "active",
|
||||||
|
release_date: null,
|
||||||
|
is_latest: false,
|
||||||
|
description: null,
|
||||||
|
source: "local",
|
||||||
|
created_at: 0,
|
||||||
|
updated_at: 0,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("modelThemePolicy", () => {
|
||||||
|
it("poster 主题应过滤掉非图像模型", () => {
|
||||||
|
const models = [
|
||||||
|
createModel("gemini-3-pro-image-preview"),
|
||||||
|
createModel("gemini-3-pro-preview"),
|
||||||
|
createModel("gemini-2.5-computer-use-preview-10-2025"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = filterModelsByTheme("poster", models);
|
||||||
|
|
||||||
|
expect(result.usedFallback).toBe(false);
|
||||||
|
expect(result.models.map((model) => model.id)).toEqual([
|
||||||
|
"gemini-3-pro-image-preview",
|
||||||
|
]);
|
||||||
|
expect(result.filteredOutCount).toBe(2);
|
||||||
|
expect(result.policyName).toBe("image-only");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("poster 主题在无匹配模型时应回退到原列表", () => {
|
||||||
|
const models = [
|
||||||
|
createModel("gemini-3-pro-preview"),
|
||||||
|
createModel("gpt-4o"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = filterModelsByTheme("poster", models);
|
||||||
|
|
||||||
|
expect(result.usedFallback).toBe(true);
|
||||||
|
expect(result.models).toEqual(models);
|
||||||
|
expect(result.policyName).toBe("fallback-all");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("poster 主题应支持能力推断识别图像模型", () => {
|
||||||
|
const models = [
|
||||||
|
createModel("vendor-creative-v1", {
|
||||||
|
capabilities: {
|
||||||
|
vision: true,
|
||||||
|
tools: false,
|
||||||
|
streaming: true,
|
||||||
|
json_mode: false,
|
||||||
|
function_calling: false,
|
||||||
|
reasoning: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createModel("generic-chat-v1"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = filterModelsByTheme("poster", models);
|
||||||
|
|
||||||
|
expect(result.usedFallback).toBe(false);
|
||||||
|
expect(result.models.map((model) => model.id)).toEqual([
|
||||||
|
"vendor-creative-v1",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("knowledge 主题应优先保留推理聊天模型", () => {
|
||||||
|
const models = [
|
||||||
|
createModel("gemini-3-pro-image-preview"),
|
||||||
|
createModel("deepseek-reasoner", {
|
||||||
|
capabilities: {
|
||||||
|
vision: false,
|
||||||
|
tools: true,
|
||||||
|
streaming: true,
|
||||||
|
json_mode: true,
|
||||||
|
function_calling: true,
|
||||||
|
reasoning: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createModel("deepseek-chat"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = filterModelsByTheme("knowledge", models);
|
||||||
|
|
||||||
|
expect(result.usedFallback).toBe(false);
|
||||||
|
expect(result.models.map((model) => model.id)).toEqual([
|
||||||
|
"deepseek-reasoner",
|
||||||
|
]);
|
||||||
|
expect(result.policyName).toBe("reasoning-priority");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("knowledge 主题在无推理模型时应回退到聊天模型", () => {
|
||||||
|
const models = [
|
||||||
|
createModel("gemini-3-pro-image-preview"),
|
||||||
|
createModel("deepseek-chat"),
|
||||||
|
createModel("text-embedding-3-large"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = filterModelsByTheme("knowledge", models);
|
||||||
|
|
||||||
|
expect(result.usedFallback).toBe(false);
|
||||||
|
expect(result.models.map((model) => model.id)).toEqual(["deepseek-chat"]);
|
||||||
|
expect(result.policyName).toBe("chat-fallback");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("social-media 主题应过滤掉图像和非聊天模型", () => {
|
||||||
|
const models = [
|
||||||
|
createModel("gemini-3-pro-image-preview"),
|
||||||
|
createModel("text-embedding-3-large"),
|
||||||
|
createModel("gpt-4o"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = filterModelsByTheme("social-media", models);
|
||||||
|
|
||||||
|
expect(result.usedFallback).toBe(false);
|
||||||
|
expect(result.models.map((model) => model.id)).toEqual(["gpt-4o"]);
|
||||||
|
expect(result.policyName).toBe("chat-only");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("general 主题不应改动模型列表", () => {
|
||||||
|
const models = [
|
||||||
|
createModel("gemini-3-pro-image-preview"),
|
||||||
|
createModel("gemini-3-pro-preview"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = filterModelsByTheme("general", models);
|
||||||
|
|
||||||
|
expect(result.usedFallback).toBe(false);
|
||||||
|
expect(result.filteredOutCount).toBe(0);
|
||||||
|
expect(result.models).toEqual(models);
|
||||||
|
expect(result.policyName).toBe("none");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import type { EnhancedModelMetadata } from "@/lib/types/modelRegistry";
|
||||||
|
|
||||||
|
export interface ThemeModelFilterResult {
|
||||||
|
models: EnhancedModelMetadata[];
|
||||||
|
usedFallback: boolean;
|
||||||
|
filteredOutCount: number;
|
||||||
|
policyName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IMAGE_INCLUDE_KEYWORDS = [
|
||||||
|
"image",
|
||||||
|
"imagen",
|
||||||
|
"dall-e",
|
||||||
|
"flux",
|
||||||
|
"stable-diffusion",
|
||||||
|
"sdxl",
|
||||||
|
"sd3",
|
||||||
|
"midjourney",
|
||||||
|
"mj",
|
||||||
|
"picture",
|
||||||
|
"绘图",
|
||||||
|
"图像",
|
||||||
|
"生图",
|
||||||
|
];
|
||||||
|
|
||||||
|
const IMAGE_EXCLUDE_KEYWORDS = [
|
||||||
|
"embedding",
|
||||||
|
"rerank",
|
||||||
|
"tts",
|
||||||
|
"stt",
|
||||||
|
"asr",
|
||||||
|
"transcribe",
|
||||||
|
"audio",
|
||||||
|
"speech",
|
||||||
|
"reasoner",
|
||||||
|
"thinking",
|
||||||
|
"computer-use",
|
||||||
|
];
|
||||||
|
|
||||||
|
const NON_CHAT_KEYWORDS = [
|
||||||
|
"embedding",
|
||||||
|
"rerank",
|
||||||
|
"tts",
|
||||||
|
"stt",
|
||||||
|
"asr",
|
||||||
|
"transcribe",
|
||||||
|
"audio",
|
||||||
|
"speech",
|
||||||
|
"whisper",
|
||||||
|
];
|
||||||
|
|
||||||
|
function containsAnyKeyword(text: string, keywords: string[]): boolean {
|
||||||
|
return keywords.some((keyword) => text.includes(keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildModelSearchText(model: EnhancedModelMetadata): string {
|
||||||
|
return [
|
||||||
|
model.id,
|
||||||
|
model.display_name,
|
||||||
|
model.family || "",
|
||||||
|
model.description || "",
|
||||||
|
]
|
||||||
|
.join(" ")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksLikeImageGenerationModel(model: EnhancedModelMetadata): boolean {
|
||||||
|
const text = buildModelSearchText(model);
|
||||||
|
|
||||||
|
if (
|
||||||
|
containsAnyKeyword(text, IMAGE_EXCLUDE_KEYWORDS) ||
|
||||||
|
containsAnyKeyword(text, NON_CHAT_KEYWORDS)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containsAnyKeyword(text, IMAGE_INCLUDE_KEYWORDS)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
model.capabilities.vision &&
|
||||||
|
!model.capabilities.tools &&
|
||||||
|
!model.capabilities.function_calling &&
|
||||||
|
!model.capabilities.json_mode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksLikeChatModel(model: EnhancedModelMetadata): boolean {
|
||||||
|
const text = buildModelSearchText(model);
|
||||||
|
|
||||||
|
if (containsAnyKeyword(text, NON_CHAT_KEYWORDS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !looksLikeImageGenerationModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHAT_THEME_IDS = new Set([
|
||||||
|
"social-media",
|
||||||
|
"document",
|
||||||
|
"video",
|
||||||
|
"music",
|
||||||
|
"novel",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function filterModelsByTheme(
|
||||||
|
theme: string | undefined,
|
||||||
|
models: EnhancedModelMetadata[],
|
||||||
|
): ThemeModelFilterResult {
|
||||||
|
const normalizedTheme = theme?.toLowerCase() || "";
|
||||||
|
if (models.length === 0) {
|
||||||
|
return {
|
||||||
|
models,
|
||||||
|
usedFallback: false,
|
||||||
|
filteredOutCount: 0,
|
||||||
|
policyName: "none",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedTheme !== "poster") {
|
||||||
|
if (normalizedTheme === "knowledge" || normalizedTheme === "planning") {
|
||||||
|
const reasoningModels = models.filter(
|
||||||
|
(model) => looksLikeChatModel(model) && model.capabilities.reasoning,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (reasoningModels.length > 0) {
|
||||||
|
return {
|
||||||
|
models: reasoningModels,
|
||||||
|
usedFallback: false,
|
||||||
|
filteredOutCount: models.length - reasoningModels.length,
|
||||||
|
policyName: "reasoning-priority",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatModels = models.filter(looksLikeChatModel);
|
||||||
|
if (chatModels.length > 0) {
|
||||||
|
return {
|
||||||
|
models: chatModels,
|
||||||
|
usedFallback: false,
|
||||||
|
filteredOutCount: models.length - chatModels.length,
|
||||||
|
policyName: "chat-fallback",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
models,
|
||||||
|
usedFallback: true,
|
||||||
|
filteredOutCount: 0,
|
||||||
|
policyName: "fallback-all",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CHAT_THEME_IDS.has(normalizedTheme)) {
|
||||||
|
const chatModels = models.filter(looksLikeChatModel);
|
||||||
|
if (chatModels.length > 0) {
|
||||||
|
return {
|
||||||
|
models: chatModels,
|
||||||
|
usedFallback: false,
|
||||||
|
filteredOutCount: models.length - chatModels.length,
|
||||||
|
policyName: "chat-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
models,
|
||||||
|
usedFallback: true,
|
||||||
|
filteredOutCount: 0,
|
||||||
|
policyName: "fallback-all",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
models,
|
||||||
|
usedFallback: false,
|
||||||
|
filteredOutCount: 0,
|
||||||
|
policyName: "none",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredModels = models.filter(looksLikeImageGenerationModel);
|
||||||
|
if (filteredModels.length === 0) {
|
||||||
|
return {
|
||||||
|
models,
|
||||||
|
usedFallback: models.length > 0,
|
||||||
|
filteredOutCount: 0,
|
||||||
|
policyName: "fallback-all",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
models: filteredModels,
|
||||||
|
usedFallback: false,
|
||||||
|
filteredOutCount: models.length - filteredModels.length,
|
||||||
|
policyName: "image-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
+2
-1
@@ -74,8 +74,9 @@ export default defineConfig(({ mode }) => {
|
|||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
server: {
|
server: {
|
||||||
|
host: "127.0.0.1",
|
||||||
port: 1420,
|
port: 1420,
|
||||||
strictPort: false,
|
strictPort: true,
|
||||||
watch: {
|
watch: {
|
||||||
ignored: ["**/src-tauri/**"],
|
ignored: ["**/src-tauri/**"],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user