Enhance configuration and model capabilities

This commit is contained in:
decolua
2026-06-16 23:32:28 +07:00
parent b282f05549
commit d03f9fb823
17 changed files with 121 additions and 52 deletions
@@ -48,7 +48,7 @@ export default function ProviderDetailPage() {
const [headerImgError, setHeaderImgError] = useState(false);
const [modelTestResults, setModelTestResults] = useState({});
const [modelsTestError, setModelsTestError] = useState("");
const [testingModelId, setTestingModelId] = useState(null);
const [testingModelIds, setTestingModelIds] = useState(() => new Set());
const [showAddCustomModel, setShowAddCustomModel] = useState(false);
const [selectedConnectionIds, setSelectedConnectionIds] = useState([]);
const [bulkProxyPoolId, setBulkProxyPoolId] = useState("__none__");
@@ -876,8 +876,8 @@ export default function ProviderDetailPage() {
);
const handleTestModel = async (modelId) => {
if (testingModelId) return;
setTestingModelId(modelId);
if (testingModelIds.has(modelId)) return;
setTestingModelIds((prev) => new Set(prev).add(modelId));
try {
const res = await fetch("/api/models/test", {
method: "POST",
@@ -891,7 +891,7 @@ export default function ProviderDetailPage() {
setModelTestResults((prev) => ({ ...prev, [modelId]: "error" }));
setModelsTestError("Network error");
} finally {
setTestingModelId(null);
setTestingModelIds((prev) => { const n = new Set(prev); n.delete(modelId); return n; });
}
};
@@ -952,7 +952,7 @@ export default function ProviderDetailPage() {
onDeleteAlias={() => handleDeleteAlias(model.alias)}
testStatus={modelTestResults[model.id]}
onTest={connections.length > 0 || isFreeNoAuth ? () => handleTestModel(model.id) : undefined}
isTesting={testingModelId === model.id}
isTesting={testingModelIds.has(model.id)}
isCustom
isFree={false}
caps={getCaps(`${providerId}/${model.id}`)}
@@ -977,7 +977,7 @@ export default function ProviderDetailPage() {
onDeleteAlias={() => handleDeleteAlias(existingAlias)}
testStatus={modelTestResults[model.id]}
onTest={connections.length > 0 || isFreeNoAuth ? () => handleTestModel(model.id) : undefined}
isTesting={testingModelId === model.id}
isTesting={testingModelIds.has(model.id)}
isFree={model.isFree}
onDisable={() => handleDisableModel(model.id)}
caps={getCaps(`${providerId}/${model.id}`)}
@@ -281,15 +281,18 @@ export default function ProvidersPage() {
Object.entries(OAUTH_PROVIDERS).filter(([, info]) => !info.hidden && matchSearch(info.name)),
"oauth",
);
const freeEntries = Object.entries(FREE_PROVIDERS).filter(
([, info]) => !info.hidden && matchSearch(info.name),
);
const freeEntries = Object.entries(FREE_PROVIDERS)
.filter(([, info]) => !info.hidden && matchSearch(info.name))
.sort(([, a], [, b]) => (b.noAuth ? 1 : 0) - (a.noAuth ? 1 : 0));
const freeTierEntries = sortByPriority(
Object.entries(FREE_TIER_PROVIDERS).filter(
([, info]) => !info.hidden && matchSearch(info.name),
([, info]) =>
!info.hidden &&
matchSearch(info.name) &&
(info.serviceKinds ?? ["llm"]).includes("llm"),
),
"freeTier",
);
).sort(([, a], [, b]) => (b.noAuth ? 1 : 0) - (a.noAuth ? 1 : 0));
// API Key: connected providers first, then alphabetical by name
const apikeyEntries = Object.entries(APIKEY_PROVIDERS)
.filter(
+6 -1
View File
@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { getCapabilitiesForModel } from "open-sse/providers/capabilities.js";
// Fetch model capabilities once and expose a lookup by fullModel ("provider/model") or bare model id.
export function useModelCaps() {
@@ -32,7 +33,11 @@ export function useModelCaps() {
if (!key) return null;
if (byFull[key]) return byFull[key];
const bare = key.includes("/") ? key.slice(key.indexOf("/") + 1) : key;
return byId[bare] || null;
if (byId[bare]) return byId[bare];
// Fallback: compute caps for dynamic models (passthrough/custom/suggested) not in static list
const provider = key.includes("/") ? key.slice(0, key.indexOf("/")) : null;
const c = getCapabilitiesForModel(provider, bare);
return { vision: c.vision, search: c.search, reasoning: c.reasoning };
};
return { getCaps };