feat(qoder): fetch latest model + nút import model trên dashboard
Merge PR #1642 (decolua/9router) — chỉ lấy code + i18n, bỏ test/docs/package.json. - qoder.js: bỏ guard QODER_MODEL_MAP cứng, resolve model_config qua dynamic API (hỗ trợ qmodel_latest không cần sửa code) - dashboard page: thêm nút "Fetch Qoder Models" tự import model list vào aliases - i18n zh-CN: thêm key cho nút fetch Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthW
|
||||
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, WEB_COOKIE_PROVIDERS, getProviderAlias, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, AI_PROVIDERS, THINKING_CONFIG } from "@/shared/constants/providers";
|
||||
import { getModelsByProviderId } from "@/shared/constants/models";
|
||||
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
|
||||
import { translate } from "@/i18n/runtime";
|
||||
import { fetchSuggestedModels } from "@/shared/utils/providerModelsFetcher";
|
||||
import ModelRow from "./ModelRow";
|
||||
import PassthroughModelsSection from "./PassthroughModelsSection";
|
||||
@@ -62,6 +63,7 @@ export default function ProviderDetailPage() {
|
||||
const [oneByOneResults, setOneByOneResults] = useState({});
|
||||
const [oneByOneSummary, setOneByOneSummary] = useState(null);
|
||||
const stopOneByOneRef = useRef(false);
|
||||
const [importingQoderModels, setImportingQoderModels] = useState(false);
|
||||
const { copied, copy } = useCopyToClipboard();
|
||||
|
||||
const AG_RISK_STORAGE_KEY = "ag_risk_confirmed";
|
||||
@@ -409,6 +411,66 @@ export default function ProviderDetailPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch Qoder model list and automatically add to available models
|
||||
const handleImportQoderModels = async () => {
|
||||
if (importingQoderModels) return;
|
||||
const activeConnection = connections.find((conn) => conn.isActive !== false);
|
||||
if (!activeConnection) {
|
||||
alert(translate("Please add an active Qoder connection first"));
|
||||
return;
|
||||
}
|
||||
|
||||
setImportingQoderModels(true);
|
||||
try {
|
||||
const res = await fetch(`/api/providers/${activeConnection.id}/models`);
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
alert(data.error || translate("Failed to fetch models"));
|
||||
return;
|
||||
}
|
||||
const models = data.models || [];
|
||||
if (models.length === 0) {
|
||||
alert(translate("No models returned"));
|
||||
return;
|
||||
}
|
||||
|
||||
let importedCount = 0;
|
||||
for (const model of models) {
|
||||
const modelId = model.id || model.name;
|
||||
if (!modelId) continue;
|
||||
|
||||
// Qoder model ID format may be "qoder/auto" or "auto", need to remove prefix
|
||||
const cleanModelId = modelId.replace(/^qoder\//, "");
|
||||
const fullModel = `${providerStorageAlias}/${cleanModelId}`;
|
||||
|
||||
// Check if already exists
|
||||
if (Object.values(modelAliases).includes(fullModel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use model ID as alias
|
||||
const alias = cleanModelId;
|
||||
if (modelAliases[alias]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await handleSetAlias(cleanModelId, alias, providerStorageAlias);
|
||||
importedCount += 1;
|
||||
}
|
||||
|
||||
if (importedCount === 0) {
|
||||
alert(translate("All models already exist, no new models added"));
|
||||
} else {
|
||||
alert(translate("Successfully added") + ` ${importedCount} ` + translate("models"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error importing Qoder models:", error);
|
||||
alert(translate("Error fetching models") + ": " + error.message);
|
||||
} finally {
|
||||
setImportingQoderModels(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRunOneByOneTest = async () => {
|
||||
if (oneByOneRunning || connections.length === 0) return;
|
||||
|
||||
@@ -926,6 +988,20 @@ export default function ProviderDetailPage() {
|
||||
Add Model
|
||||
</button>
|
||||
|
||||
{/* Import Qoder models button — only show for qoder provider */}
|
||||
{providerId === "qoder" && connections.some((conn) => conn.isActive !== false) && (
|
||||
<button
|
||||
onClick={handleImportQoderModels}
|
||||
disabled={importingQoderModels}
|
||||
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-blue-500/40 px-3 py-2 text-xs text-blue-600 dark:text-blue-400 transition-colors hover:border-blue-500 hover:bg-blue-500/5 sm:w-auto disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm" style={importingQoderModels ? { animation: "spin 1s linear infinite" } : undefined}>
|
||||
{importingQoderModels ? "progress_activity" : "download"}
|
||||
</span>
|
||||
{importingQoderModels ? translate("Fetching...") : translate("Fetch Qoder Models")}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Suggested models from provider API — show only models not yet added */}
|
||||
{suggestedModels.length > 0 && (() => {
|
||||
const addedFullModels = new Set(Object.values(modelAliases));
|
||||
|
||||
Reference in New Issue
Block a user