diff --git a/package.json b/package.json
index e5c082ff..11b684e0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "9router-app",
- "version": "0.3.17",
+ "version": "0.3.18",
"description": "9Router web dashboard",
"private": true,
"scripts": {
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js
index 368b3990..c9322880 100644
--- a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js
+++ b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js
@@ -351,6 +351,14 @@ export default function AntigravityToolCard({
>
)}
+ {/* Windows admin warning */}
+ {!isRunning && isWindows && (
+
+ warning
+ Windows: Run terminal (9Router) as Administrator to enable MITM
+
+ )}
+
{/* When stopped: how it works */}
{!isRunning && (
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.js b/src/app/(dashboard)/dashboard/providers/[id]/page.js
index f49afa62..6a499e87 100644
--- a/src/app/(dashboard)/dashboard/providers/[id]/page.js
+++ b/src/app/(dashboard)/dashboard/providers/[id]/page.js
@@ -25,7 +25,7 @@ export default function ProviderDetailPage() {
const [modelAliases, setModelAliases] = useState({});
const [headerImgError, setHeaderImgError] = useState(false);
const [modelTestResults, setModelTestResults] = useState({});
- const [testingModels, setTestingModels] = useState(false);
+ const [testingModelId, setTestingModelId] = useState(null);
const [showAddCustomModel, setShowAddCustomModel] = useState(false);
const { copied, copy } = useCopyToClipboard();
@@ -259,24 +259,21 @@ export default function ProviderDetailPage() {
}
};
- const handleTestModels = async () => {
- if (testingModels) return;
- const conn = connections.find((c) => c.isActive !== false) || connections[0];
- if (!conn) return;
- setTestingModels(true);
- setModelTestResults({});
+ const handleTestModel = async (modelId) => {
+ if (testingModelId) return;
+ setTestingModelId(modelId);
try {
- const res = await fetch(`/api/providers/${conn.id}/test-models`, { method: "POST" });
+ const res = await fetch("/api/models/test", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ model: `${providerStorageAlias}/${modelId}` }),
+ });
const data = await res.json();
- if (res.ok) {
- const map = {};
- for (const r of data.results || []) map[r.modelId] = r.ok ? "ok" : "error";
- setModelTestResults(map);
- }
+ setModelTestResults((prev) => ({ ...prev, [modelId]: data.ok ? "ok" : "error" }));
} catch {
- // silent fail
+ setModelTestResults((prev) => ({ ...prev, [modelId]: "error" }));
} finally {
- setTestingModels(false);
+ setTestingModelId(null);
}
};
@@ -342,6 +339,8 @@ export default function ProviderDetailPage() {
onSetAlias={(alias) => handleSetAlias(model.id, alias, providerStorageAlias)}
onDeleteAlias={() => handleDeleteAlias(existingAlias)}
testStatus={modelTestResults[model.id]}
+ onTest={connections.length > 0 ? () => handleTestModel(model.id) : undefined}
+ isTesting={testingModelId === model.id}
/>
);
})}
@@ -559,18 +558,6 @@ export default function ProviderDetailPage() {
{providerInfo.passthroughModels ? "Model Aliases" : "Available Models"}
- {connections.length > 0 && (
-
- )}
{renderModelsSection()}
@@ -638,7 +625,7 @@ export default function ProviderDetailPage() {
);
}
-function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus, isCustom, onDeleteAlias }) {
+function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus, isCustom, onDeleteAlias, onTest, isTesting }) {
const borderColor = testStatus === "ok"
? "border-green-500/40"
: testStatus === "error"
@@ -660,6 +647,18 @@ function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus, isCusto
{testStatus === "ok" ? "check_circle" : testStatus === "error" ? "cancel" : "smart_toy"}
{fullModel}
+ {onTest && (
+
+ )}