feat: 平台管理 - 模型提供商连接测试功能

- 前端:添加"测试连接"按钮,支持实时测试 provider 可用性
- 后端:新增 TestProvider 接口,发送测试请求验证配置
- 路由:注册 POST /providers/{id}/test 端点
- 初始化:LLM Manager 优先从数据库加载 providers,失败时回退到环境变量
- .gitignore:忽略构建产物 server/bin/、server-* 和 test-* 目录
This commit is contained in:
selfrelease
2026-06-22 18:05:19 +08:00
parent fe2c6fb2e4
commit 0c1a8544e8
4 changed files with 175 additions and 40 deletions
+37 -1
View File
@@ -26,7 +26,7 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { toast } from "sonner";
import { Plus, Pencil, Power, Trash2, Cpu, CheckCircle2, XCircle } from "lucide-react";
import { Plus, Pencil, Power, Trash2, Cpu, CheckCircle2, XCircle, Wifi } from "lucide-react";
import { Pagination } from "@/components/ui/pagination";
interface ProviderForm {
@@ -55,6 +55,7 @@ export default function PlatformProvidersPage() {
const [editing, setEditing] = useState<ProviderForm | null>(null);
const [deleteTarget, setDeleteTarget] = useState<ModelProvider | null>(null);
const [page, setPage] = useState(1);
const [testingId, setTestingId] = useState<string | null>(null);
const { data: providers } = useQuery({
queryKey: ["platformProviders"],
@@ -126,6 +127,31 @@ export default function PlatformProvidersPage() {
onError: (e: Error) => toast.error(e.message),
});
const testConnection = useMutation({
mutationFn: (id: string) => api.post(`/api/v1/platform/providers/${id}/test`),
onSuccess: (data: any) => {
if (data.success) {
toast.success("连接测试成功", {
description: data.response || "Provider 响应正常",
});
} else {
toast.error("连接测试失败", {
description: data.message || "未知错误",
});
}
setTestingId(null);
},
onError: (e: Error) => {
toast.error("连接测试失败", { description: e.message });
setTestingId(null);
},
});
const handleTest = (id: string) => {
setTestingId(id);
testConnection.mutate(id);
};
const handleSubmit = () => {
if (!editing) return;
if (!editing.name || !editing.base_url) {
@@ -222,6 +248,16 @@ export default function PlatformProvidersPage() {
>
<Pencil className="h-3 w-3" />
</Button>
<Button
size="sm"
variant="ghost"
className="h-7 gap-1 text-xs"
onClick={() => handleTest(p.id)}
disabled={testingId === p.id}
>
<Wifi className="h-3 w-3" />
{testingId === p.id ? "测试中..." : "测试连接"}
</Button>
<Button
size="sm"
variant="ghost"