feat(ai): introduce deep reading functionality and refactor AI module
- Added `AiDeepReadingController`, `AiDeepReadingService`, and related DTOs and models to support deep reading capabilities using AI. - Integrated new methods for generating and managing deep readings, including error handling and caching mechanisms. - Updated `AiModule` to include dependencies for deep reading services and controllers. - Removed the obsolete `AiAgentModule` and its associated test controller. - Enhanced configuration options to enable or disable deep reading features. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
5
apps/core/global.d.ts
vendored
5
apps/core/global.d.ts
vendored
@@ -1,8 +1,5 @@
|
|||||||
import type { Document, PaginateModel } from 'mongoose'
|
|
||||||
|
|
||||||
import '@mx-space/compiled/zx-global'
|
|
||||||
|
|
||||||
import type { ModelType } from '@typegoose/typegoose/lib/types'
|
import type { ModelType } from '@typegoose/typegoose/lib/types'
|
||||||
|
import type { Document, PaginateModel } from 'mongoose'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
export type KV<T = any> = Record<string, T>
|
export type KV<T = any> = Record<string, T>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { AxiosRequestConfig } from 'axios'
|
import type { AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
|
import { argv } from '@mx-space/compiled'
|
||||||
|
|
||||||
export const PORT = process.env.PORT || 2333
|
export const PORT = process.env.PORT || 2333
|
||||||
export const API_VERSION = 2
|
export const API_VERSION = 2
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { LogLevel } from '@nestjs/common'
|
|||||||
import type { NestFastifyApplication } from '@nestjs/platform-fastify'
|
import type { NestFastifyApplication } from '@nestjs/platform-fastify'
|
||||||
|
|
||||||
import { Logger } from '@innei/pretty-logger-nestjs'
|
import { Logger } from '@innei/pretty-logger-nestjs'
|
||||||
|
import { chalk } from '@mx-space/compiled'
|
||||||
import { NestFactory } from '@nestjs/core'
|
import { NestFactory } from '@nestjs/core'
|
||||||
|
|
||||||
import { CROSS_DOMAIN, DEBUG_MODE, PORT } from './app.config'
|
import { CROSS_DOMAIN, DEBUG_MODE, PORT } from './app.config'
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { ErrorCode, ErrorCodeEnum } from '~/constants/error-code.constant'
|
|||||||
|
|
||||||
export class BusinessException extends HttpException {
|
export class BusinessException extends HttpException {
|
||||||
public bizCode: ErrorCodeEnum
|
public bizCode: ErrorCodeEnum
|
||||||
constructor(code: ErrorCodeEnum, extraMessage?: string)
|
constructor(code: ErrorCodeEnum, extraMessage?: string, trace?: string)
|
||||||
constructor(message: string)
|
constructor(message: string)
|
||||||
constructor(...args: any[]) {
|
constructor(...args: any[]) {
|
||||||
let status = 500
|
let status = 500
|
||||||
const [bizCode, extraMessage] = args as any
|
const [bizCode, extraMessage, trace] = args as any
|
||||||
const bizError = ErrorCode[bizCode] || []
|
const bizError = ErrorCode[bizCode] || []
|
||||||
const [message] = bizError
|
const [message] = bizError
|
||||||
status = bizError[1] ?? status
|
status = bizError[1] ?? status
|
||||||
@@ -27,6 +27,8 @@ export class BusinessException extends HttpException {
|
|||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.stack = trace
|
||||||
|
|
||||||
this.bizCode = typeof bizCode === 'number' ? bizCode : ErrorCodeEnum.Default
|
this.bizCode = typeof bizCode === 'number' ? bizCode : ErrorCodeEnum.Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { createWriteStream } from 'node:fs'
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common'
|
import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common'
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify'
|
import type { FastifyReply, FastifyRequest } from 'fastify'
|
||||||
import type { WriteStream } from 'node:fs'
|
import type { WriteStream } from 'node:fs'
|
||||||
|
|
||||||
|
import { chalk } from '@mx-space/compiled'
|
||||||
import {
|
import {
|
||||||
Catch,
|
Catch,
|
||||||
HttpException,
|
HttpException,
|
||||||
@@ -122,7 +124,7 @@ export class AllExceptionsFilter implements ExceptionFilter {
|
|||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
this.errorLogPipe =
|
this.errorLogPipe =
|
||||||
this.errorLogPipe ??
|
this.errorLogPipe ??
|
||||||
fs.createWriteStream(resolve(LOG_DIR, 'error.log'), {
|
createWriteStream(resolve(LOG_DIR, 'error.log'), {
|
||||||
flags: 'a+',
|
flags: 'a+',
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
} from '@nestjs/common'
|
} from '@nestjs/common'
|
||||||
import type { Observable } from 'rxjs'
|
import type { Observable } from 'rxjs'
|
||||||
|
|
||||||
|
import { chalk } from '@mx-space/compiled'
|
||||||
import { Injectable, Logger, SetMetadata } from '@nestjs/common'
|
import { Injectable, Logger, SetMetadata } from '@nestjs/common'
|
||||||
|
|
||||||
import { HTTP_REQUEST_TIME } from '~/constants/meta.constant'
|
import { HTTP_REQUEST_TIME } from '~/constants/meta.constant'
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const RECENTLY_COLLECTION_NAME = 'recentlies'
|
|||||||
export const ANALYZE_COLLECTION_NAME = 'analyzes'
|
export const ANALYZE_COLLECTION_NAME = 'analyzes'
|
||||||
export const WEBHOOK_EVENT_COLLECTION_NAME = 'webhook_events'
|
export const WEBHOOK_EVENT_COLLECTION_NAME = 'webhook_events'
|
||||||
export const AI_SUMMARY_COLLECTION_NAME = 'ai_summaries'
|
export const AI_SUMMARY_COLLECTION_NAME = 'ai_summaries'
|
||||||
|
export const AI_DEEP_READING_COLLECTION_NAME = 'ai_deep_readings'
|
||||||
|
|
||||||
export const USER_COLLECTION_NAME = 'users'
|
export const USER_COLLECTION_NAME = 'users'
|
||||||
export enum CollectionRefTypes {
|
export enum CollectionRefTypes {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import cluster from 'node:cluster'
|
import cluster from 'node:cluster'
|
||||||
import { mkdirSync, writeFileSync } from 'node:fs'
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
||||||
|
|
||||||
import { Logger } from '@nestjs/common'
|
import { Logger } from '@nestjs/common'
|
||||||
|
|
||||||
@@ -18,7 +18,8 @@ import { cwd, isDev } from './env.global'
|
|||||||
import { registerJSONGlobal } from './json.global'
|
import { registerJSONGlobal } from './json.global'
|
||||||
|
|
||||||
import './dayjs.global'
|
import './dayjs.global'
|
||||||
import '@mx-space/compiled/zx-global'
|
|
||||||
|
import { $, chalk } from '@mx-space/compiled'
|
||||||
|
|
||||||
// 建立目录
|
// 建立目录
|
||||||
function createAppFolders() {
|
function createAppFolders() {
|
||||||
@@ -37,7 +38,7 @@ function createAppFolders() {
|
|||||||
Logger.log(chalk.blue(`文件回收站目录已经建好:${STATIC_FILE_TRASH_DIR}`))
|
Logger.log(chalk.blue(`文件回收站目录已经建好:${STATIC_FILE_TRASH_DIR}`))
|
||||||
|
|
||||||
const packageJSON = `${DATA_DIR}/package.json`
|
const packageJSON = `${DATA_DIR}/package.json`
|
||||||
const hasPKG = fs.existsSync(packageJSON)
|
const hasPKG = existsSync(packageJSON)
|
||||||
if (!hasPKG) {
|
if (!hasPKG) {
|
||||||
writeFileSync(packageJSON, '{"name":"modules"}', {
|
writeFileSync(packageJSON, '{"name":"modules"}', {
|
||||||
flag: 'a',
|
flag: 'a',
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!env node
|
#!env node
|
||||||
// register global
|
// register global
|
||||||
import cluster from 'node:cluster'
|
import cluster from 'node:cluster'
|
||||||
|
import { cpus } from 'node:os'
|
||||||
|
|
||||||
|
import { argv } from '@mx-space/compiled'
|
||||||
|
|
||||||
import { DEBUG_MODE } from './app.config'
|
import { DEBUG_MODE } from './app.config'
|
||||||
import { registerForMemoryDump } from './dump'
|
import { registerForMemoryDump } from './dump'
|
||||||
@@ -51,7 +54,7 @@ async function main() {
|
|||||||
DEBUG_MODE.memoryDump && registerForMemoryDump()
|
DEBUG_MODE.memoryDump && registerForMemoryDump()
|
||||||
if (CLUSTER.enable) {
|
if (CLUSTER.enable) {
|
||||||
Cluster.register(
|
Cluster.register(
|
||||||
Number.parseInt(CLUSTER.workers) || os.cpus().length,
|
Number.parseInt(CLUSTER.workers) || cpus().length,
|
||||||
bootstrap,
|
bootstrap,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { forwardRef, Module } from '@nestjs/common'
|
|
||||||
|
|
||||||
import { McpModule } from '../../mcp/mcp.module'
|
|
||||||
import { AiModule } from '../ai.module'
|
|
||||||
import { AIAgentService } from './ai-agent.service'
|
|
||||||
import { TestController } from './test.controller'
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [McpModule, forwardRef(() => AiModule)],
|
|
||||||
providers: [AIAgentService],
|
|
||||||
controllers: isDev ? [TestController] : [],
|
|
||||||
})
|
|
||||||
export class AiAgentModule {}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Controller, Get } from '@nestjs/common'
|
|
||||||
|
|
||||||
import { AIAgentService } from './ai-agent.service'
|
|
||||||
|
|
||||||
@Controller('ai/test')
|
|
||||||
export class TestController {
|
|
||||||
constructor(private readonly aiAgentService: AIAgentService) {}
|
|
||||||
|
|
||||||
@Get('/')
|
|
||||||
async test() {
|
|
||||||
return this.aiAgentService.runWithTools(
|
|
||||||
'Posts Id: 65e99e28eb677674816310c7 主要了写了什么内容',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Delete, Get, Param, Post, Query } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { ApiController } from '~/common/decorators/api-controller.decorator'
|
||||||
|
import { Auth } from '~/common/decorators/auth.decorator'
|
||||||
|
import { BizException } from '~/common/exceptions/biz.exception'
|
||||||
|
import { ErrorCodeEnum } from '~/constants/error-code.constant'
|
||||||
|
import { MongoIdDto } from '~/shared/dto/id.dto'
|
||||||
|
import { PagerDto } from '~/shared/dto/pager.dto'
|
||||||
|
|
||||||
|
import { ConfigsService } from '../../configs/configs.service'
|
||||||
|
import { GetDeepReadingQueryDto } from './ai-deep-reading.dto'
|
||||||
|
import { AiDeepReadingService } from './ai-deep-reading.service'
|
||||||
|
|
||||||
|
@ApiController('ai/deep-readings')
|
||||||
|
export class AiDeepReadingController {
|
||||||
|
constructor(
|
||||||
|
private readonly service: AiDeepReadingService,
|
||||||
|
private readonly configService: ConfigsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('/')
|
||||||
|
@Auth()
|
||||||
|
async getDeepReadings(@Query() query: PagerDto) {
|
||||||
|
return this.service.getAllDeepReadings(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/generate/:id')
|
||||||
|
@Auth()
|
||||||
|
async generateDeepReading(@Param() params: MongoIdDto) {
|
||||||
|
return this.service.generateDeepReadingByOpenAI(params.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/:id')
|
||||||
|
@Auth()
|
||||||
|
async deleteDeepReading(@Param() params: MongoIdDto) {
|
||||||
|
return this.service.deleteDeepReadingInDb(params.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/article/:id')
|
||||||
|
async getArticleDeepReading(
|
||||||
|
@Param() params: MongoIdDto,
|
||||||
|
@Query() query: GetDeepReadingQueryDto,
|
||||||
|
) {
|
||||||
|
const dbStored = await this.service.getDeepReadingByArticleId(params.id)
|
||||||
|
|
||||||
|
const aiConfig = await this.configService.get('ai')
|
||||||
|
if (!dbStored && !query.onlyDb) {
|
||||||
|
const shouldGenerate = aiConfig?.enableDeepReading
|
||||||
|
if (shouldGenerate) {
|
||||||
|
return this.service.generateDeepReadingByOpenAI(params.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dbStored && !aiConfig.enableDeepReading) {
|
||||||
|
throw new BizException(ErrorCodeEnum.AINotEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbStored
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { IsBoolean, IsOptional, IsString } from 'class-validator'
|
||||||
|
|
||||||
|
import { TransformBoolean } from '~/common/decorators/transform-boolean.decorator'
|
||||||
|
|
||||||
|
class BaseLangQueryDto {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
lang: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GenerateAiDeepReadingDto extends BaseLangQueryDto {
|
||||||
|
@IsString()
|
||||||
|
refId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetDeepReadingQueryDto extends BaseLangQueryDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
@TransformBoolean()
|
||||||
|
onlyDb?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateDeepReadingDto {
|
||||||
|
@IsString()
|
||||||
|
deepReading: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
criticalAnalysis?: string
|
||||||
|
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
keyPoints?: string[]
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import mongoose from 'mongoose'
|
||||||
|
|
||||||
|
import { modelOptions, prop } from '@typegoose/typegoose'
|
||||||
|
|
||||||
|
import { AI_DEEP_READING_COLLECTION_NAME } from '~/constants/db.constant'
|
||||||
|
import { BaseModel } from '~/shared/model/base.model'
|
||||||
|
|
||||||
|
@modelOptions({
|
||||||
|
options: {
|
||||||
|
customName: AI_DEEP_READING_COLLECTION_NAME,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class AIDeepReadingModel extends BaseModel {
|
||||||
|
@prop({
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
hash: string
|
||||||
|
|
||||||
|
@prop({
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
refId: string
|
||||||
|
|
||||||
|
@prop({ type: [String] })
|
||||||
|
keyPoints?: mongoose.Types.Array<string>
|
||||||
|
|
||||||
|
@prop()
|
||||||
|
criticalAnalysis?: string
|
||||||
|
|
||||||
|
@prop()
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
import { AgentExecutor, createOpenAIToolsAgent } from 'langchain/agents'
|
||||||
|
import { StructuredOutputParser } from 'langchain/output_parsers'
|
||||||
|
import type { PagerDto } from '~/shared/dto/pager.dto'
|
||||||
|
|
||||||
|
import { ToolDefinition } from '@langchain/core/language_models/base'
|
||||||
|
import { JsonOutputParser } from '@langchain/core/output_parsers'
|
||||||
|
import { JsonOutputToolsParser } from '@langchain/core/output_parsers/openai_tools'
|
||||||
|
import {
|
||||||
|
ChatPromptTemplate,
|
||||||
|
HumanMessagePromptTemplate,
|
||||||
|
MessagesPlaceholder,
|
||||||
|
SystemMessagePromptTemplate,
|
||||||
|
} from '@langchain/core/prompts'
|
||||||
|
import { DynamicStructuredTool } from '@langchain/core/tools'
|
||||||
|
import { z } from '@mx-space/compiled/zod'
|
||||||
|
import { Injectable, Logger } from '@nestjs/common'
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter'
|
||||||
|
|
||||||
|
import { BizException } from '~/common/exceptions/biz.exception'
|
||||||
|
import { CollectionRefTypes } from '~/constants/db.constant'
|
||||||
|
import { ErrorCodeEnum } from '~/constants/error-code.constant'
|
||||||
|
import { DatabaseService } from '~/processors/database/database.service'
|
||||||
|
import { RedisService } from '~/processors/redis/redis.service'
|
||||||
|
import { InjectModel } from '~/transformers/model.transformer'
|
||||||
|
import { md5 } from '~/utils/tool.util'
|
||||||
|
|
||||||
|
import { ConfigsService } from '../../configs/configs.service'
|
||||||
|
import { AiService } from '../ai.service'
|
||||||
|
import { AIDeepReadingModel } from './ai-deep-reading.model'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AiDeepReadingService {
|
||||||
|
private readonly logger: Logger
|
||||||
|
constructor(
|
||||||
|
@InjectModel(AIDeepReadingModel)
|
||||||
|
private readonly aiDeepReadingModel: MongooseModel<AIDeepReadingModel>,
|
||||||
|
private readonly databaseService: DatabaseService,
|
||||||
|
private readonly configService: ConfigsService,
|
||||||
|
|
||||||
|
private readonly redisService: RedisService,
|
||||||
|
private readonly aiService: AiService,
|
||||||
|
) {
|
||||||
|
this.logger = new Logger(AiDeepReadingService.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private cachedTaskId2AiPromise = new Map<string, Promise<any>>()
|
||||||
|
|
||||||
|
private async deepReadingAgentChain(articleId: string) {
|
||||||
|
const {
|
||||||
|
ai: { enableDeepReading },
|
||||||
|
} = await this.configService.waitForConfigReady()
|
||||||
|
|
||||||
|
if (!enableDeepReading) {
|
||||||
|
throw new BizException(ErrorCodeEnum.AINotEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
const article = await this.databaseService.findGlobalById(articleId)
|
||||||
|
if (!article || article.type === CollectionRefTypes.Recently) {
|
||||||
|
throw new BizException(ErrorCodeEnum.ContentNotFoundCantProcess)
|
||||||
|
}
|
||||||
|
|
||||||
|
const llm = await this.aiService.getOpenAiChain({
|
||||||
|
maxTokens: 8192,
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataModel = {
|
||||||
|
keyPoints: [] as string[],
|
||||||
|
criticalAnalysis: '',
|
||||||
|
content: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建分析文章的工具
|
||||||
|
const tools = [
|
||||||
|
new DynamicStructuredTool({
|
||||||
|
name: 'deep_reading',
|
||||||
|
description: `获取深度阅读内容`,
|
||||||
|
schema: z.object({}),
|
||||||
|
func: async () => {
|
||||||
|
const llm = await this.aiService.getOpenAiChain({
|
||||||
|
maxTokens: 1024 * 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await llm
|
||||||
|
.bind({
|
||||||
|
tool_choice: {
|
||||||
|
type: 'function',
|
||||||
|
function: { name: 'deep_reading' },
|
||||||
|
},
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: 'deep_reading',
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'deep_reading',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
content: {
|
||||||
|
type: 'string',
|
||||||
|
description: '深度阅读内容',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['content'],
|
||||||
|
},
|
||||||
|
description: `创建一个全面的深度阅读Markdown文本,保持文章的原始结构但提供扩展的解释和见解。
|
||||||
|
内容应该:
|
||||||
|
1. 遵循原文的流程和主要论点
|
||||||
|
2. 包含原文的所有关键技术细节
|
||||||
|
3. 扩展未充分解释的复杂概念
|
||||||
|
4. 在需要的地方提供额外背景和解释
|
||||||
|
5. 保持文章的原始语调和语言风格
|
||||||
|
6. 使用适当的Markdown格式,包括标题、代码块、列表等
|
||||||
|
7. 输出的语言必须与原文的语言匹配`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
.pipe(new JsonOutputToolsParser())
|
||||||
|
.invoke([
|
||||||
|
{
|
||||||
|
content: `分析以下文章:${article.document.text}\n\n创建一个全面的深度阅读Markdown文本,保持文章的原始结构但提供扩展的解释和见解。`,
|
||||||
|
role: 'system',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.then((result: any[]) => {
|
||||||
|
const content = result[0]?.args?.content
|
||||||
|
dataModel.content = content
|
||||||
|
return content
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new DynamicStructuredTool({
|
||||||
|
name: 'save_key_points',
|
||||||
|
description: '保存关键点到数据库',
|
||||||
|
schema: z.object({
|
||||||
|
keyPoints: z.array(z.string()).describe('关键点数组'),
|
||||||
|
}),
|
||||||
|
func: async (data: { keyPoints: string[] }) => {
|
||||||
|
dataModel.keyPoints = data.keyPoints
|
||||||
|
return '关键点已保存'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
new DynamicStructuredTool({
|
||||||
|
name: 'save_critical_analysis',
|
||||||
|
description: '保存批判性分析到数据库',
|
||||||
|
schema: z.object({
|
||||||
|
criticalAnalysis: z.string().describe('批判性分析'),
|
||||||
|
}),
|
||||||
|
func: async (data: { criticalAnalysis: string }) => {
|
||||||
|
dataModel.criticalAnalysis = data.criticalAnalysis
|
||||||
|
return '批判性分析已保存'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
// 创建Agent提示模板
|
||||||
|
const prompt = ChatPromptTemplate.fromMessages([
|
||||||
|
SystemMessagePromptTemplate.fromTemplate(
|
||||||
|
`你是一个专门进行文章深度阅读的AI助手,需要分析文章并提供详细的解读。
|
||||||
|
分析过程:
|
||||||
|
1. 首先提取文章关键点,然后使用 save_key_points 保存到数据库
|
||||||
|
2. 然后进行批判性分析,包括文章的优点、缺点和改进建议,然后使用 save_critical_analysis 保存到数据库
|
||||||
|
3. 最后使用 deep_reading 生成完整的深度阅读内容
|
||||||
|
4. 返回完整结果,包括关键点、批判性分析和深度阅读内容
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
new MessagesPlaceholder('chat_history'),
|
||||||
|
['human', '文章标题: {article_title}\n文章内容: {article_content}'],
|
||||||
|
new MessagesPlaceholder('agent_scratchpad'),
|
||||||
|
])
|
||||||
|
|
||||||
|
// 创建Agent
|
||||||
|
const agent = await createOpenAIToolsAgent({
|
||||||
|
llm,
|
||||||
|
tools,
|
||||||
|
prompt,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建Agent执行器
|
||||||
|
const executor = new AgentExecutor({
|
||||||
|
agent,
|
||||||
|
tools,
|
||||||
|
verbose: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 执行Agent
|
||||||
|
await executor.invoke({
|
||||||
|
article_title: article.document.title,
|
||||||
|
article_content: article.document.text,
|
||||||
|
chat_history: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
return {
|
||||||
|
keyPoints: dataModel.keyPoints,
|
||||||
|
criticalAnalysis: dataModel.criticalAnalysis,
|
||||||
|
content: dataModel.content,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Agent execution error: ${error.message}`)
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCodeEnum.AIException,
|
||||||
|
error.message,
|
||||||
|
error.stack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateDeepReadingByOpenAI(articleId: string) {
|
||||||
|
const {
|
||||||
|
ai: { enableDeepReading },
|
||||||
|
} = await this.configService.waitForConfigReady()
|
||||||
|
|
||||||
|
if (!enableDeepReading) {
|
||||||
|
throw new BizException(ErrorCodeEnum.AINotEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
const article = await this.databaseService.findGlobalById(articleId)
|
||||||
|
if (!article) {
|
||||||
|
throw new BizException(ErrorCodeEnum.ContentNotFoundCantProcess)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (article.type === CollectionRefTypes.Recently) {
|
||||||
|
throw new BizException(ErrorCodeEnum.ContentNotFoundCantProcess)
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskId = `ai:deepreading:${articleId}`
|
||||||
|
try {
|
||||||
|
if (this.cachedTaskId2AiPromise.has(taskId)) {
|
||||||
|
return this.cachedTaskId2AiPromise.get(taskId)
|
||||||
|
}
|
||||||
|
const redis = this.redisService.getClient()
|
||||||
|
|
||||||
|
const isProcessing = await redis.get(taskId)
|
||||||
|
|
||||||
|
if (isProcessing === 'processing' && !isDev) {
|
||||||
|
throw new BizException(ErrorCodeEnum.AIProcessing)
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskPromise = handle.bind(this)(
|
||||||
|
articleId,
|
||||||
|
article.document.text,
|
||||||
|
article.document.title,
|
||||||
|
) as Promise<any>
|
||||||
|
|
||||||
|
this.cachedTaskId2AiPromise.set(taskId, taskPromise)
|
||||||
|
return await taskPromise
|
||||||
|
|
||||||
|
async function handle(
|
||||||
|
this: AiDeepReadingService,
|
||||||
|
id: string,
|
||||||
|
text: string,
|
||||||
|
) {
|
||||||
|
// 处理时间增加到5分钟
|
||||||
|
await redis.set(taskId, 'processing', 'EX', 300)
|
||||||
|
|
||||||
|
const result = await this.deepReadingAgentChain(id)
|
||||||
|
|
||||||
|
await redis.del(taskId)
|
||||||
|
|
||||||
|
const contentMd5 = md5(text)
|
||||||
|
|
||||||
|
const doc = await this.aiDeepReadingModel.create({
|
||||||
|
hash: contentMd5,
|
||||||
|
refId: id,
|
||||||
|
keyPoints: result.keyPoints,
|
||||||
|
criticalAnalysis: result.criticalAnalysis,
|
||||||
|
content: result.content,
|
||||||
|
})
|
||||||
|
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
this.logger.error(
|
||||||
|
`OpenAI encountered an error processing article ${articleId}: ${error.message}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCodeEnum.AIException,
|
||||||
|
error.message,
|
||||||
|
error.stack,
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
this.cachedTaskId2AiPromise.delete(taskId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllDeepReadings(pager: PagerDto) {
|
||||||
|
const { page, size } = pager
|
||||||
|
const deepReadings = await this.aiDeepReadingModel.paginate(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
page,
|
||||||
|
limit: size,
|
||||||
|
sort: {
|
||||||
|
created: -1,
|
||||||
|
},
|
||||||
|
lean: true,
|
||||||
|
leanWithId: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return deepReadings
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDeepReadingByArticleId(articleId: string) {
|
||||||
|
const article = await this.databaseService.findGlobalById(articleId)
|
||||||
|
if (!article) {
|
||||||
|
throw new BizException(ErrorCodeEnum.ContentNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await this.aiDeepReadingModel.find({
|
||||||
|
refId: articleId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return docs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteDeepReadingByArticleId(articleId: string) {
|
||||||
|
await this.aiDeepReadingModel.deleteMany({
|
||||||
|
refId: articleId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteDeepReadingInDb(id: string) {
|
||||||
|
return this.aiDeepReadingModel.findByIdAndDelete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEvent('article.delete')
|
||||||
|
async handleDeleteArticle(event: { id: string }) {
|
||||||
|
await this.deleteDeepReadingByArticleId(event.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export const DEFAULT_SUMMARY_LANG = 'zh'
|
export const DEFAULT_SUMMARY_LANG = 'zh'
|
||||||
|
|
||||||
export const LANGUAGE_CODE_TO_NAME = {
|
export const LANGUAGE_CODE_TO_NAME = {
|
||||||
ar: 'Arabic',
|
ar: 'Arabic',
|
||||||
bg: 'Bulgarian',
|
bg: 'Bulgarian',
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { forwardRef, Module } from '@nestjs/common'
|
import { forwardRef, Module } from '@nestjs/common'
|
||||||
|
|
||||||
import { AiAgentModule } from './ai-agent/ai-agent.module'
|
import { McpModule } from '../mcp/mcp.module'
|
||||||
|
import { AIAgentService } from './ai-agent/ai-agent.service'
|
||||||
|
import { AiDeepReadingController } from './ai-deep-reading/ai-deep-reading.controller'
|
||||||
|
import { AiDeepReadingService } from './ai-deep-reading/ai-deep-reading.service'
|
||||||
import { AiSummaryController } from './ai-summary/ai-summary.controller'
|
import { AiSummaryController } from './ai-summary/ai-summary.controller'
|
||||||
import { AiSummaryService } from './ai-summary/ai-summary.service'
|
import { AiSummaryService } from './ai-summary/ai-summary.service'
|
||||||
import { AiWriterController } from './ai-writer/ai-writer.controller'
|
import { AiWriterController } from './ai-writer/ai-writer.controller'
|
||||||
@@ -8,9 +11,19 @@ import { AiWriterService } from './ai-writer/ai-writer.service'
|
|||||||
import { AiService } from './ai.service'
|
import { AiService } from './ai.service'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [forwardRef(() => AiAgentModule)],
|
imports: [forwardRef(() => McpModule)],
|
||||||
providers: [AiSummaryService, AiService, AiWriterService],
|
providers: [
|
||||||
controllers: [AiSummaryController, AiWriterController],
|
AiSummaryService,
|
||||||
|
AiService,
|
||||||
|
AiWriterService,
|
||||||
|
AiDeepReadingService,
|
||||||
|
AIAgentService,
|
||||||
|
],
|
||||||
|
controllers: [
|
||||||
|
AiSummaryController,
|
||||||
|
AiWriterController,
|
||||||
|
AiDeepReadingController,
|
||||||
|
],
|
||||||
exports: [AiService],
|
exports: [AiService],
|
||||||
})
|
})
|
||||||
export class AiModule {}
|
export class AiModule {}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ConfigsService } from '../configs/configs.service'
|
|||||||
export class AiService {
|
export class AiService {
|
||||||
constructor(private readonly configService: ConfigsService) {}
|
constructor(private readonly configService: ConfigsService) {}
|
||||||
|
|
||||||
public async getOpenAiChain() {
|
public async getOpenAiChain(options?: { maxTokens?: number }) {
|
||||||
const {
|
const {
|
||||||
ai: { openAiKey, openAiEndpoint, openAiPreferredModel },
|
ai: { openAiKey, openAiEndpoint, openAiPreferredModel },
|
||||||
} = await this.configService.waitForConfigReady()
|
} = await this.configService.waitForConfigReady()
|
||||||
@@ -24,6 +24,7 @@ export class AiService {
|
|||||||
configuration: {
|
configuration: {
|
||||||
baseURL: openAiEndpoint || void 0,
|
baseURL: openAiEndpoint || void 0,
|
||||||
},
|
},
|
||||||
|
maxTokens: options?.maxTokens,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { existsSync, statSync } from 'node:fs'
|
import { createReadStream, existsSync, statSync } from 'node:fs'
|
||||||
import { readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
import { readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||||
import { join, resolve } from 'node:path'
|
import path, { join, resolve } from 'node:path'
|
||||||
import { flatten } from 'lodash'
|
import { flatten } from 'lodash'
|
||||||
import { mkdirp } from 'mkdirp'
|
import { mkdirp } from 'mkdirp'
|
||||||
|
|
||||||
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||||
|
import { $, cd } from '@mx-space/compiled'
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Injectable,
|
Injectable,
|
||||||
@@ -148,7 +149,7 @@ export class BackupService {
|
|||||||
|
|
||||||
async getFileStream(dirname: string) {
|
async getFileStream(dirname: string) {
|
||||||
const path = this.checkBackupExist(dirname)
|
const path = this.checkBackupExist(dirname)
|
||||||
const stream = fs.createReadStream(path)
|
const stream = createReadStream(path)
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
@@ -179,7 +180,7 @@ export class BackupService {
|
|||||||
|
|
||||||
async restore(restoreFilePath: string) {
|
async restore(restoreFilePath: string) {
|
||||||
await this.backup()
|
await this.backup()
|
||||||
const isExist = fs.existsSync(restoreFilePath)
|
const isExist = existsSync(restoreFilePath)
|
||||||
if (!isExist) {
|
if (!isExist) {
|
||||||
throw new InternalServerErrorException('备份文件不存在')
|
throw new InternalServerErrorException('备份文件不存在')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const generateDefaultConfig: () => IConfig = () => ({
|
|||||||
openAiPreferredModel: 'gpt-4o-mini',
|
openAiPreferredModel: 'gpt-4o-mini',
|
||||||
openAiKey: '',
|
openAiKey: '',
|
||||||
aiSummaryTargetLanguage: 'auto',
|
aiSummaryTargetLanguage: 'auto',
|
||||||
|
enableDeepReading: false,
|
||||||
},
|
},
|
||||||
oauth: {
|
oauth: {
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -448,6 +448,13 @@ export class AIDto {
|
|||||||
})
|
})
|
||||||
enableAutoGenerateSummary: boolean
|
enableAutoGenerateSummary: boolean
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
@JSONSchemaToggleField('开启 AI 深度阅读', {
|
||||||
|
description: '是否开启调用 AI 去生成深度阅读',
|
||||||
|
})
|
||||||
|
enableDeepReading: boolean
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@JSONSchemaPlainField('AI 摘要目标语言', {
|
@JSONSchemaPlainField('AI 摘要目标语言', {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { RedisService } from '~/processors/redis/redis.service'
|
|||||||
import { SubPubBridgeService } from '~/processors/redis/subpub.service'
|
import { SubPubBridgeService } from '~/processors/redis/subpub.service'
|
||||||
import { InjectModel } from '~/transformers/model.transformer'
|
import { InjectModel } from '~/transformers/model.transformer'
|
||||||
import { getRedisKey } from '~/utils/redis.util'
|
import { getRedisKey } from '~/utils/redis.util'
|
||||||
import { camelcaseKeys } from '~/utils/tool.util'
|
import { camelcaseKeys, sleep } from '~/utils/tool.util'
|
||||||
|
|
||||||
import { generateDefaultConfig } from './configs.default'
|
import { generateDefaultConfig } from './configs.default'
|
||||||
import { OAuthDto } from './configs.dto'
|
import { OAuthDto } from './configs.dto'
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { readFile } from 'node:fs/promises'
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
|
|
||||||
|
import { chalk } from '@mx-space/compiled'
|
||||||
import { BadRequestException, Get, Query, Sse } from '@nestjs/common'
|
import { BadRequestException, Get, Query, Sse } from '@nestjs/common'
|
||||||
|
|
||||||
import { ApiController } from '~/common/decorators/api-controller.decorator'
|
import { ApiController } from '~/common/decorators/api-controller.decorator'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
import { FastifyReply, FastifyRequest } from 'fastify'
|
import { FastifyReply, FastifyRequest } from 'fastify'
|
||||||
import { lookup } from 'mime-types'
|
import { lookup } from 'mime-types'
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createWriteStream } from 'node:fs'
|
import { createWriteStream } from 'node:fs'
|
||||||
import { resolve } from 'node:path'
|
import path, { resolve } from 'node:path'
|
||||||
import type { Readable } from 'node:stream'
|
import type { Readable } from 'node:stream'
|
||||||
import type { FileType } from './file.type'
|
import type { FileType } from './file.type'
|
||||||
|
|
||||||
|
import { fs } from '@mx-space/compiled'
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Injectable,
|
Injectable,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import os from 'node:os'
|
||||||
|
import path from 'node:path'
|
||||||
import type { Readable } from 'form-data'
|
import type { Readable } from 'form-data'
|
||||||
|
|
||||||
|
import { fs } from '@mx-space/compiled'
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Delete,
|
Delete,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createReadStream, existsSync, statSync } from 'node:fs'
|
import { createReadStream, existsSync, statSync } from 'node:fs'
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import { extname, join } from 'node:path'
|
import path, { extname, join } from 'node:path'
|
||||||
import { render } from 'ejs'
|
import { render } from 'ejs'
|
||||||
import { FastifyReply, FastifyRequest } from 'fastify'
|
import { FastifyReply, FastifyRequest } from 'fastify'
|
||||||
import { lookup } from 'mime-types'
|
import { lookup } from 'mime-types'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import path from 'node:path'
|
||||||
import { URL } from 'node:url'
|
import { URL } from 'node:url'
|
||||||
import { parseHTML } from 'linkedom'
|
import { parseHTML } from 'linkedom'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import path from 'node:path'
|
||||||
import { isSemVer } from 'class-validator'
|
import { isSemVer } from 'class-validator'
|
||||||
import { catchError, lastValueFrom, Observable } from 'rxjs'
|
import { catchError, lastValueFrom, Observable } from 'rxjs'
|
||||||
import { lt, major, minor } from 'semver'
|
import { lt, major, minor } from 'semver'
|
||||||
|
|
||||||
|
import { chalk, fs } from '@mx-space/compiled'
|
||||||
import { Query, Sse } from '@nestjs/common'
|
import { Query, Sse } from '@nestjs/common'
|
||||||
|
|
||||||
import { dashboard } from '~/../package.json'
|
import { dashboard } from '~/../package.json'
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { appendFile, rm, writeFile } from 'node:fs/promises'
|
import { appendFile, rm, writeFile } from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
import { inspect } from 'node:util'
|
import { inspect } from 'node:util'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { catchError, Observable } from 'rxjs'
|
import { catchError, Observable } from 'rxjs'
|
||||||
import type { Subscriber } from 'rxjs'
|
import type { Subscriber } from 'rxjs'
|
||||||
|
|
||||||
|
import { chalk } from '@mx-space/compiled'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
|
|
||||||
import { dashboard } from '~/../package.json'
|
import { dashboard } from '~/../package.json'
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from '~/common/exceptions/biz.exception'
|
} from '~/common/exceptions/biz.exception'
|
||||||
import { ErrorCodeEnum } from '~/constants/error-code.constant'
|
import { ErrorCodeEnum } from '~/constants/error-code.constant'
|
||||||
import { InjectModel } from '~/transformers/model.transformer'
|
import { InjectModel } from '~/transformers/model.transformer'
|
||||||
import { getAvatar } from '~/utils/tool.util'
|
import { getAvatar, sleep } from '~/utils/tool.util'
|
||||||
|
|
||||||
import { AuthService } from '../auth/auth.service'
|
import { AuthService } from '../auth/auth.service'
|
||||||
import { UserModel } from './user.model'
|
import { UserModel } from './user.model'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ActivityModel } from '~/modules/activity/activity.model'
|
import { ActivityModel } from '~/modules/activity/activity.model'
|
||||||
|
import { AIDeepReadingModel } from '~/modules/ai/ai-deep-reading/ai-deep-reading.model'
|
||||||
import { AISummaryModel } from '~/modules/ai/ai-summary/ai-summary.model'
|
import { AISummaryModel } from '~/modules/ai/ai-summary/ai-summary.model'
|
||||||
import { AnalyzeModel } from '~/modules/analyze/analyze.model'
|
import { AnalyzeModel } from '~/modules/analyze/analyze.model'
|
||||||
import { AuthnModel } from '~/modules/authn/authn.model'
|
import { AuthnModel } from '~/modules/authn/authn.model'
|
||||||
@@ -27,6 +28,7 @@ import { getProviderByTypegooseClass } from '~/transformers/model.transformer'
|
|||||||
export const databaseModels = [
|
export const databaseModels = [
|
||||||
ActivityModel,
|
ActivityModel,
|
||||||
AISummaryModel,
|
AISummaryModel,
|
||||||
|
AIDeepReadingModel,
|
||||||
AnalyzeModel,
|
AnalyzeModel,
|
||||||
AuthnModel,
|
AuthnModel,
|
||||||
CategoryModel,
|
CategoryModel,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createReadStream } from 'node:fs'
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
import { Socket } from 'socket.io'
|
import { Socket } from 'socket.io'
|
||||||
import type {
|
import type {
|
||||||
@@ -51,8 +52,7 @@ export class AdminEventsGateway
|
|||||||
|
|
||||||
this.subscribeSocketToHandlerMap.set(client, handler)
|
this.subscribeSocketToHandlerMap.set(client, handler)
|
||||||
if (prevLog) {
|
if (prevLog) {
|
||||||
const stream = fs
|
const stream = createReadStream(resolve(LOG_DIR, getTodayLogFilePath()), {
|
||||||
.createReadStream(resolve(LOG_DIR, getTodayLogFilePath()), {
|
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
highWaterMark: 32 * 1024,
|
highWaterMark: 32 * 1024,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { statSync } from 'node:fs'
|
||||||
import { readdir, rm } from 'node:fs/promises'
|
import { readdir, rm } from 'node:fs/promises'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -106,7 +107,7 @@ export class CronService {
|
|||||||
const rmTaskArr = [] as Promise<any>[]
|
const rmTaskArr = [] as Promise<any>[]
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const filePath = join(LOG_DIR, file)
|
const filePath = join(LOG_DIR, file)
|
||||||
const state = fs.statSync(filePath)
|
const state = statSync(filePath)
|
||||||
const oldThanWeek = dayjs().diff(state.mtime, 'day') > 7
|
const oldThanWeek = dayjs().diff(state.mtime, 'day') > 7
|
||||||
if (oldThanWeek) {
|
if (oldThanWeek) {
|
||||||
rmTaskArr.push(rm(filePath))
|
rmTaskArr.push(rm(filePath))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import axios from 'axios'
|
|||||||
import axiosRetry, { exponentialDelay } from 'axios-retry'
|
import axiosRetry, { exponentialDelay } from 'axios-retry'
|
||||||
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
|
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
|
import { chalk } from '@mx-space/compiled'
|
||||||
import { Injectable, Logger } from '@nestjs/common'
|
import { Injectable, Logger } from '@nestjs/common'
|
||||||
|
|
||||||
import { AXIOS_CONFIG, DEBUG_MODE } from '~/app.config'
|
import { AXIOS_CONFIG, DEBUG_MODE } from '~/app.config'
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import mongoose from 'mongoose'
|
import mongoose from 'mongoose'
|
||||||
import type { CollectionRefTypes } from '~/constants/db.constant'
|
import type { CollectionRefTypes } from '~/constants/db.constant'
|
||||||
|
|
||||||
|
import { chalk } from '@mx-space/compiled'
|
||||||
|
|
||||||
import { MONGO_DB } from '~/app.config'
|
import { MONGO_DB } from '~/app.config'
|
||||||
import {
|
import {
|
||||||
NOTE_COLLECTION_NAME,
|
NOTE_COLLECTION_NAME,
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import cdp, { exec } from 'node:child_process'
|
import cdp, { exec } from 'node:child_process'
|
||||||
import { builtinModules } from 'node:module'
|
import { builtinModules } from 'node:module'
|
||||||
|
import os from 'node:os'
|
||||||
|
import path from 'node:path'
|
||||||
import { promisify } from 'node:util'
|
import { promisify } from 'node:util'
|
||||||
|
|
||||||
|
import { $, cd, fs } from '@mx-space/compiled'
|
||||||
|
|
||||||
export async function getFolderSize(folderPath: string) {
|
export async function getFolderSize(folderPath: string) {
|
||||||
try {
|
try {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user