feat: update ai integration (#2422)
* init Signed-off-by: Innei <tukon479@gmail.com> * update Signed-off-by: Innei <tukon479@gmail.com> * chore: update package dependencies and remove unused code - Updated package manager version in package.json to pnpm@10.10.0. - Removed references to `@modelcontextprotocol/sdk` from core application files. - Deleted unused `mcp.controller.ts` and cleaned up related imports in `mcp.module.ts`. - Adjusted optional dependencies in pnpm-lock.yaml. Signed-off-by: Innei <tukon479@gmail.com> * refactor: update AI summary and writer services to use new tools parser - Replaced `JsonOutputFunctionsParser` with `JsonOutputToolsParser` in both `ai-summary.service.ts` and `ai-writer.service.ts`. - Updated function definitions to align with the new tools structure, including changes to how functions are defined and invoked. - Enhanced error handling to return empty objects when no results are found. Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -52,121 +52,121 @@
|
||||
"redis-memory-server": "^0.12.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^4.22.1",
|
||||
"@antfu/install-pkg": "1.0.0",
|
||||
"@aws-sdk/client-s3": "3.802.0",
|
||||
"@babel/core": "7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.27.1",
|
||||
"@babel/plugin-transform-typescript": "7.27.1",
|
||||
"@babel/types": "^7.27.1",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
"@fastify/multipart": "9.0.3",
|
||||
"@fastify/static": "8.1.1",
|
||||
"@innei/next-async": "0.3.0",
|
||||
"@innei/pretty-logger-nestjs": "0.3.3",
|
||||
"@keyv/redis": "4.4.0",
|
||||
"@langchain/openai": "0.5.10",
|
||||
"@algolia/client-search": "catalog:",
|
||||
"@antfu/install-pkg": "catalog:",
|
||||
"@aws-sdk/client-s3": "catalog:",
|
||||
"@babel/core": "catalog:",
|
||||
"@babel/plugin-transform-modules-commonjs": "catalog:",
|
||||
"@babel/plugin-transform-typescript": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@fastify/cookie": "catalog:",
|
||||
"@fastify/multipart": "catalog:",
|
||||
"@fastify/static": "catalog:",
|
||||
"@innei/next-async": "catalog:",
|
||||
"@innei/pretty-logger-nestjs": "catalog:",
|
||||
"@keyv/redis": "catalog:",
|
||||
"@langchain/openai": "catalog:",
|
||||
"@mx-space/compiled": "workspace:*",
|
||||
"@nestjs/cache-manager": "3.0.1",
|
||||
"@nestjs/common": "11.1.0",
|
||||
"@nestjs/core": "11.1.0",
|
||||
"@nestjs/event-emitter": "3.0.1",
|
||||
"@nestjs/mapped-types": "^2.1.0",
|
||||
"@nestjs/platform-fastify": "11.1.0",
|
||||
"@nestjs/platform-socket.io": "11.1.0",
|
||||
"@nestjs/schedule": "6.0.0",
|
||||
"@nestjs/throttler": "6.4.0",
|
||||
"@nestjs/websockets": "11.1.0",
|
||||
"@simplewebauthn/server": "10.0.1",
|
||||
"@socket.io/redis-adapter": "8.3.0",
|
||||
"@socket.io/redis-emitter": "5.1.0",
|
||||
"@typegoose/auto-increment": "4.13.0",
|
||||
"@typegoose/typegoose": "12.15.0",
|
||||
"@types/jsonwebtoken": "9.0.9",
|
||||
"algoliasearch": "4.24.0",
|
||||
"axios": "^1.9.0",
|
||||
"axios-retry": "4.5.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"blurhash": "2.0.5",
|
||||
"cache-manager": "6.4.2",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.13.2",
|
||||
"class-validator-jsonschema": "npm:@innei/class-validator-jsonschema@3.1.2",
|
||||
"cls-hooked": "^4.2.2",
|
||||
"commander": "13.1.0",
|
||||
"dayjs": "1.11.13",
|
||||
"ejs": "3.1.10",
|
||||
"form-data": "4.0.2",
|
||||
"inquirer": "^10.2.2",
|
||||
"isbot": "5.1.27",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"jszip": "3.10.1",
|
||||
"keyv": "5.3.3",
|
||||
"langchain": "0.3.24",
|
||||
"linkedom": "0.18.10",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "11.1.0",
|
||||
"marked": "15.0.11",
|
||||
"mime-types": "^3.0.1",
|
||||
"mkdirp": "^3.0.1",
|
||||
"mongoose": "8.14.1",
|
||||
"mongoose-aggregate-paginate-v2": "1.1.4",
|
||||
"mongoose-autopopulate": "1.1.0",
|
||||
"mongoose-lean-getters": "2.2.1",
|
||||
"mongoose-lean-virtuals": "1.1.0",
|
||||
"mongoose-paginate-v2": "1.9.0",
|
||||
"node-machine-id": "1.1.12",
|
||||
"nodemailer": "7.0.0",
|
||||
"openai": "4.97.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"qs": "6.14.0",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"remove-markdown": "0.6.2",
|
||||
"remove-md-codeblock": "0.0.4",
|
||||
"rxjs": "7.8.2",
|
||||
"semver": "7.7.1",
|
||||
"slugify": "1.6.6",
|
||||
"snakecase-keys": "6.0.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ua-parser-js": "2.0.3",
|
||||
"vm2": "3.9.19",
|
||||
"wildcard-match": "5.1.4",
|
||||
"xss": "1.0.15",
|
||||
"zx-cjs": "7.0.7-0"
|
||||
"@nestjs/cache-manager": "catalog:",
|
||||
"@nestjs/common": "catalog:",
|
||||
"@nestjs/core": "catalog:",
|
||||
"@nestjs/event-emitter": "catalog:",
|
||||
"@nestjs/mapped-types": "catalog:",
|
||||
"@nestjs/platform-fastify": "catalog:",
|
||||
"@nestjs/platform-socket.io": "catalog:",
|
||||
"@nestjs/schedule": "catalog:",
|
||||
"@nestjs/throttler": "catalog:",
|
||||
"@nestjs/websockets": "catalog:",
|
||||
"@simplewebauthn/server": "catalog:",
|
||||
"@socket.io/redis-adapter": "catalog:",
|
||||
"@socket.io/redis-emitter": "catalog:",
|
||||
"@typegoose/auto-increment": "catalog:",
|
||||
"@typegoose/typegoose": "catalog:",
|
||||
"@types/jsonwebtoken": "catalog:",
|
||||
"algoliasearch": "catalog:",
|
||||
"axios": "catalog:",
|
||||
"axios-retry": "catalog:",
|
||||
"bcryptjs": "catalog:",
|
||||
"blurhash": "catalog:",
|
||||
"cache-manager": "catalog:",
|
||||
"class-transformer": "catalog:",
|
||||
"class-validator": "catalog:",
|
||||
"class-validator-jsonschema": "catalog:",
|
||||
"cls-hooked": "catalog:",
|
||||
"commander": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"ejs": "catalog:",
|
||||
"form-data": "catalog:",
|
||||
"inquirer": "catalog:",
|
||||
"isbot": "catalog:",
|
||||
"js-yaml": "catalog:",
|
||||
"json5": "catalog:",
|
||||
"jsonwebtoken": "catalog:",
|
||||
"jszip": "catalog:",
|
||||
"keyv": "catalog:",
|
||||
"langchain": "catalog:",
|
||||
"linkedom": "catalog:",
|
||||
"lodash": "catalog:",
|
||||
"lru-cache": "catalog:",
|
||||
"marked": "catalog:",
|
||||
"mime-types": "catalog:",
|
||||
"mkdirp": "catalog:",
|
||||
"mongoose": "catalog:",
|
||||
"mongoose-aggregate-paginate-v2": "catalog:",
|
||||
"mongoose-autopopulate": "catalog:",
|
||||
"mongoose-lean-getters": "catalog:",
|
||||
"mongoose-lean-virtuals": "catalog:",
|
||||
"mongoose-paginate-v2": "catalog:",
|
||||
"node-machine-id": "catalog:",
|
||||
"nodemailer": "catalog:",
|
||||
"openai": "catalog:",
|
||||
"pluralize": "catalog:",
|
||||
"qs": "catalog:",
|
||||
"reflect-metadata": "catalog:",
|
||||
"remove-markdown": "catalog:",
|
||||
"remove-md-codeblock": "catalog:",
|
||||
"rxjs": "catalog:",
|
||||
"semver": "catalog:",
|
||||
"slugify": "catalog:",
|
||||
"snakecase-keys": "catalog:",
|
||||
"source-map-support": "catalog:",
|
||||
"ua-parser-js": "catalog:",
|
||||
"vm2": "catalog:",
|
||||
"wildcard-match": "catalog:",
|
||||
"xss": "catalog:",
|
||||
"zx-cjs": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@langchain/core": "0.3.51",
|
||||
"@nestjs/cli": "11.0.7",
|
||||
"@nestjs/schematics": "11.0.5",
|
||||
"@nestjs/testing": "11.1.0",
|
||||
"@swc/core": "1.11.24",
|
||||
"@types/babel__core": "7.20.5",
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/cls-hooked": "^4.3.9",
|
||||
"@types/ejs": "3.1.5",
|
||||
"@types/get-image-colors": "4.0.5",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/lodash": "4.17.16",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/mongoose-aggregate-paginate-v2": "1.0.12",
|
||||
"@types/node": "22.15.3",
|
||||
"@types/nodemailer": "6.4.17",
|
||||
"@types/qs": "6.9.18",
|
||||
"@types/remove-markdown": "0.3.4",
|
||||
"@types/semver": "7.7.0",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/validator": "13.15.0",
|
||||
"@vercel/ncc": "0.38.3",
|
||||
"cron": "^3.5.0",
|
||||
"ioredis": "5.6.1",
|
||||
"sharp": "0.34.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"unplugin-swc": "1.5.2",
|
||||
"vite": "5.4.10",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "1.5.2",
|
||||
"zx": "7.2.3"
|
||||
"@langchain/core": "catalog:",
|
||||
"@nestjs/cli": "catalog:",
|
||||
"@nestjs/schematics": "catalog:",
|
||||
"@nestjs/testing": "catalog:",
|
||||
"@swc/core": "catalog:",
|
||||
"@types/babel__core": "catalog:",
|
||||
"@types/bcryptjs": "catalog:",
|
||||
"@types/cls-hooked": "catalog:",
|
||||
"@types/ejs": "catalog:",
|
||||
"@types/get-image-colors": "catalog:",
|
||||
"@types/js-yaml": "catalog:",
|
||||
"@types/lodash": "catalog:",
|
||||
"@types/mime-types": "catalog:",
|
||||
"@types/mongoose-aggregate-paginate-v2": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/nodemailer": "catalog:",
|
||||
"@types/qs": "catalog:",
|
||||
"@types/remove-markdown": "catalog:",
|
||||
"@types/semver": "catalog:",
|
||||
"@types/ua-parser-js": "catalog:",
|
||||
"@types/validator": "catalog:",
|
||||
"@vercel/ncc": "catalog:",
|
||||
"cron": "catalog:",
|
||||
"ioredis": "catalog:",
|
||||
"sharp": "catalog:",
|
||||
"socket.io": "catalog:",
|
||||
"unplugin-swc": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vite-tsconfig-paths": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"zx": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import { HelperModule as BizHelperModule } from './modules/helper/helper.module'
|
||||
import { InitModule } from './modules/init/init.module'
|
||||
import { LinkModule } from './modules/link/link.module'
|
||||
import { MarkdownModule } from './modules/markdown/markdown.module'
|
||||
import { McpModule } from './modules/mcp/mcp.module'
|
||||
import { NoteModule } from './modules/note/note.module'
|
||||
import { OptionModule } from './modules/option/option.module'
|
||||
import { PageModule } from './modules/page/page.module'
|
||||
@@ -94,6 +95,7 @@ import { RedisModule } from './processors/redis/redis.module'
|
||||
HealthModule,
|
||||
LinkModule,
|
||||
MarkdownModule,
|
||||
McpModule,
|
||||
NoteModule,
|
||||
OptionModule,
|
||||
PageModule,
|
||||
|
||||
13
apps/core/src/modules/ai/ai-agent/ai-agent.module.ts
Normal file
13
apps/core/src/modules/ai/ai-agent/ai-agent.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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 {}
|
||||
490
apps/core/src/modules/ai/ai-agent/ai-agent.service.ts
Normal file
490
apps/core/src/modules/ai/ai-agent/ai-agent.service.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
import { AgentExecutor, createOpenAIToolsAgent } from 'langchain/agents'
|
||||
|
||||
import {
|
||||
ChatPromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
} from '@langchain/core/prompts'
|
||||
import { DynamicStructuredTool, ToolInterface } from '@langchain/core/tools'
|
||||
import { z } from '@mx-space/compiled/zod'
|
||||
import { Injectable } from '@nestjs/common'
|
||||
|
||||
import { McpService } from '../../mcp/mcp.service'
|
||||
import { AiService } from '../ai.service'
|
||||
|
||||
@Injectable()
|
||||
export class AIAgentService {
|
||||
constructor(
|
||||
private readonly aiService: AiService,
|
||||
private readonly mcpService: McpService,
|
||||
) {}
|
||||
|
||||
// 创建获取帖子的工具
|
||||
private createGetPostTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_post_by_id',
|
||||
description: '根据ID获取博客文章',
|
||||
schema: z.object({
|
||||
id: z.string().describe('帖子的ID'),
|
||||
}),
|
||||
func: async ({ id }) => {
|
||||
try {
|
||||
const post = await this.mcpService.getPostById(id)
|
||||
return JSON.stringify(post)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取帖子列表的工具
|
||||
private createGetPostsTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_posts',
|
||||
description: '获取博客文章列表',
|
||||
schema: z.object({
|
||||
page: z.number().optional().describe('页码,默认为1'),
|
||||
size: z.number().optional().describe('每页数量,默认为10'),
|
||||
}),
|
||||
func: async ({ page = 1, size = 10 }) => {
|
||||
try {
|
||||
const posts = await this.mcpService.getPosts(page, size)
|
||||
return JSON.stringify(posts)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取笔记的工具
|
||||
private createGetNoteTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_note_by_id',
|
||||
description: '根据ID获取笔记',
|
||||
schema: z.object({
|
||||
id: z.string().describe('笔记的ID或nid'),
|
||||
}),
|
||||
func: async ({ id }) => {
|
||||
try {
|
||||
const note = await this.mcpService.getNoteById(id)
|
||||
return JSON.stringify(note)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取笔记列表的工具
|
||||
private createGetNotesTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_notes',
|
||||
description: '获取笔记列表',
|
||||
schema: z.object({
|
||||
page: z.number().optional().describe('页码,默认为1'),
|
||||
size: z.number().optional().describe('每页数量,默认为10'),
|
||||
}),
|
||||
func: async ({ page = 1, size = 10 }) => {
|
||||
try {
|
||||
const notes = await this.mcpService.getNotes(page, size)
|
||||
return JSON.stringify(notes)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private createGetLatestPostTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_latest_post',
|
||||
description: '获取最新的一篇博客文章',
|
||||
func: async () => {
|
||||
const post = await this.mcpService.getLatestPost()
|
||||
return JSON.stringify(post)
|
||||
},
|
||||
schema: z.object({}),
|
||||
})
|
||||
}
|
||||
|
||||
private createGetLatestNotesTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_latest_notes',
|
||||
description: '获取最新的一篇笔记',
|
||||
func: async () => {
|
||||
const notes = await this.mcpService.getLatestNotes()
|
||||
return JSON.stringify(notes)
|
||||
},
|
||||
schema: z.object({}),
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取分类的工具
|
||||
private createGetCategoryTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_category_by_id',
|
||||
description: '根据ID获取分类',
|
||||
schema: z.object({
|
||||
id: z.string().describe('分类的ID'),
|
||||
}),
|
||||
func: async ({ id }) => {
|
||||
try {
|
||||
const category = await this.mcpService.getCategoryById(id)
|
||||
return JSON.stringify(category)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取所有分类的工具
|
||||
private createGetAllCategoriesTools(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_all_categories',
|
||||
description: '获取所有分类及其文章数量',
|
||||
schema: z.object({}),
|
||||
func: async () => {
|
||||
try {
|
||||
const categories = await this.mcpService.getAllCategories()
|
||||
return JSON.stringify(categories)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取分类下文章的工具
|
||||
private createGetPostsByCategoryTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_posts_by_category',
|
||||
description: '获取指定分类下的所有文章',
|
||||
schema: z.object({
|
||||
categoryId: z.string().describe('分类的ID'),
|
||||
}),
|
||||
func: async ({ categoryId }) => {
|
||||
try {
|
||||
const posts = await this.mcpService.getPostsByCategory(categoryId)
|
||||
return JSON.stringify(posts)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取标签统计的工具
|
||||
private createGetTagsSummaryTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_tags_summary',
|
||||
description: '获取所有标签及其文章数量统计',
|
||||
schema: z.object({}),
|
||||
func: async () => {
|
||||
try {
|
||||
const tags = await this.mcpService.getTagsSummary()
|
||||
return JSON.stringify(tags)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取标签下文章的工具
|
||||
private createGetPostsByTagTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_posts_by_tag',
|
||||
description: '获取指定标签下的所有文章',
|
||||
schema: z.object({
|
||||
tag: z.string().describe('标签名称'),
|
||||
}),
|
||||
func: async ({ tag }) => {
|
||||
try {
|
||||
const posts = await this.mcpService.getPostsByTag(tag)
|
||||
return JSON.stringify(posts)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取页面的工具
|
||||
private createGetPageTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_page_by_id',
|
||||
description: '根据ID获取页面',
|
||||
schema: z.object({
|
||||
id: z.string().describe('页面的ID'),
|
||||
}),
|
||||
func: async ({ id }) => {
|
||||
try {
|
||||
const page = await this.mcpService.getPageById(id)
|
||||
return JSON.stringify(page)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取所有页面的工具
|
||||
private createGetAllPagesTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_all_pages',
|
||||
description: '获取所有页面',
|
||||
schema: z.object({}),
|
||||
func: async () => {
|
||||
try {
|
||||
const pages = await this.mcpService.getAllPages()
|
||||
return JSON.stringify(pages)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取所有说说的工具
|
||||
private createGetAllSaysTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_all_says',
|
||||
description: '获取所有说说/状态更新',
|
||||
schema: z.object({}),
|
||||
func: async () => {
|
||||
try {
|
||||
const says = await this.mcpService.getAllSays()
|
||||
return JSON.stringify(says)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取随机说说的工具
|
||||
private createGetRandomSayTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_random_say',
|
||||
description: '获取随机一条说说/状态更新',
|
||||
schema: z.object({}),
|
||||
func: async () => {
|
||||
try {
|
||||
const say = await this.mcpService.getRandomSay()
|
||||
return JSON.stringify(say)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取所有动态的工具
|
||||
private createGetAllRecentlyTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_all_recently',
|
||||
description: '获取所有动态/活动',
|
||||
schema: z.object({}),
|
||||
func: async () => {
|
||||
try {
|
||||
const recently = await this.mcpService.getAllRecently()
|
||||
return JSON.stringify(recently)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取特定动态的工具
|
||||
private createGetRecentlyByIdTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_recently_by_id',
|
||||
description: '根据ID获取特定动态/活动',
|
||||
schema: z.object({
|
||||
id: z.string().describe('动态的ID'),
|
||||
}),
|
||||
func: async ({ id }) => {
|
||||
try {
|
||||
const recently = await this.mcpService.getRecentlyById(id)
|
||||
return JSON.stringify(recently)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取最新动态的工具
|
||||
private createGetLatestRecentlyTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_latest_recently',
|
||||
description: '获取最新的一条动态/活动',
|
||||
schema: z.object({}),
|
||||
func: async () => {
|
||||
try {
|
||||
const recently = await this.mcpService.getLatestRecently()
|
||||
return JSON.stringify(recently)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取分页动态的工具
|
||||
private createGetRecentlyOffsetTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_recently_offset',
|
||||
description: '获取指定范围的动态/活动',
|
||||
schema: z.object({
|
||||
size: z.number().optional().describe('数量,默认为10'),
|
||||
before: z.string().optional().describe('获取此ID之前的动态'),
|
||||
after: z.string().optional().describe('获取此ID之后的动态'),
|
||||
}),
|
||||
func: async ({ size = 10, before, after }) => {
|
||||
try {
|
||||
const recently = await this.mcpService.getRecentlyOffset({
|
||||
size,
|
||||
before,
|
||||
after,
|
||||
})
|
||||
return JSON.stringify(recently)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取评论列表的工具
|
||||
private createGetCommentsTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_comments',
|
||||
description: '获取所有评论,可按状态筛选',
|
||||
schema: z.object({
|
||||
page: z.number().optional().describe('页码,默认为1'),
|
||||
size: z.number().optional().describe('每页数量,默认为10'),
|
||||
state: z.number().optional().describe('评论状态筛选,0表示所有'),
|
||||
}),
|
||||
func: async ({ page = 1, size = 10, state = 0 }) => {
|
||||
try {
|
||||
const comments = await this.mcpService.getComments({
|
||||
page,
|
||||
size,
|
||||
state,
|
||||
})
|
||||
return JSON.stringify(comments)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 创建获取内容评论的工具
|
||||
private createGetContentCommentsTool(): ToolInterface {
|
||||
return new DynamicStructuredTool({
|
||||
name: 'get_content_comments',
|
||||
description: '获取特定内容的评论',
|
||||
schema: z.object({
|
||||
id: z.string().describe('内容的ID'),
|
||||
type: z.string().optional().describe('内容类型,如post, note, page等'),
|
||||
}),
|
||||
func: async ({ id, type }) => {
|
||||
try {
|
||||
const comments = await this.mcpService.getContentComments(id, type)
|
||||
return JSON.stringify(comments)
|
||||
} catch (error) {
|
||||
return `Error: ${error.message}`
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 使用LangChain的Agent模式与MCP数据交互
|
||||
async runWithTools(userPrompt: string) {
|
||||
// 获取OpenAI模型
|
||||
const model = await this.aiService.getOpenAiChain()
|
||||
|
||||
// 初始化工具列表
|
||||
const tools = [
|
||||
this.createGetPostTool(),
|
||||
this.createGetPostsTool(),
|
||||
this.createGetNoteTool(),
|
||||
this.createGetNotesTool(),
|
||||
this.createGetLatestPostTool(),
|
||||
this.createGetLatestNotesTool(),
|
||||
this.createGetCategoryTool(),
|
||||
this.createGetAllCategoriesTools(),
|
||||
this.createGetPostsByCategoryTool(),
|
||||
this.createGetTagsSummaryTool(),
|
||||
this.createGetPostsByTagTool(),
|
||||
this.createGetPageTool(),
|
||||
this.createGetAllPagesTool(),
|
||||
this.createGetAllSaysTool(),
|
||||
this.createGetRandomSayTool(),
|
||||
this.createGetAllRecentlyTool(),
|
||||
this.createGetRecentlyByIdTool(),
|
||||
this.createGetLatestRecentlyTool(),
|
||||
this.createGetRecentlyOffsetTool(),
|
||||
this.createGetCommentsTool(),
|
||||
this.createGetContentCommentsTool(),
|
||||
]
|
||||
|
||||
// 创建Agent提示模板
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
[
|
||||
'system',
|
||||
`你是一个可以访问博客数据库的智能助手。使用提供的工具来获取和分析数据。
|
||||
|
||||
当你需要回答用户问题时,请遵循以下步骤:
|
||||
1. 分析用户的问题,确定需要获取什么数据
|
||||
2. 使用合适的工具获取数据
|
||||
3. 检查和分析获取的数据
|
||||
4. 如需更多信息,继续使用工具获取
|
||||
5. 根据所有收集到的数据提供完整回答
|
||||
|
||||
你可以查询的内容包括:
|
||||
- 博客文章(posts)
|
||||
- 笔记(notes)
|
||||
- 分类(categories)
|
||||
- 标签(tags)
|
||||
- 自定义页面(pages)
|
||||
- 说说/状态更新(says)
|
||||
- 动态/活动(recently)
|
||||
- 评论(comments)
|
||||
|
||||
不要编造信息,只使用通过工具获得的真实数据。`,
|
||||
],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['human', '{input}'],
|
||||
new MessagesPlaceholder('agent_scratchpad'),
|
||||
])
|
||||
|
||||
// 创建Agent
|
||||
const agent = await createOpenAIToolsAgent({
|
||||
llm: model,
|
||||
tools,
|
||||
prompt,
|
||||
})
|
||||
|
||||
// 创建Agent执行器
|
||||
const executor = new AgentExecutor({
|
||||
agent,
|
||||
tools,
|
||||
maxIterations: 5,
|
||||
verbose: isDev,
|
||||
})
|
||||
|
||||
// 执行Agent
|
||||
const result = await executor.invoke({
|
||||
input: userPrompt,
|
||||
chat_history: [],
|
||||
})
|
||||
|
||||
return result.output
|
||||
}
|
||||
}
|
||||
15
apps/core/src/modules/ai/ai-agent/test.controller.ts
Normal file
15
apps/core/src/modules/ai/ai-agent/test.controller.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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 主要了写了什么内容',
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { JsonOutputFunctionsParser } from 'langchain/output_parsers'
|
||||
import removeMdCodeblock from 'remove-md-codeblock'
|
||||
import type { PagerDto } from '~/shared/dto/pager.dto'
|
||||
|
||||
import { JsonOutputToolsParser } from '@langchain/core/output_parsers/openai_tools'
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
|
||||
@@ -57,33 +58,42 @@ export class AiSummaryService {
|
||||
throw new BizException(ErrorCodeEnum.ContentNotFoundCantProcess)
|
||||
}
|
||||
|
||||
const parser = new JsonOutputFunctionsParser()
|
||||
const parser = new JsonOutputToolsParser()
|
||||
|
||||
const runnable = openai
|
||||
.bind({
|
||||
functions: [
|
||||
tools: [
|
||||
{
|
||||
name: 'extractor',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
summary: {
|
||||
type: 'string',
|
||||
description: `The summary of the input text in the ${LANGUAGE_CODE_TO_NAME[lang] || LANGUAGE_CODE_TO_NAME[DEFAULT_SUMMARY_LANG]}, and the length of the summary is less than 150 words.`,
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'extractor',
|
||||
description: `Extract the summary of the input text in the ${LANGUAGE_CODE_TO_NAME[lang] || LANGUAGE_CODE_TO_NAME[DEFAULT_SUMMARY_LANG]}, and the length of the summary is less than 150 words.`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
summary: {
|
||||
type: 'string',
|
||||
description: `The summary of the input text in the ${LANGUAGE_CODE_TO_NAME[lang] || LANGUAGE_CODE_TO_NAME[DEFAULT_SUMMARY_LANG]}, and the length of the summary is less than 150 words.`,
|
||||
},
|
||||
},
|
||||
required: ['summary'],
|
||||
},
|
||||
required: ['summary'],
|
||||
},
|
||||
},
|
||||
],
|
||||
function_call: { name: 'extractor' },
|
||||
|
||||
tool_choice: { type: 'function', function: { name: 'extractor' } },
|
||||
})
|
||||
.pipe(parser)
|
||||
const result = await runnable.invoke([
|
||||
const result = (await runnable.invoke([
|
||||
this.serializeText(article.document.text),
|
||||
])
|
||||
])) as any[]
|
||||
|
||||
return (result as any).summary
|
||||
if (result.length === 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return result[0]?.args?.summary
|
||||
}
|
||||
async generateSummaryByOpenAI(articleId: string, lang: string) {
|
||||
const {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { JsonOutputFunctionsParser } from 'langchain/output_parsers'
|
||||
import type { FunctionDefinition } from '@langchain/core/language_models/base'
|
||||
import type {
|
||||
FunctionDefinition,
|
||||
ToolDefinition,
|
||||
} from '@langchain/core/language_models/base'
|
||||
|
||||
import { JsonOutputToolsParser } from '@langchain/core/output_parsers/openai_tools'
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
|
||||
import { AiService } from '../ai.service'
|
||||
@@ -16,23 +19,30 @@ export class AiWriterService {
|
||||
text: string,
|
||||
parameters: FunctionDefinition['parameters'],
|
||||
) {
|
||||
const functionSchema: FunctionDefinition = {
|
||||
name: 'extractor',
|
||||
description: 'Extracts fields from the input.',
|
||||
parameters,
|
||||
const toolDefinition: ToolDefinition = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'extractor',
|
||||
description: 'Extracts fields from the input.',
|
||||
parameters,
|
||||
},
|
||||
}
|
||||
const model = await this.aiService.getOpenAiChain()
|
||||
const parser = new JsonOutputFunctionsParser()
|
||||
const parser = new JsonOutputToolsParser()
|
||||
|
||||
const runnable = model
|
||||
.bind({
|
||||
functions: [functionSchema],
|
||||
function_call: { name: 'extractor' },
|
||||
tools: [toolDefinition],
|
||||
tool_choice: { type: 'function', function: { name: 'extractor' } },
|
||||
})
|
||||
.pipe(parser)
|
||||
const result = await runnable.invoke([text])
|
||||
const result = (await runnable.invoke([text])) as any[]
|
||||
|
||||
return result
|
||||
if (result.length === 0) {
|
||||
return {}
|
||||
}
|
||||
// Extract just the args object from the first tool call response
|
||||
return result[0]?.args || {}
|
||||
}
|
||||
async generateTitleAndSlugByOpenAI(text: string) {
|
||||
return this.queryByFunctionSchema(text, {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import type { ChatModel } from 'openai/resources'
|
||||
|
||||
export const DEFAULT_SUMMARY_LANG = 'zh'
|
||||
type _Models = ChatModel
|
||||
export const LANGUAGE_CODE_TO_NAME = {
|
||||
ar: 'Arabic',
|
||||
bg: 'Bulgarian',
|
||||
@@ -46,150 +43,3 @@ export const LANGUAGE_CODE_TO_NAME = {
|
||||
vi: 'Vietnamese',
|
||||
zh: 'Chinese',
|
||||
}
|
||||
|
||||
export const OpenAiSupportedModels = [
|
||||
{
|
||||
label: 'o3-mini',
|
||||
value: 'o3-mini',
|
||||
},
|
||||
{
|
||||
label: 'o3-mini-2025-01-31',
|
||||
value: 'o3-mini-2025-01-31',
|
||||
},
|
||||
{
|
||||
label: 'o1',
|
||||
value: 'o1',
|
||||
},
|
||||
{
|
||||
label: 'o1-2024-12-17',
|
||||
value: 'o1-2024-12-17',
|
||||
},
|
||||
{
|
||||
label: 'o1-preview',
|
||||
value: 'o1-preview',
|
||||
},
|
||||
{
|
||||
label: 'o1-preview-2024-09-12',
|
||||
value: 'o1-preview-2024-09-12',
|
||||
},
|
||||
{
|
||||
label: 'o1-mini',
|
||||
value: 'o1-mini',
|
||||
},
|
||||
{
|
||||
label: 'o1-mini-2024-09-12',
|
||||
value: 'o1-mini-2024-09-12',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o',
|
||||
value: 'gpt-4o',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-2024-11-20',
|
||||
value: 'gpt-4o-2024-11-20',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-2024-08-06',
|
||||
value: 'gpt-4o-2024-08-06',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-2024-05-13',
|
||||
value: 'gpt-4o-2024-05-13',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-audio-preview',
|
||||
value: 'gpt-4o-audio-preview',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-audio-preview-2024-10-01',
|
||||
value: 'gpt-4o-audio-preview-2024-10-01',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-audio-preview-2024-12-17',
|
||||
value: 'gpt-4o-audio-preview-2024-12-17',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-mini-audio-preview',
|
||||
value: 'gpt-4o-mini-audio-preview',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4o-mini-2024-07-18',
|
||||
value: 'gpt-4o-mini-2024-07-18',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-turbo',
|
||||
value: 'gpt-4-turbo',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-turbo-2024-04-09',
|
||||
value: 'gpt-4-turbo-2024-04-09',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-0125-preview',
|
||||
value: 'gpt-4-0125-preview',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-turbo-preview',
|
||||
value: 'gpt-4-turbo-preview',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-1106-preview',
|
||||
value: 'gpt-4-1106-preview',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-vision-preview',
|
||||
value: 'gpt-4-vision-preview',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4',
|
||||
value: 'gpt-4',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-0314',
|
||||
value: 'gpt-4-0314',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-0613',
|
||||
value: 'gpt-4-0613',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-32k',
|
||||
value: 'gpt-4-32k',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-32k-0314',
|
||||
value: 'gpt-4-32k-0314',
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-32k-0613',
|
||||
value: 'gpt-4-32k-0613',
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo',
|
||||
value: 'gpt-3.5-turbo',
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-16k',
|
||||
value: 'gpt-3.5-turbo-16k',
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-0301',
|
||||
value: 'gpt-3.5-turbo-0301',
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-0613',
|
||||
value: 'gpt-3.5-turbo-0613',
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-1106',
|
||||
value: 'gpt-3.5-turbo-1106',
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-0125',
|
||||
value: 'gpt-3.5-turbo-0125',
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-16k-0613',
|
||||
value: 'gpt-3.5-turbo-16k-0613',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { forwardRef, Module } from '@nestjs/common'
|
||||
|
||||
import { AiAgentModule } from './ai-agent/ai-agent.module'
|
||||
import { AiSummaryController } from './ai-summary/ai-summary.controller'
|
||||
import { AiSummaryService } from './ai-summary/ai-summary.service'
|
||||
import { AiWriterController } from './ai-writer/ai-writer.controller'
|
||||
@@ -7,6 +8,7 @@ import { AiWriterService } from './ai-writer/ai-writer.service'
|
||||
import { AiService } from './ai.service'
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => AiAgentModule)],
|
||||
providers: [AiSummaryService, AiService, AiWriterService],
|
||||
controllers: [AiSummaryController, AiWriterController],
|
||||
exports: [AiService],
|
||||
|
||||
@@ -87,7 +87,7 @@ export const generateDefaultConfig: () => IConfig = () => ({
|
||||
enableAutoGenerateSummary: false,
|
||||
enableSummary: false,
|
||||
openAiEndpoint: '',
|
||||
openAiPreferredModel: 'gpt-3.5-turbo',
|
||||
openAiPreferredModel: 'gpt-4o-mini',
|
||||
openAiKey: '',
|
||||
aiSummaryTargetLanguage: 'auto',
|
||||
},
|
||||
|
||||
@@ -19,7 +19,6 @@ import type { ChatModel } from 'openai/resources'
|
||||
|
||||
import { IsAllowedUrl } from '~/decorators/dto/isAllowedUrl'
|
||||
|
||||
import { OpenAiSupportedModels } from '../ai/ai.constants'
|
||||
import { Encrypt } from './configs.encrypt.util'
|
||||
import {
|
||||
halfFieldOption,
|
||||
@@ -431,13 +430,8 @@ export class AIDto {
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@JSONSchemaPlainField('OpenAI 默认模型', {
|
||||
'ui:options': {
|
||||
type: 'select',
|
||||
values: OpenAiSupportedModels,
|
||||
},
|
||||
})
|
||||
openAiPreferredModel: ChatModel
|
||||
@JSONSchemaPlainField('OpenAI 默认模型')
|
||||
openAiPreferredModel: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
|
||||
26
apps/core/src/modules/mcp/mcp.module.ts
Normal file
26
apps/core/src/modules/mcp/mcp.module.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { forwardRef, Module } from '@nestjs/common'
|
||||
|
||||
import { CategoryModule } from '../category/category.module'
|
||||
import { CommentModule } from '../comment/comment.module'
|
||||
import { NoteModule } from '../note/note.module'
|
||||
import { PageModule } from '../page/page.module'
|
||||
import { PostModule } from '../post/post.module'
|
||||
import { RecentlyModule } from '../recently/recently.module'
|
||||
import { SayModule } from '../say/say.module'
|
||||
import { McpService } from './mcp.service'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
forwardRef(() => NoteModule),
|
||||
forwardRef(() => PostModule),
|
||||
forwardRef(() => CategoryModule),
|
||||
forwardRef(() => PageModule),
|
||||
forwardRef(() => SayModule),
|
||||
forwardRef(() => RecentlyModule),
|
||||
forwardRef(() => CommentModule),
|
||||
],
|
||||
|
||||
providers: [McpService],
|
||||
exports: [McpService],
|
||||
})
|
||||
export class McpModule {}
|
||||
327
apps/core/src/modules/mcp/mcp.service.ts
Normal file
327
apps/core/src/modules/mcp/mcp.service.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
|
||||
import { CannotFindException } from '~/common/exceptions/cant-find.exception'
|
||||
|
||||
import { CategoryService } from '../category/category.service'
|
||||
import { CommentService } from '../comment/comment.service'
|
||||
import { NoteService } from '../note/note.service'
|
||||
import { PageService } from '../page/page.service'
|
||||
import { PostService } from '../post/post.service'
|
||||
import { RecentlyService } from '../recently/recently.service'
|
||||
import { SayService } from '../say/say.service'
|
||||
|
||||
@Injectable()
|
||||
export class McpService {
|
||||
constructor(
|
||||
private readonly postService: PostService,
|
||||
private readonly noteService: NoteService,
|
||||
private readonly categoryService: CategoryService,
|
||||
private readonly pageService: PageService,
|
||||
private readonly sayService: SayService,
|
||||
private readonly recentlyService: RecentlyService,
|
||||
private readonly commentService: CommentService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get a post by ID
|
||||
* @param id Post ID
|
||||
* @returns The requested post
|
||||
*/
|
||||
async getPostById(id: string) {
|
||||
const post = await this.postService.model
|
||||
.findById(id)
|
||||
.populate('category')
|
||||
.populate({
|
||||
path: 'related',
|
||||
select: 'title slug id _id categoryId category',
|
||||
})
|
||||
|
||||
if (!post) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
/**
|
||||
* Get posts with pagination
|
||||
* @param page Page number
|
||||
* @param size Page size
|
||||
* @returns Paginated posts
|
||||
*/
|
||||
async getPosts(page = 1, size = 10) {
|
||||
const query = this.postService.model
|
||||
.find()
|
||||
.populate('category')
|
||||
.sort({ created: -1 })
|
||||
.skip((page - 1) * size)
|
||||
.limit(size)
|
||||
|
||||
const posts = await query.exec()
|
||||
const total = await this.postService.model.countDocuments()
|
||||
|
||||
return {
|
||||
data: posts,
|
||||
pagination: {
|
||||
total,
|
||||
size,
|
||||
currentPage: page,
|
||||
totalPage: Math.ceil(total / size),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a note by ID
|
||||
* @param id Note ID or nid
|
||||
* @returns The requested note
|
||||
*/
|
||||
async getNoteById(id: string | number) {
|
||||
const note = await this.noteService.findOneByIdOrNid(id)
|
||||
|
||||
if (!note) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notes with pagination
|
||||
* @param page Page number
|
||||
* @param size Page size
|
||||
* @returns Paginated notes
|
||||
*/
|
||||
async getNotes(page = 1, size = 10) {
|
||||
const query = this.noteService.model
|
||||
.find({ hide: false })
|
||||
.sort({ created: -1 })
|
||||
.skip((page - 1) * size)
|
||||
.limit(size)
|
||||
|
||||
const notes = await query.exec()
|
||||
const total = await this.noteService.model.countDocuments({ hide: false })
|
||||
|
||||
return {
|
||||
data: notes,
|
||||
pagination: {
|
||||
total,
|
||||
size,
|
||||
currentPage: page,
|
||||
totalPage: Math.ceil(total / size),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async getLatestPost() {
|
||||
const post = await this.postService.model.findOne().sort({ created: -1 })
|
||||
|
||||
if (!post) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
async getLatestNotes() {
|
||||
const notes = await this.noteService.model.findOne().sort({ created: -1 })
|
||||
|
||||
if (!notes) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return notes
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category by ID
|
||||
* @param id Category ID
|
||||
* @returns The requested category with post count
|
||||
*/
|
||||
async getCategoryById(id: string) {
|
||||
const category = await this.categoryService.findCategoryById(id)
|
||||
|
||||
if (!category) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return category
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all categories
|
||||
* @returns All categories with post counts
|
||||
*/
|
||||
async getAllCategories() {
|
||||
return await this.categoryService.findAllCategory()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get posts in a specific category
|
||||
* @param categoryId Category ID
|
||||
* @returns Posts in the specified category
|
||||
*/
|
||||
async getPostsByCategory(categoryId: string) {
|
||||
const posts = await this.categoryService.findCategoryPost(categoryId)
|
||||
|
||||
if (!posts || posts.length === 0) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return posts
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of all tags and their post counts
|
||||
* @returns Tags with post counts
|
||||
*/
|
||||
async getTagsSummary() {
|
||||
return await this.categoryService.getPostTagsSum()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get posts with a specific tag
|
||||
* @param tag Tag name
|
||||
* @returns Posts with the specified tag
|
||||
*/
|
||||
async getPostsByTag(tag: string) {
|
||||
return await this.categoryService.findArticleWithTag(tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a page by ID
|
||||
* @param id Page ID
|
||||
* @returns The requested page
|
||||
*/
|
||||
async getPageById(id: string) {
|
||||
const page = await this.pageService.model.findById(id)
|
||||
|
||||
if (!page) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return page
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pages
|
||||
* @returns All pages
|
||||
*/
|
||||
async getAllPages() {
|
||||
const pages = await this.pageService.model.find().sort({ order: 1 })
|
||||
return pages
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all says (quotes/status updates)
|
||||
* @returns All says
|
||||
*/
|
||||
async getAllSays() {
|
||||
const says = await this.sayService.model.find().sort({ created: -1 })
|
||||
return says
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random say
|
||||
* @returns A random say
|
||||
*/
|
||||
async getRandomSay() {
|
||||
const res = await this.sayService.model.find({}).lean()
|
||||
if (res.length === 0) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
// Get a random item from the array
|
||||
const randomIndex = Math.floor(Math.random() * res.length)
|
||||
return res[randomIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all recently activity
|
||||
* @returns All recently activity
|
||||
*/
|
||||
async getAllRecently() {
|
||||
return await this.recentlyService.getAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific recently activity by ID
|
||||
* @param id Recently activity ID
|
||||
* @returns The requested recently activity
|
||||
*/
|
||||
async getRecentlyById(id: string) {
|
||||
const recently = await this.recentlyService.getOne(id)
|
||||
|
||||
if (!recently) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return recently
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest recently activities with pagination
|
||||
* @param size Number of items to retrieve
|
||||
* @param before Retrieve items before this ID
|
||||
* @param after Retrieve items after this ID
|
||||
* @returns Paginated recently activities
|
||||
*/
|
||||
async getRecentlyOffset({
|
||||
size = 10,
|
||||
before,
|
||||
after,
|
||||
}: {
|
||||
size?: number
|
||||
before?: string
|
||||
after?: string
|
||||
}) {
|
||||
return await this.recentlyService.getOffset({ size, before, after })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest recently activity
|
||||
* @returns The latest recently activity
|
||||
*/
|
||||
async getLatestRecently() {
|
||||
const recently = await this.recentlyService.getLatestOne()
|
||||
|
||||
if (!recently) {
|
||||
throw new CannotFindException()
|
||||
}
|
||||
|
||||
return recently
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments with pagination
|
||||
* @param page Page number
|
||||
* @param size Page size
|
||||
* @param state Comment state filter (0 = all)
|
||||
* @returns Paginated comments
|
||||
*/
|
||||
async getComments({ page = 1, size = 10, state = 0 } = {}) {
|
||||
return await this.commentService.getComments({ page, size, state })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments for a specific content
|
||||
* @param id Content ID
|
||||
* @param type Content type (post, page, note, etc.)
|
||||
* @returns Comments for the specified content
|
||||
*/
|
||||
async getContentComments(id: string, type?: string) {
|
||||
const allowComment = await this.commentService.allowComment(id, type as any)
|
||||
|
||||
if (!allowComment) {
|
||||
return []
|
||||
}
|
||||
|
||||
const comments = await this.commentService.model.find({
|
||||
ref: id,
|
||||
})
|
||||
|
||||
await this.commentService.fillAndReplaceAvatarUrl(comments)
|
||||
|
||||
return comments
|
||||
}
|
||||
}
|
||||
50
package.json
50
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.15.9",
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
||||
"license": "AGPLv3",
|
||||
"homepage": "https://github.com/mx-space/core#readme",
|
||||
"repository": {
|
||||
@@ -24,23 +24,31 @@
|
||||
"redis-memory-server": "0.12.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"zx-cjs": "7.0.7-0"
|
||||
"zx-cjs": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@innei/prettier": "0.15.0",
|
||||
"@sxzz/eslint-config": "6.1.1",
|
||||
"@types/node": "22.14.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "^9.24.0",
|
||||
"lint-staged": "15.5.0",
|
||||
"prettier": "3.5.3",
|
||||
"rimraf": "6.0.1",
|
||||
"simple-git-hooks": "2.12.1",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"tsup": "8.4.0",
|
||||
"typescript": "5.8.3",
|
||||
"vite-tsconfig-paths": "5.1.4"
|
||||
"@innei/prettier": "catalog:",
|
||||
"@sxzz/eslint-config": "catalog:",
|
||||
"@types/node": "catalog:conflicts_@types/node_22_14_0",
|
||||
"cross-env": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"lint-staged": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"rimraf": "catalog:",
|
||||
"simple-git-hooks": "catalog:",
|
||||
"ts-node": "catalog:",
|
||||
"tsconfig-paths": "catalog:",
|
||||
"tsup": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite-tsconfig-paths": "catalog:"
|
||||
},
|
||||
"resolutions": {
|
||||
"get-pixels@^3>request": "./external/request",
|
||||
"mongodb": "6.12.0",
|
||||
"pino": "./external/pino",
|
||||
"semver": "7.7.1",
|
||||
"typescript": "5.7.3",
|
||||
"whatwg-url": "14.1.1"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
@@ -51,13 +59,5 @@
|
||||
"prettier --ignore-path ./.prettierignore --write "
|
||||
]
|
||||
},
|
||||
"issues": "https://github.com/mx-space/core/issues",
|
||||
"resolutions": {
|
||||
"get-pixels@^3>request": "./external/request",
|
||||
"mongodb": "6.12.0",
|
||||
"pino": "./external/pino",
|
||||
"semver": "7.7.1",
|
||||
"typescript": "5.7.3",
|
||||
"whatwg-url": "14.1.1"
|
||||
}
|
||||
"issues": "https://github.com/mx-space/core/issues"
|
||||
}
|
||||
@@ -6,14 +6,16 @@
|
||||
"exports": {
|
||||
".": "./dist/index.cjs",
|
||||
"./zx-global": "./zx-global.cjs",
|
||||
"./auth": "./dist/auth.cjs"
|
||||
"./auth": "./dist/auth.cjs",
|
||||
"./zod": "./dist/zod.cjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup"
|
||||
},
|
||||
"devDependencies": {
|
||||
"better-auth": "1.2.5",
|
||||
"nanoid": "5.1.5",
|
||||
"zx": "7.2.3"
|
||||
"better-auth": "catalog:",
|
||||
"nanoid": "catalog:",
|
||||
"zod": "catalog:",
|
||||
"zx": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { defineConfig } from 'tsup'
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
target: 'es2020',
|
||||
entry: ['index.ts', 'auth.ts'],
|
||||
entry: ['index.ts', 'auth.ts', 'zod.ts'],
|
||||
dts: true,
|
||||
external: ['mongodb'],
|
||||
format: ['cjs'],
|
||||
|
||||
1
packages/compiled/zod.ts
Normal file
1
packages/compiled/zod.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './node_modules/zod'
|
||||
1452
pnpm-lock.yaml
generated
1452
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,136 @@
|
||||
packages:
|
||||
- packages/*
|
||||
- apps/*
|
||||
catalog:
|
||||
'@algolia/client-search': ^4.22.1
|
||||
'@antfu/install-pkg': 1.0.0
|
||||
'@aws-sdk/client-s3': 3.802.0
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/plugin-transform-modules-commonjs': 7.27.1
|
||||
'@babel/plugin-transform-typescript': 7.27.1
|
||||
'@babel/types': ^7.27.1
|
||||
'@fastify/cookie': 11.0.2
|
||||
'@fastify/multipart': 9.0.3
|
||||
'@fastify/static': 8.1.1
|
||||
'@innei/next-async': 0.3.0
|
||||
'@innei/prettier': 0.15.0
|
||||
'@innei/pretty-logger-nestjs': 0.3.3
|
||||
'@keyv/redis': 4.4.0
|
||||
'@langchain/core': 0.3.51
|
||||
'@langchain/openai': 0.5.10
|
||||
'@modelcontextprotocol/sdk': 1.11.0
|
||||
'@nestjs/cache-manager': 3.0.1
|
||||
'@nestjs/cli': 11.0.7
|
||||
'@nestjs/common': 11.1.0
|
||||
'@nestjs/core': 11.1.0
|
||||
'@nestjs/event-emitter': 3.0.1
|
||||
'@nestjs/mapped-types': ^2.1.0
|
||||
'@nestjs/platform-fastify': 11.1.0
|
||||
'@nestjs/platform-socket.io': 11.1.0
|
||||
'@nestjs/schedule': 6.0.0
|
||||
'@nestjs/schematics': 11.0.5
|
||||
'@nestjs/testing': 11.1.0
|
||||
'@nestjs/throttler': 6.4.0
|
||||
'@nestjs/websockets': 11.1.0
|
||||
'@simplewebauthn/server': 10.0.1
|
||||
'@socket.io/redis-adapter': 8.3.0
|
||||
'@socket.io/redis-emitter': 5.1.0
|
||||
'@swc/core': 1.11.24
|
||||
'@sxzz/eslint-config': 6.1.1
|
||||
'@typegoose/auto-increment': 4.13.0
|
||||
'@typegoose/typegoose': 12.15.0
|
||||
'@types/babel__core': 7.20.5
|
||||
'@types/bcryptjs': ^3.0.0
|
||||
'@types/cls-hooked': ^4.3.9
|
||||
'@types/ejs': 3.1.5
|
||||
'@types/get-image-colors': 4.0.5
|
||||
'@types/js-yaml': 4.0.9
|
||||
'@types/jsonwebtoken': 9.0.9
|
||||
'@types/lodash': 4.17.16
|
||||
'@types/mime-types': 2.1.4
|
||||
'@types/mongoose-aggregate-paginate-v2': 1.0.12
|
||||
'@types/node': 22.15.3
|
||||
'@types/nodemailer': 6.4.17
|
||||
'@types/qs': 6.9.18
|
||||
'@types/remove-markdown': 0.3.4
|
||||
'@types/semver': 7.7.0
|
||||
'@types/ua-parser-js': 0.7.39
|
||||
'@types/validator': 13.15.0
|
||||
'@vercel/ncc': 0.38.3
|
||||
algoliasearch: 4.24.0
|
||||
axios: ^1.9.0
|
||||
axios-retry: 4.5.0
|
||||
bcryptjs: ^3.0.2
|
||||
better-auth: 1.2.5
|
||||
blurhash: 2.0.5
|
||||
cache-manager: 6.4.2
|
||||
class-transformer: 0.5.1
|
||||
class-validator: 0.13.2
|
||||
class-validator-jsonschema: npm:@innei/class-validator-jsonschema@3.1.2
|
||||
cls-hooked: ^4.2.2
|
||||
commander: 13.1.0
|
||||
cron: ^3.5.0
|
||||
cross-env: 7.0.3
|
||||
dayjs: 1.11.13
|
||||
ejs: 3.1.10
|
||||
eslint: ^9.24.0
|
||||
form-data: 4.0.2
|
||||
inquirer: ^10.2.2
|
||||
ioredis: 5.6.1
|
||||
isbot: 5.1.27
|
||||
js-yaml: ^4.1.0
|
||||
json5: 2.2.3
|
||||
jsonwebtoken: 9.0.2
|
||||
jszip: 3.10.1
|
||||
keyv: 5.3.3
|
||||
langchain: 0.3.24
|
||||
linkedom: 0.18.10
|
||||
lint-staged: 15.5.0
|
||||
lodash: ^4.17.21
|
||||
lru-cache: 11.1.0
|
||||
marked: 15.0.11
|
||||
mime-types: ^3.0.1
|
||||
mkdirp: ^3.0.1
|
||||
mongoose: 8.14.1
|
||||
mongoose-aggregate-paginate-v2: 1.1.4
|
||||
mongoose-autopopulate: 1.1.0
|
||||
mongoose-lean-getters: 2.2.1
|
||||
mongoose-lean-virtuals: 1.1.0
|
||||
mongoose-paginate-v2: 1.9.0
|
||||
nanoid: 5.1.5
|
||||
node-machine-id: 1.1.12
|
||||
nodemailer: 7.0.0
|
||||
openai: 4.97.0
|
||||
pluralize: ^8.0.0
|
||||
prettier: 3.5.3
|
||||
qs: 6.14.0
|
||||
reflect-metadata: 0.2.2
|
||||
remove-markdown: 0.6.2
|
||||
remove-md-codeblock: 0.0.4
|
||||
rimraf: 6.0.1
|
||||
rxjs: 7.8.2
|
||||
semver: 7.7.1
|
||||
sharp: 0.34.1
|
||||
simple-git-hooks: 2.12.1
|
||||
slugify: 1.6.6
|
||||
snakecase-keys: 6.0.0
|
||||
socket.io: ^4.8.1
|
||||
source-map-support: ^0.5.21
|
||||
ts-node: 10.9.2
|
||||
tsconfig-paths: 4.2.0
|
||||
tsup: 8.4.0
|
||||
typescript: 5.8.3
|
||||
ua-parser-js: 2.0.3
|
||||
unplugin-swc: 1.5.2
|
||||
vite: 5.4.10
|
||||
vite-tsconfig-paths: 5.1.4
|
||||
vitest: 1.5.2
|
||||
vm2: 3.9.19
|
||||
wildcard-match: 5.1.4
|
||||
xss: 1.0.15
|
||||
zod: 3.24.3
|
||||
zx: 7.2.3
|
||||
zx-cjs: 7.0.7-0
|
||||
catalogs:
|
||||
conflicts_@types/node_22_14_0:
|
||||
'@types/node': 22.14.0
|
||||
|
||||
Reference in New Issue
Block a user