Add Test Connection button to AI settings with plain-English error messages
- POST /settings/ai/test sends a minimal text prompt to verify the provider - Returns ok/message — maps HTTP status codes to human-readable explanations - 405 → "URL pointing at wrong place, check custom URL has no path suffix" - 401 → invalid key, 404 → wrong path, 429 → rate limited, etc. - Test button appears once a key is saved; result shown inline below buttons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e676e698ff
commit
1ece0908af
4 changed files with 8732 additions and 2 deletions
|
|
@ -8,7 +8,7 @@ import {
|
|||
changePassword, updateProfile, exportData, getMe,
|
||||
} from "@/api/auth";
|
||||
import { listBackups, triggerBackup, downloadBackup, restoreBackup } from "@/api/admin";
|
||||
import { getAiSettings, saveAiSettings, clearAiSettings } from "@/api/settings";
|
||||
import { getAiSettings, saveAiSettings, clearAiSettings, testAiSettings } from "@/api/settings";
|
||||
import type { AiSettings } from "@/api/settings";
|
||||
import type { BackupFile } from "@/api/admin";
|
||||
import { cn } from "@/utils/cn";
|
||||
|
|
@ -699,6 +699,13 @@ function AiSection() {
|
|||
},
|
||||
});
|
||||
|
||||
const [testResult, setTestResult] = useState<{ ok: boolean; message: string } | null>(null);
|
||||
const testMutation = useMutation({
|
||||
mutationFn: testAiSettings,
|
||||
onSuccess: (result) => setTestResult(result),
|
||||
onError: (e: any) => setTestResult({ ok: false, message: e?.response?.data?.detail ?? "Test failed" }),
|
||||
});
|
||||
|
||||
const defaultModel = provider === "anthropic" ? "claude-haiku-4-5-20251001" : "gpt-4o-mini";
|
||||
const defaultUrl = provider === "anthropic" ? "https://api.anthropic.com" : "https://api.openai.com";
|
||||
|
||||
|
|
@ -793,7 +800,7 @@ function AiSection() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="flex gap-3 flex-wrap">
|
||||
<button
|
||||
onClick={() => saveMutation.mutate()}
|
||||
disabled={saveMutation.isPending || (!apiKey && !current?.has_api_key)}
|
||||
|
|
@ -803,6 +810,17 @@ function AiSection() {
|
|||
Save settings
|
||||
</button>
|
||||
|
||||
{current?.has_api_key && (
|
||||
<button
|
||||
onClick={() => { setTestResult(null); testMutation.mutate(); }}
|
||||
disabled={testMutation.isPending}
|
||||
className="flex items-center gap-2 border border-border px-4 py-2 rounded-lg text-sm font-medium hover:bg-secondary disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{testMutation.isPending ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
|
||||
Test connection
|
||||
</button>
|
||||
)}
|
||||
|
||||
{current?.has_api_key && (
|
||||
<button
|
||||
onClick={() => clearMutation.mutate()}
|
||||
|
|
@ -814,6 +832,20 @@ function AiSection() {
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{testResult && (
|
||||
<div className={cn(
|
||||
"flex items-start gap-2 rounded-lg px-3 py-2.5 text-sm",
|
||||
testResult.ok
|
||||
? "bg-success/10 border border-success/30 text-success"
|
||||
: "bg-destructive/10 border border-destructive/30 text-destructive"
|
||||
)}>
|
||||
{testResult.ok
|
||||
? <CheckCircle className="w-4 h-4 shrink-0 mt-0.5" />
|
||||
: <AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" />}
|
||||
<span>{testResult.message}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue