refactor: 简化模型管理,移除启用/禁用,优化测试和布局

- 移除供应商/模型启用禁用能力,清理DB schema/migration/API/前端
- 供应商测试改为Base URL连通性+/models探测
- 新增POST /api/models/test模型连接测试
- 新增GET /api/providers/options专用供应商选项接口
- 统一工具栏为ModelsToolbar,参考项目管理布局
- 模型弹窗优化:默认能力、响应式3列标签、并排数值
- 前后端正整数校验、供应商下拉loading/error/empty状态
- 表格列宽统一,操作列/名称列固定宽度
This commit is contained in:
2026-05-29 18:03:33 +08:00
parent 9241c782e6
commit 34e915ccf4
39 changed files with 895 additions and 961 deletions

View File

@@ -38,6 +38,12 @@ export async function handleCreateModel(req: Request, db: Database, mode: Runtim
return jsonResponse(createApiError(`Invalid capabilities: ${invalidCaps.join(", ")}`, 400), { mode, status: 400 });
}
const numberError = validateOptionalPositiveInteger("contextLength", body.contextLength);
if (numberError) return jsonResponse(createApiError(numberError, 400), { mode, status: 400 });
const tokenError = validateOptionalPositiveInteger("maxOutputTokens", body.maxOutputTokens);
if (tokenError) return jsonResponse(createApiError(tokenError, 400), { mode, status: 400 });
const result = createModel(db, body);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
@@ -45,3 +51,9 @@ export async function handleCreateModel(req: Request, db: Database, mode: Runtim
return jsonResponse(result, { mode, status: 201 });
}
function validateOptionalPositiveInteger(field: string, value: null | number | undefined): null | string {
if (value === undefined || value === null) return null;
if (!Number.isInteger(value) || value <= 0) return `${field} must be a positive integer`;
return null;
}

View File

@@ -1,22 +0,0 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import { disableModel } from "../../db/models";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export function handleDisableModel(req: Request, db: Database, mode: RuntimeMode): Response {
const url = new URL(req.url);
const idStr = url.pathname.split("/")[3];
const validated = validateIdParam(idStr ?? "", mode);
if (validated instanceof Response) return validated;
const result = disableModel(db, validated.id);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
return jsonResponse(result, { mode });
}

View File

@@ -1,22 +0,0 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import { enableModel } from "../../db/models";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export function handleEnableModel(req: Request, db: Database, mode: RuntimeMode): Response {
const url = new URL(req.url);
const idStr = url.pathname.split("/")[3];
const validated = validateIdParam(idStr ?? "", mode);
if (validated instanceof Response) return validated;
const result = enableModel(db, validated.id);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
return jsonResponse(result, { mode });
}

View File

@@ -0,0 +1,42 @@
import type Database from "bun:sqlite";
import type { RuntimeMode, TestModelRequest } from "../../../shared/api";
import { testModelConnection } from "../../ai/registry";
import { getProvider } from "../../db/providers";
import { createApiError, jsonResponse } from "../../helpers";
export async function handleTestModelConfig(req: Request, db: Database, mode: RuntimeMode): Promise<Response> {
let body: TestModelRequest;
try {
body = (await req.json()) as TestModelRequest;
} catch {
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
}
if (!body.providerId || typeof body.providerId !== "string") {
return jsonResponse(createApiError("providerId is required", 400), { mode, status: 400 });
}
if (!body.modelId || typeof body.modelId !== "string") {
return jsonResponse(createApiError("modelId is required", 400), { mode, status: 400 });
}
const providerResult = getProvider(db, body.providerId);
if ("error" in providerResult) {
return jsonResponse(createApiError(providerResult.error, providerResult.status), {
mode,
status: providerResult.status,
});
}
const testResult = await testModelConnection({
apiKey: providerResult.provider.apiKey,
baseUrl: providerResult.provider.baseUrl,
modelId: body.modelId,
name: providerResult.provider.name,
type: providerResult.provider.type,
});
return jsonResponse({ modelTestResponse: testResult }, { mode });
}

View File

@@ -34,6 +34,12 @@ export async function handleUpdateModel(req: Request, db: Database, mode: Runtim
}
}
const numberError = validateOptionalPositiveInteger("contextLength", body.contextLength);
if (numberError) return jsonResponse(createApiError(numberError, 400), { mode, status: 400 });
const tokenError = validateOptionalPositiveInteger("maxOutputTokens", body.maxOutputTokens);
if (tokenError) return jsonResponse(createApiError(tokenError, 400), { mode, status: 400 });
const result = updateModel(db, validated.id, body);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
@@ -41,3 +47,9 @@ export async function handleUpdateModel(req: Request, db: Database, mode: Runtim
return jsonResponse(result, { mode });
}
function validateOptionalPositiveInteger(field: string, value: null | number | undefined): null | string {
if (value === undefined || value === null) return null;
if (!Number.isInteger(value) || value <= 0) return `${field} must be a positive integer`;
return null;
}

View File

@@ -1,22 +0,0 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import { disableProvider } from "../../db/providers";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export function handleDisableProvider(req: Request, db: Database, mode: RuntimeMode): Response {
const url = new URL(req.url);
const idStr = url.pathname.split("/")[3];
const validated = validateIdParam(idStr ?? "", mode);
if (validated instanceof Response) return validated;
const result = disableProvider(db, validated.id);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
return jsonResponse(result, { mode });
}

View File

@@ -1,22 +0,0 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import { enableProvider } from "../../db/providers";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export function handleEnableProvider(req: Request, db: Database, mode: RuntimeMode): Response {
const url = new URL(req.url);
const idStr = url.pathname.split("/")[3];
const validated = validateIdParam(idStr ?? "", mode);
if (validated instanceof Response) return validated;
const result = enableProvider(db, validated.id);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
return jsonResponse(result, { mode });
}

View File

@@ -0,0 +1,10 @@
import type Database from "bun:sqlite";
import type { RuntimeMode } from "../../../shared/api";
import { listProviderOptions } from "../../db/providers";
import { jsonResponse } from "../../helpers";
export function handleListProviderOptions(db: Database, mode: RuntimeMode): Response {
return jsonResponse({ items: listProviderOptions(db) }, { mode });
}

View File

@@ -3,35 +3,7 @@ import type Database from "bun:sqlite";
import type { CreateProviderRequest, RuntimeMode } from "../../../shared/api";
import { testProviderConnection } from "../../ai/registry";
import { getProvider } from "../../db/providers";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
export async function handleTestProvider(req: Request, db: Database, mode: RuntimeMode): Promise<Response> {
const url = new URL(req.url);
const idStr = url.pathname.split("/")[3];
const validated = validateIdParam(idStr ?? "", mode);
if (validated instanceof Response) return validated;
const providerResult = getProvider(db, validated.id);
if ("error" in providerResult) {
return jsonResponse(createApiError(providerResult.error, providerResult.status), {
mode,
status: providerResult.status,
});
}
const provider = providerResult.provider;
const testResult = await testProviderConnection({
apiKey: provider.apiKey,
baseUrl: provider.baseUrl,
name: provider.name,
type: provider.type,
});
return jsonResponse({ providerTestResponse: testResult }, { mode });
}
export async function handleTestProviderConfig(req: Request, _db: Database, mode: RuntimeMode): Promise<Response> {
const validated = await readProviderConfig(req, mode);