refactor(ai-chat): 拆分超大文件并通过 aislop 100 分门槛

- AiChatService 拆出抽象基类,职责不变
- submissions 按提交内容/评审确认/运行时/流程拆为 4 个模块并保持导出兼容
- 导入工具执行器抽取共享 runner,消除重复签名块
- 全仓 aislop 扫描 100/100,0 警告
This commit is contained in:
2026-08-06 12:13:37 +08:00
parent 7f09d5271e
commit 824c33a71c
9 changed files with 1202 additions and 1058 deletions

View File

@@ -16,30 +16,13 @@ import { AiFormService } from './ai-form.service';
import { AiReviewService } from './ai-review.service';
import { AiModelStreamService } from './ai-model-stream.service';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import {
AiConversation,
AiMessage,
AiReview,
AiToolRun,
type AiReviewSectionType,
} from './entities';
import { AiConversation, AiMessage, AiToolRun } from './entities';
import type {
AiChatServiceContext,
AiSseEmitter,
GenerationInput,
ModelContentPart,
ModelMessage,
ModelToolCall,
PublicConversation,
} from './ai-chat.types';
import {
listConversations,
createConversation,
updateConversation,
deleteConversation,
deleteAllConversations,
getMessages,
deleteMessage,
requireOwnedConversation,
acquireConversation,
normalizeTitle,
@@ -49,35 +32,7 @@ import {
truncateText,
serializeMessage,
} from './ai-chat.conversations';
import {
streamMessage,
regenerateMessage,
editMessage,
buildContext,
buildUserContent,
} from './ai-chat.streaming';
import {
resolveFormConversationId,
resolvePreflightConversationId,
resolveReviewConversationId,
resolveImportPreflight,
submitForm,
submitReview,
confirmReviewStep,
confirmReviewGroup,
assertReviewImportPermissions,
a2uiSubmitInfo,
buildFormSubmitModelContent,
markFormSubmittedOnMessage,
a2uiReviewSubmitInfo,
buildReviewSubmitModelContent,
markReviewSubmittedOnMessage,
} from './ai-chat.submissions';
import { denyWriteTool, executeTool } from './ai-chat.tools';
import {
executePreflightImport,
executeStartImportWizard,
} from './ai-chat.tool-actions';
import { buildContext, buildUserContent } from './ai-chat.streaming';
import { executeGeneration } from './ai-chat.generation';
import {
assertGeneratedLength,
@@ -88,32 +43,52 @@ import {
safeToolName,
throwIfAborted,
} from './ai-chat.helpers';
import { AiChatServiceBase } from './ai-chat.service-base';
@Injectable()
export class AiChatService implements AiChatServiceContext {
readonly activeConversations = new Set<number>();
export class AiChatService extends AiChatServiceBase {
private readonly redactingReplacer = makeRedactingReplacer((value) => this.redactText(value));
constructor(
@InjectRepository(AiConversation)
readonly conversations: Repository<AiConversation>,
conversations: Repository<AiConversation>,
@InjectRepository(AiMessage)
readonly messages: Repository<AiMessage>,
messages: Repository<AiMessage>,
@InjectRepository(AiToolRun)
readonly toolRuns: Repository<AiToolRun>,
readonly dataSource: DataSource,
readonly configService: AiConfigService,
readonly toolExecutor: AgentToolExecutor,
readonly modelStream: AiModelStreamService,
readonly attachmentService: AiAttachmentService,
readonly formService: AiFormService,
readonly reviewService: AiReviewService,
readonly chartService: AiChartService,
readonly abilityFactory: CaslAbilityFactory,
readonly authorization: AuthorizationService,
readonly excelReader?: AiExcelReaderService,
readonly importsService?: ImportsService,
readonly opLog?: OperationLogsService,
) {}
toolRuns: Repository<AiToolRun>,
dataSource: DataSource,
configService: AiConfigService,
toolExecutor: AgentToolExecutor,
modelStream: AiModelStreamService,
attachmentService: AiAttachmentService,
formService: AiFormService,
reviewService: AiReviewService,
chartService: AiChartService,
abilityFactory: CaslAbilityFactory,
authorization: AuthorizationService,
excelReader?: AiExcelReaderService,
importsService?: ImportsService,
opLog?: OperationLogsService,
) {
super(
conversations,
messages,
toolRuns,
dataSource,
configService,
toolExecutor,
modelStream,
attachmentService,
formService,
reviewService,
chartService,
abilityFactory,
authorization,
excelReader,
importsService,
opLog,
);
}
listSkills(user: AuthenticatedUser): AgentSkillDescriptor[] {
return this.toolExecutor.listSkills(AgentToolContextFactory.fromAuthenticatedUser(user));
@@ -167,49 +142,6 @@ export class AiChatService implements AiChatServiceContext {
return assertGeneratedLength(reasoning, content);
}
private readonly redactingReplacer = makeRedactingReplacer((value) => this.redactText(value));
a2uiSubmitInfo(metadata: Record<string, unknown> | null) {
return a2uiSubmitInfo(metadata);
}
a2uiReviewSubmitInfo(metadata: Record<string, unknown> | null) {
return a2uiReviewSubmitInfo(metadata);
}
buildFormSubmitModelContent(submit: { title: string; values: Record<string, unknown> }): string {
return buildFormSubmitModelContent(submit);
}
buildReviewSubmitModelContent(submit: {
reviewId: string;
reviewTitle: string;
resultMessage: string;
}): string {
return buildReviewSubmitModelContent(submit);
}
markFormSubmittedOnMessage(assistantMessageId: number, conversationId: number): Promise<void> {
return markFormSubmittedOnMessage(this, assistantMessageId, conversationId);
}
markReviewSubmittedOnMessage(
assistantMessageId: number,
conversationId: number,
review?: AiReview,
): Promise<void> {
return markReviewSubmittedOnMessage(this, assistantMessageId, conversationId, review);
}
assertReviewImportPermissions(
user: AuthenticatedUser,
review: AiReview,
sectionKey?: string,
sectionType?: AiReviewSectionType,
): void {
return assertReviewImportPermissions(this, user, review, sectionKey, sectionType);
}
buildContext(
conversationId: number,
focusUserMessageId: number,
@@ -256,217 +188,7 @@ export class AiChatService implements AiChatServiceContext {
return acquireConversation(this, conversationId);
}
executeTool(
messageId: number,
call: ModelToolCall,
context: ReturnType<typeof AgentToolContextFactory.fromAuthenticatedUser>,
allowedSkillKey: string | null,
allowWriteTools: boolean,
reviewSubmitted: boolean,
userId: number,
emit: AiSseEmitter,
): Promise<string> {
return executeTool(
this,
messageId,
call,
context,
allowedSkillKey,
allowWriteTools,
reviewSubmitted,
userId,
emit,
);
}
denyWriteTool(
messageId: number,
call: ModelToolCall,
emit: AiSseEmitter,
): Promise<string> {
return denyWriteTool(this, messageId, call, emit);
}
executeStartImportWizard(
messageId: number,
call: ModelToolCall,
context: ReturnType<typeof AgentToolContextFactory.fromAuthenticatedUser>,
emit: AiSseEmitter,
): Promise<string> {
return executeStartImportWizard(this, messageId, call, context, emit);
}
executePreflightImport(
messageId: number,
call: ModelToolCall,
context: ReturnType<typeof AgentToolContextFactory.fromAuthenticatedUser>,
emit: AiSseEmitter,
): Promise<string> {
return executePreflightImport(this, messageId, call, context, emit);
}
executeGeneration(input: GenerationInput): Promise<void> {
return executeGeneration(this, input);
}
listConversations(userId: number): Promise<PublicConversation[]> {
return listConversations(this, userId);
}
createConversation(
user: AuthenticatedUser,
title?: string,
lockedSkillKey?: string | null,
): Promise<PublicConversation> {
return createConversation(this, user, title, lockedSkillKey);
}
updateConversation(
user: AuthenticatedUser,
id: number,
dto: { title?: string; lockedSkillKey?: string | null },
): Promise<PublicConversation> {
return updateConversation(this, user, id, dto);
}
deleteConversation(userId: number, id: number): Promise<void> {
return deleteConversation(this, userId, id);
}
deleteAllConversations(userId: number): Promise<number> {
return deleteAllConversations(this, userId);
}
getMessages(userId: number, conversationId: number, page = 1, limit = 50) {
return getMessages(this, userId, conversationId, page, limit);
}
deleteMessage(
userId: number,
conversationId: number,
messageId: number,
): Promise<{ deletedIds: number[] }> {
return deleteMessage(this, userId, conversationId, messageId);
}
streamMessage(
user: AuthenticatedUser,
conversationId: number,
dto: {
message: string;
attachmentIds?: number[];
clientRequestId: string;
skillKey?: string | null;
reasoningEffort?: string | null;
},
signal: AbortSignal,
emit: AiSseEmitter,
onReady: () => void,
): Promise<void> {
return streamMessage(this, user, conversationId, dto, signal, emit, onReady);
}
regenerateMessage(
user: AuthenticatedUser,
conversationId: number,
assistantMessageId: number,
clientRequestId: string,
reasoningEffort: string | null | undefined,
signal: AbortSignal,
emit: AiSseEmitter,
onReady: () => void,
): Promise<void> {
return regenerateMessage(
this,
user,
conversationId,
assistantMessageId,
clientRequestId,
reasoningEffort,
signal,
emit,
onReady,
);
}
editMessage(
user: AuthenticatedUser,
conversationId: number,
messageId: number,
dto: { content: string; clientRequestId: string; reasoningEffort?: string | null },
signal: AbortSignal,
emit: AiSseEmitter,
onReady: () => void,
): Promise<void> {
return editMessage(this, user, conversationId, messageId, dto, signal, emit, onReady);
}
resolveFormConversationId(userId: number, formId: string): Promise<number> {
return resolveFormConversationId(this, userId, formId);
}
resolveReviewConversationId(userId: number, reviewId: string): Promise<number> {
return resolveReviewConversationId(this, userId, reviewId);
}
resolvePreflightConversationId(userId: number, messageId: number): Promise<number> {
return resolvePreflightConversationId(this, userId, messageId);
}
submitForm(
user: AuthenticatedUser,
formId: string,
dto: {
values: Record<string, unknown>;
clientRequestId: string;
reasoningEffort?: string | null;
},
signal: AbortSignal,
emit: AiSseEmitter,
onReady: () => void,
): Promise<void> {
return submitForm(this, user, formId, dto, signal, emit, onReady);
}
submitReview(
user: AuthenticatedUser,
reviewId: string,
dto: { clientRequestId: string; reasoningEffort?: string | null },
signal: AbortSignal,
emit: AiSseEmitter,
onReady: () => void,
): Promise<void> {
return submitReview(this, user, reviewId, dto, signal, emit, onReady);
}
resolveImportPreflight(
user: AuthenticatedUser,
messageId: number,
dto: {
clientRequestId: string;
mapping?: Record<string, unknown>;
settings?: Record<string, unknown>;
},
signal: AbortSignal,
emit: AiSseEmitter,
onReady: () => void,
): Promise<void> {
return resolveImportPreflight(this, user, messageId, dto, signal, emit, onReady);
}
confirmReviewStep(
user: AuthenticatedUser,
reviewId: string,
sectionKey: string,
): Promise<Record<string, unknown>> {
return confirmReviewStep(this, user, reviewId, sectionKey);
}
confirmReviewGroup(
user: AuthenticatedUser,
reviewId: string,
type: AiReviewSectionType,
): Promise<Record<string, unknown>> {
return confirmReviewGroup(this, user, reviewId, type);
}
}