import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, Repository } from 'typeorm'; import { AiConfigService } from '../ai-config/ai-config.service'; import { AgentToolExecutor } from '../agent-tools/agent-tool.executor'; import { AgentToolContextFactory, type AgentSkillDescriptor, } from '../agent-tools/agent-tool.types'; import { AuthorizationService, CaslAbilityFactory, type AuthenticatedUser } from '../authorization'; import { AiAttachmentService } from './ai-attachment.service'; import { ImportsService } from '../imports/imports.service'; import { AiChartService } from './ai-chart.service'; import { AiExcelReaderService } from './ai-excel-reader.service'; import { AiFormService } from './ai-form.service'; import { AiReviewService } from './ai-review.service'; import { A2uiSubmissionsService } from './ai-a2ui-submissions.service'; import { AiModelStreamService } from './ai-model-stream.service'; import { OperationLogsService } from '../operation-logs/operation-logs.service'; import { AiConversation, AiMessage, AiToolRun } from './entities'; import type { GenerationInput, ModelContentPart, ModelMessage, } from './ai-chat.types'; import { requireOwnedConversation, acquireConversation, normalizeTitle, titleFromMessage, metadataSkillKey, assertSkillAvailable, truncateText, serializeMessage, } from './ai-chat.conversations'; import { buildContext, buildUserContent } from './ai-chat.streaming'; import { executeGeneration } from './ai-chat.generation'; import { assertGeneratedLength, errorCode, makeRedactingReplacer, parseToolArguments, redactText, safeToolName, throwIfAborted, } from './ai-chat.helpers'; import { AiChatServiceBase } from './ai-chat.service-base'; @Injectable() export class AiChatService extends AiChatServiceBase { private readonly redactingReplacer = makeRedactingReplacer((value) => this.redactText(value)); constructor( @InjectRepository(AiConversation) conversations: Repository, @InjectRepository(AiMessage) messages: Repository, @InjectRepository(AiToolRun) toolRuns: Repository, dataSource: DataSource, configService: AiConfigService, toolExecutor: AgentToolExecutor, modelStream: AiModelStreamService, attachmentService: AiAttachmentService, formService: AiFormService, reviewService: AiReviewService, chartService: AiChartService, abilityFactory: CaslAbilityFactory, authorization: AuthorizationService, a2uiSubmissions?: A2uiSubmissionsService, excelReader?: AiExcelReaderService, importsService?: ImportsService, opLog?: OperationLogsService, ) { super( conversations, messages, toolRuns, dataSource, configService, toolExecutor, modelStream, attachmentService, formService, reviewService, chartService, abilityFactory, authorization, a2uiSubmissions, excelReader, importsService, opLog, ); } listSkills(user: AuthenticatedUser): AgentSkillDescriptor[] { return this.toolExecutor.listSkills(AgentToolContextFactory.fromAuthenticatedUser(user)); } serializeMessage(message: AiMessage): Record { return serializeMessage(this, message); } redactText(value: string): string { return redactText(value); } summarize(value: unknown): string | null { if (value === undefined || value === null) return null; let json: string; try { json = JSON.stringify(value, this.redactingReplacer); } catch { return '[无法序列化]'; } return this.redactText(json).slice(0, 2000); } safeStructured(value: unknown): unknown { if (value === undefined || value === null) return null; try { return JSON.parse(JSON.stringify(value, this.redactingReplacer)) as unknown; } catch { return null; } } parseToolArguments(value: string): unknown { return parseToolArguments(value); } safeToolName(name: string): string { return safeToolName(name); } throwIfAborted(signal: AbortSignal): void { return throwIfAborted(signal); } errorCode(error: unknown): string { return errorCode(error); } assertGeneratedLength(reasoning: string, content: string): void { return assertGeneratedLength(reasoning, content); } buildContext( conversationId: number, focusUserMessageId: number, focusContent: string | ModelContentPart[], skillKey: string | null, supportsVision: boolean, ): Promise { return buildContext(this, conversationId, focusUserMessageId, focusContent, skillKey, supportsVision); } buildUserContent( text: string, attachments: any[], supportsVision: boolean, ): Promise { return buildUserContent(this, text, attachments, supportsVision); } truncateText(value: string, max: number): string { return truncateText(this, value, max); } metadataSkillKey(metadata: Record | null): string | null { return metadataSkillKey(this, metadata); } normalizeTitle(title?: string): string { return normalizeTitle(this, title); } titleFromMessage(message: string): string { return titleFromMessage(this, message); } assertSkillAvailable(user: AuthenticatedUser, skillKey?: string | null): void { return assertSkillAvailable(this, user, skillKey); } requireOwnedConversation(userId: number, id: number): Promise { return requireOwnedConversation(this, userId, id); } acquireConversation(conversationId: number): Promise { return acquireConversation(this, conversationId); } executeGeneration(input: GenerationInput): Promise { return executeGeneration(this, input); } }