fix: 质量修复 — ESLint 规则 TS6 兼容 + catch 注解 + 空函数体注释化 + 后端架构对齐 + 前端红线修复

- enforce-catch-type: 增加 TSUnknownKeyword 判断,消除28个 TS6 假阳性
- no-empty-function: 统一为注释方案,移除测试/生产分支和 eslint-disable 引导
- logger.ts: 空函数体改为注释说明,删除无用 eslint-disable 指令
- 补充15处 catch 子句 : unknown 类型注解
- 清理7个测试文件失效 eslint-disable 指令
- chat/send.ts: 提取 getModelWithProvider DAO,消除直接 Drizzle 操作
- projects/update.ts: 修复死代码+条件逻辑 bug
- providers/update.ts: 补充至少一个字段校验
- 前端: inline style → CSS className, ProviderFormModal whitespace 校验
- 开发文档: 更新 Zod 使用说明(AI SDK 框架级约束)
This commit is contained in:
2026-06-01 23:11:42 +08:00
parent 0d60120219
commit ab7b7fb189
28 changed files with 124 additions and 83 deletions

View File

@@ -47,7 +47,7 @@ export async function bootstrap(options: BootstrapOptions, dependencies: Bootstr
try {
logger = await buildLogger(config.logging, options.mode, options.version);
} catch (logInitError) {
} catch (logInitError: unknown) {
createFallback().fatal(
`日志初始化失败: ${logInitError instanceof Error ? logInitError.message : String(logInitError)}`,
);
@@ -83,7 +83,7 @@ export async function bootstrap(options: BootstrapOptions, dependencies: Bootstr
staticAssets: options.staticAssets,
version: options.version,
});
} catch (error) {
} catch (error: unknown) {
if (logger) {
logger.fatal({ error: error instanceof Error ? error.message : String(error) }, "启动失败");
logger.flush();

View File

@@ -192,7 +192,7 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
if (bytes <= 0) {
issues.push(issue("invalid-value", "server.logging.file.rotation.size", "滚动大小必须为正整数字节数"));
}
} catch (error) {
} catch (error: unknown) {
issues.push(
issue(
"invalid-value",

View File

@@ -11,4 +11,13 @@ export {
} from "./conversations";
export { loadMigrationsFromDir, type MigrationRecord } from "./load-migrations";
export { runMigrations } from "./migrate";
export {
createModel,
deleteModel,
getModel,
getModelsByProviderId,
getModelWithProvider,
listModels,
updateModel,
} from "./models";
export { conversations, messages, projects, schemaMigrations } from "./schema";

View File

@@ -90,6 +90,38 @@ export function getModelsByProviderId(raw: Database, providerId: string): number
return Number(result?.count ?? 0);
}
export function getModelWithProvider(
raw: Database,
modelId: string,
):
| { error: string; status: number }
| {
model: { modelId: string; name: string; providerId: string };
provider: { apiKey: string; baseUrl: string; id: string; type: string };
} {
const db = wrap(raw);
const row = db.select().from(models).where(eq(models.id, modelId)).get();
if (!row) return { error: "模型不存在", status: 404 };
const providerRow = db.select().from(providers).where(eq(providers.id, row.providerId)).get();
if (!providerRow) return { error: "供应商不存在", status: 404 };
return {
model: {
modelId: row.modelId,
name: row.name,
providerId: row.providerId,
},
provider: {
apiKey: providerRow.apiKey,
baseUrl: providerRow.baseUrl,
id: providerRow.id,
type: providerRow.type,
},
};
}
export function listModels(
raw: Database,
options: { keyword?: string; page: number; pageSize: number; providerId?: string },

View File

@@ -1,21 +1,19 @@
import type Database from "bun:sqlite";
import { createAgentUIStreamResponse, generateText, type UIMessage } from "ai";
import { eq } from "drizzle-orm";
import type { RuntimeMode } from "../../../shared/api";
import type { Logger } from "../../logger";
import { createAlfredAgent } from "../../ai/agents/alfred-agent";
import { buildProviderRegistry } from "../../ai/registry";
import { wrap } from "../../db/connection";
import {
createMessage,
getConversation,
updateConversation,
updateConversationTimestamp,
} from "../../db/conversations";
import { models, providers } from "../../db/schema";
import { getModelWithProvider } from "../../db/models";
import { createApiError, jsonResponse } from "../../helpers";
import { validateIdParam } from "../../middleware";
@@ -29,7 +27,7 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
let body: { conversationId?: string; messages?: UIMessage[] };
try {
body = (await req.json()) as typeof body;
} catch (e) {
} catch (e: unknown) {
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
}
@@ -81,19 +79,13 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
let model;
try {
const d = wrap(db);
const modelRow = d.select().from(models).where(eq(models.id, conversation.modelId)).get();
if (!modelRow) {
return jsonResponse(createApiError(`模型不存在: ${conversation.modelId}`, 400), { mode, status: 400 });
}
const providerRow = d.select().from(providers).where(eq(providers.id, modelRow.providerId)).get();
if (!providerRow) {
return jsonResponse(createApiError(`供应商不存在: ${modelRow.providerId}`, 400), { mode, status: 400 });
const result = getModelWithProvider(db, conversation.modelId);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
}
const registry = buildProviderRegistry(db);
model = registry.languageModel(`${providerRow.id}:${modelRow.modelId}`);
model = registry.languageModel(`${result.provider.id}:${result.model.modelId}`);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
return jsonResponse(createApiError(`模型初始化失败:${msg}`, 500), { mode, status: 500 });

View File

@@ -25,10 +25,11 @@ export async function handleUpdateProject(
} catch (e: unknown) {
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
}
if (!body.name && !body.description && body.name !== "" && body.description !== "") {
const hasName = body.name !== undefined && body.name.trim() !== "";
const hasDescription = body.description !== undefined && body.description.trim() !== "";
if (!hasName && !hasDescription) {
return jsonResponse(createApiError("At least one of name or description is required", 400), { mode, status: 400 });
}

View File

@@ -34,6 +34,13 @@ export async function handleUpdateProvider(
});
}
if (body.name === undefined && body.baseUrl === undefined && body.apiKey === undefined && body.type === undefined) {
return jsonResponse(createApiError("至少需要提供 name, baseUrl, apiKey 或 type 中的一个字段", 400), {
mode,
status: 400,
});
}
const result = updateProvider(db, validated.id, body, logger);
if ("error" in result) {
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });

View File

@@ -29,7 +29,7 @@ export async function fetchConversation(projectId: string, conversationId: strin
try {
const response = await fetch(`/api/projects/${projectId}/conversations/${conversationId}`);
return handleResponse(response, (data) => (data as ConversationResponse).conversation);
} catch (err) {
} catch (err: unknown) {
logger.error("获取会话失败", {
conversationId,
error: err instanceof Error ? err.message : String(err),
@@ -61,7 +61,7 @@ export async function updateConversation(
method: "PATCH",
});
return handleResponse(response, (data) => (data as ConversationResponse).conversation);
} catch (err) {
} catch (err: unknown) {
logger.error("更新会话失败", {
conversationId,
error: err instanceof Error ? err.message : String(err),

View File

@@ -109,7 +109,7 @@ export function ModelFormModal({
message.success("模型已创建");
}
onOpenChange(false);
} catch (err) {
} catch (err: unknown) {
if (err instanceof Error) {
message.error(err.message);
}
@@ -136,7 +136,7 @@ export function ModelFormModal({
} else {
message.error(result.message);
}
} catch (err) {
} catch (err: unknown) {
message.error((err as Error).message);
} finally {
setTesting(false);

View File

@@ -69,7 +69,7 @@ export function ModelTable({
try {
await onDelete(id);
message.success("模型已删除");
} catch (err) {
} catch (err: unknown) {
message.error((err as Error).message);
}
};

View File

@@ -83,7 +83,7 @@ export function ProviderFormModal({
message.success("供应商已创建");
}
onOpenChange(false);
} catch (err) {
} catch (err: unknown) {
if (err instanceof Error) {
message.error(err.message);
}
@@ -105,7 +105,7 @@ export function ProviderFormModal({
} else {
message.error(result.message);
}
} catch (err) {
} catch (err: unknown) {
if (err instanceof Error) {
message.error(err.message);
}
@@ -135,10 +135,18 @@ export function ProviderFormModal({
<Form.Item label="供应商类型" name="type" rules={[{ message: "请选择供应商类型", required: true }]}>
<Select options={TYPE_OPTIONS} placeholder="请选择供应商类型" />
</Form.Item>
<Form.Item label="Base URL" name="baseUrl" rules={[{ message: "请输入 Base URL", required: true }]}>
<Form.Item
label="Base URL"
name="baseUrl"
rules={[{ message: "请输入 Base URL", required: true, whitespace: true }]}
>
<Input placeholder="https://api.openai.com/v1" />
</Form.Item>
<Form.Item label="API Key" name="apiKey" rules={[{ message: "请输入 API Key", required: true }]}>
<Form.Item
label="API Key"
name="apiKey"
rules={[{ message: "请输入 API Key", required: true, whitespace: true }]}
>
<Input.Password placeholder="请输入 API Key" />
</Form.Item>
<Form.Item>

View File

@@ -39,7 +39,7 @@ export function ProviderTable({ data, loading, onDelete, onEdit, onPageChange, p
try {
await onDelete(id);
message.success("供应商已删除");
} catch (err) {
} catch (err: unknown) {
message.error((err as Error).message);
}
};

View File

@@ -111,7 +111,7 @@ export function ModelsPage() {
};
return (
<Space orientation="vertical" size="large" style={{ flex: 1 }}>
<Space className="app-page-flex" orientation="vertical" size="large">
<ModelsToolbar
activeTab={activeTab}
key={activeTab}

View File

@@ -53,7 +53,7 @@ export function ProjectFormModal({
message.success("项目已创建");
}
onOpenChange(false);
} catch (err) {
} catch (err: unknown) {
if (err instanceof Error) {
message.error(err.message);
}

View File

@@ -69,7 +69,7 @@ export function ProjectTable({
try {
await onArchive(id);
message.success("项目已归档");
} catch (err) {
} catch (err: unknown) {
message.error((err as Error).message);
}
};
@@ -78,7 +78,7 @@ export function ProjectTable({
try {
await onRestore(id);
message.success("项目已恢复");
} catch (err) {
} catch (err: unknown) {
message.error((err as Error).message);
}
};
@@ -87,7 +87,7 @@ export function ProjectTable({
try {
await onDelete(id);
message.success("项目已永久删除");
} catch (err) {
} catch (err: unknown) {
message.error((err as Error).message);
}
};

View File

@@ -35,7 +35,7 @@ export function ProjectsPage() {
const isRowActionPending = archiveMutation.isPending || restoreMutation.isPending || deleteMutation.isPending;
return (
<Space orientation="vertical" size="large" style={{ flex: 1 }}>
<Space className="app-page-flex" orientation="vertical" size="large">
<ProjectToolbar
activeTab={tabValue}
keyword={keyword}

View File

@@ -250,3 +250,7 @@ body {
.card-extra-actions .btn-dimmed:hover {
color: var(--ant-color-text-secondary);
}
.app-page-flex {
flex: 1;
}

View File

@@ -18,7 +18,9 @@ export interface Sink {
}
class AntdMessageSink implements Sink {
constructor(private messageApi: MessageInstance) {}
constructor(private messageApi: MessageInstance) {
// 仅存储依赖,无需初始化操作
}
write(level: LogLevel, message: string, _data: unknown, _bindings: Record<string, unknown>): void {
if (level === "warn") this.messageApi.warning(message);
@@ -70,7 +72,9 @@ class BaseLogger implements Logger {
}
class ConsoleSink implements Sink {
constructor(private isProduction: boolean) {}
constructor(private isProduction: boolean) {
// 仅存储配置,无需初始化操作
}
write(level: LogLevel, message: string, data: unknown, bindings: Record<string, unknown>): void {
if (this.isProduction && LEVEL_ORDER[level] < LEVEL_ORDER.warn) return;
@@ -85,23 +89,31 @@ class ConsoleSink implements Sink {
}
}
/* eslint-disable @typescript-eslint/no-empty-function */
class NoopLogger implements Logger {
child(_bindings: Record<string, unknown>): Logger {
return this;
}
debug(_message: string, _data?: unknown): void {}
debug(_message: string, _data?: unknown): void {
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
}
error(_message: string, _data?: unknown): void {}
error(_message: string, _data?: unknown): void {
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
}
info(_message: string, _data?: unknown): void {}
info(_message: string, _data?: unknown): void {
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
}
setLevel(_level: LogLevel): void {}
setLevel(_level: LogLevel): void {
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
}
warn(_message: string, _data?: unknown): void {}
warn(_message: string, _data?: unknown): void {
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
}
}
/* eslint-enable @typescript-eslint/no-empty-function */
export class MemoryLogger implements Logger {
entries: Array<{ data?: unknown; level: LogLevel; message: string }> = [];
@@ -123,8 +135,9 @@ export class MemoryLogger implements Logger {
this.capture("info", message, data);
}
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
setLevel(_level: LogLevel): void {}
setLevel(_level: LogLevel): void {
// MemoryLogger.setLevel 为接口兼容,无需实际过滤
}
warn(message: string, data?: unknown): void {
this.capture("warn", message, data);