diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index b55835116..87ce33728 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -1337,6 +1337,7 @@ export function AdminSettingsClient() { const [enhanceProConfig, setEnhanceProConfig] = useState( defaultEnhanceProKreaConfig() ); + const [enhanceProApiKeysDraft, setEnhanceProApiKeysDraft] = useState(""); const [enhanceProPresetEditorMode, setEnhanceProPresetEditorMode] = useState< "simple" | "advanced" >("simple"); @@ -1449,6 +1450,10 @@ export function AdminSettingsClient() { const [threeDModelAliasConfig, setThreeDModelAliasConfig] = useState( defaultThreeDModelAliasConfig() ); + const [tripoProviderSearch, setTripoProviderSearch] = useState(""); + const [tripoProviderBusyId, setTripoProviderBusyId] = useState< + string | null + >(null); const [worldlabsProviderSearch, setWorldlabsProviderSearch] = useState(""); const [worldlabsProviderBusyId, setWorldlabsProviderBusyId] = useState< string | null @@ -1564,8 +1569,17 @@ export function AdminSettingsClient() { setEnhanceProConfig( normalizeEnhanceProKreaConfig(enhanceProConfigData.setting?.value) ); + setEnhanceProApiKeysDraft(""); }, [enhanceProConfigData]); + const enhanceProApiKeysDraftCount = useMemo( + () => + normalizeEnhanceProKreaConfig({ + apiKeys: enhanceProApiKeysDraft, + }).apiKeys.length, + [enhanceProApiKeysDraft] + ); + useEffect(() => { if (enhanceProConfig.presets.length === 0) { if (selectedEnhanceProPresetId) { @@ -1976,6 +1990,28 @@ export function AdminSettingsClient() { ); }, [threeDModelAliasConfigData]); + const tripoProviders = useMemo( + () => + (providersData?.providers || []).filter( + (provider) => provider.type === "tripo-3d" + ), + [providersData?.providers] + ); + const filteredTripoProviders = useMemo(() => { + const keyword = tripoProviderSearch.trim().toLowerCase(); + if (!keyword) return tripoProviders; + + return tripoProviders.filter((provider) => + [provider.name, provider.apiKey, provider.baseUrl] + .filter(Boolean) + .some((value) => value.toLowerCase().includes(keyword)) + ); + }, [tripoProviderSearch, tripoProviders]); + const enabledTripoProviderCount = useMemo( + () => tripoProviders.filter((provider) => provider.isEnabled).length, + [tripoProviders] + ); + const worldlabsProviders = useMemo( () => (providersData?.providers || []).filter( @@ -3138,13 +3174,28 @@ export function AdminSettingsClient() { const handleSaveEnhanceProConfig = async () => { try { + const hasEnhanceProApiKeysDraft = + enhanceProApiKeysDraft.trim().length > 0; + const normalizedConfig = normalizeEnhanceProKreaConfig({ + ...enhanceProConfig, + providerName: + enhanceProConfig.providerName.trim() || "Krea Topaz Generative", + baseUrl: + enhanceProConfig.baseUrl.trim() || + ENHANCE_PRO_KREA_DEFAULT_BASE_URL, + apiKey: enhanceProConfig.apiKey.trim(), + apiKeys: hasEnhanceProApiKeysDraft + ? enhanceProApiKeysDraft + : enhanceProConfig.apiKeys, + }); + if (enhanceProConfig.enabled) { - if (!enhanceProConfig.baseUrl.trim()) { + if (!normalizedConfig.baseUrl) { toast.error("请输入 Krea API 地址"); return; } - if (!enhanceProConfig.apiKey.trim()) { - toast.error("请输入 Krea API Key"); + if (!normalizedConfig.apiKey && normalizedConfig.apiKeys.length === 0) { + toast.error("请输入 Krea API Key 或 Key 池"); return; } } @@ -3154,22 +3205,7 @@ export function AdminSettingsClient() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key: ENHANCE_PRO_KREA_SETTING_KEY, - value: { - enabled: enhanceProConfig.enabled, - providerName: - enhanceProConfig.providerName.trim() || "Krea Topaz Generative", - baseUrl: - enhanceProConfig.baseUrl.trim() || - ENHANCE_PRO_KREA_DEFAULT_BASE_URL, - apiKey: enhanceProConfig.apiKey.trim(), - defaultModel: enhanceProConfig.defaultModel, - defaultOutputFormat: enhanceProConfig.defaultOutputFormat, - allowBatchSize: enhanceProConfig.allowBatchSize, - maxBatchSize: enhanceProConfig.maxBatchSize, - maxOutputEdge: enhanceProConfig.maxOutputEdge, - enableAdvancedControls: enhanceProConfig.enableAdvancedControls, - presets: enhanceProConfig.presets, - }, + value: normalizedConfig, }), }); @@ -4322,6 +4358,82 @@ export function AdminSettingsClient() { } }; + const handleAddTripo3dPoolKey = async () => { + try { + const trimmedName = tripo3dConfig.name.trim() || "Tripo3D Key"; + const trimmedBaseUrl = + tripo3dConfig.baseUrl.trim() || getProviderDefaultBaseUrl("tripo-3d"); + const trimmedApiKey = tripo3dConfig.apiKey.trim(); + + if (!trimmedApiKey) { + toast.error("请填写要加入 Key 池的 Tripo3D API Key"); + return; + } + + const response = await fetch("/api/admin/ai-providers", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: trimmedName, + type: "tripo-3d", + baseUrl: trimmedBaseUrl, + apiKey: trimmedApiKey, + isEnabled: tripo3dConfig.isEnabled, + }), + }); + + if (!response.ok) { + throw new Error("添加失败"); + } + + toast.success("Tripo3D Key 已加入池"); + setTripo3dConfig((prev) => ({ ...prev, apiKey: "" })); + mutateProviders(); + } catch (error) { + toast.error("添加 Tripo3D Key 失败"); + } + }; + + const handleEditTripoPoolProvider = (provider: AiProvider) => { + setTripo3dConfig({ + providerId: provider.id, + name: provider.name || "Tripo3D", + baseUrl: provider.baseUrl || getProviderDefaultBaseUrl("tripo-3d"), + apiKey: "", + isEnabled: provider.isEnabled, + }); + }; + + const handleToggleTripoPoolProvider = async ( + provider: AiProvider, + isEnabled: boolean + ) => { + setTripoProviderBusyId(provider.id); + try { + const response = await fetch("/api/admin/ai-providers", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + id: provider.id, + isEnabled, + }), + }); + + if (!response.ok) { + throw new Error("更新状态失败"); + } + + toast.success( + `已${isEnabled ? "启用" : "停用"} ${provider.name || "Tripo3D Key"}` + ); + mutateProviders(); + } catch (error) { + toast.error("更新 Tripo3D Key 状态失败"); + } finally { + setTripoProviderBusyId(null); + } + }; + const handleSaveWorldlabs3dConfig = async () => { try { const trimmedName = worldlabs3dConfig.name.trim() || "World Labs"; @@ -13244,6 +13356,101 @@ export function AdminSettingsClient() { } /> +
+
+
+
Tripo3D Key 池
+
+ 当前共 {tripoProviders.length} 个 Key,已启用{" "} + {enabledTripoProviderCount} 个。生成任务会轮询使用可用 Key。 +
+
+
+ + setTripoProviderSearch(e.target.value)} + /> +
+
+ +
+ {filteredTripoProviders.length ? ( + filteredTripoProviders.map((provider) => ( +
+
+
+
{provider.name}
+ + {provider.isEnabled ? "已启用" : "已禁用"} + +
+
+ Key: {provider.apiKey} +
+
+ API: {provider.baseUrl} +
+
+ 创建时间: {formatBeijingTime(provider.createdAt)} +
+
+
+ +
+ + void handleToggleTripoPoolProvider( + provider, + checked + ) + } + /> + + {provider.isEnabled ? "启用中" : "已停用"} + +
+ +
+
+ )) + ) : ( +
+ {tripoProviders.length + ? "没有匹配到符合筛选条件的 Tripo3D Key。" + : "当前还没有导入任何 Tripo3D Key。"} +
+ )} +
+
@@ -13263,9 +13470,14 @@ export function AdminSettingsClient() {
- +
+ + +
@@ -14595,6 +14807,52 @@ export function AdminSettingsClient() { /> +
+ +