wuyi 2 năm trước cách đây
mục cha
commit
f7a55ecd66
41 tập tin đã thay đổi với 1901 bổ sung93 xóa
  1. 12 3
      .env
  2. 8 3
      .env.production
  3. 4 0
      .vscode/settings.json
  4. 12 1
      fix.js
  5. 47 0
      langChain.mjs
  6. 8 0
      package.json
  7. 4 3
      src/api-users/api-user.controller.ts
  8. 6 3
      src/app.module.ts
  9. 2 1
      src/auth/auth.service.ts
  10. 3 2
      src/auth/jwt.strategy.ts
  11. 11 9
      src/chat-pdf/chat-pdf.service.ts
  12. 16 1
      src/chat/chat.controller.ts
  13. 11 3
      src/chat/chat.module.ts
  14. 44 20
      src/chat/chat.service.ts
  15. 3 2
      src/chat/chatgpt/index.ts
  16. 2 1
      src/file/file.module.ts
  17. 11 0
      src/file/file.service.ts
  18. 20 0
      src/knowledge-base/entities/knowledge-base.entity.ts
  19. 40 0
      src/knowledge-base/entities/knowledge-embedding.entity.ts
  20. 41 0
      src/knowledge-base/entities/knowledge-file.entity.ts
  21. 6 0
      src/knowledge-base/enums/file-status.enum.ts
  22. 50 0
      src/knowledge-base/knowledge-base.controller.ts
  23. 16 0
      src/knowledge-base/knowledge-base.module.ts
  24. 477 0
      src/knowledge-base/knowledge-base.service.ts
  25. 2 1
      src/model/role.enum.ts
  26. 22 0
      src/org/entities/org-user.entity.ts
  27. 37 0
      src/org/entities/org.entity.ts
  28. 46 0
      src/org/org.admin.controller.ts
  29. 72 0
      src/org/org.controller.ts
  30. 16 0
      src/org/org.module.ts
  31. 296 0
      src/org/org.service.ts
  32. 0 6
      src/redis.ts
  33. 7 2
      src/users/dto/user-create.dto.ts
  34. 44 1
      src/users/dto/user-update.dto.ts
  35. 4 1
      src/users/entities/users.entity.ts
  36. 22 2
      src/users/users.admin.controller.ts
  37. 1 1
      src/users/users.controller.ts
  38. 55 10
      src/users/users.service.ts
  39. 0 0
      src/utils/pgvector/index.ts
  40. 13 13
      tsconfig.json
  41. 410 4
      yarn.lock

+ 12 - 3
.env

@@ -48,9 +48,16 @@ ALIYUN_OSS_CDN=https://cdn.raex.vip
 ALIYUN_SMS_SIGN=走马信息
 ALIYUN_SMS_TEMPLATE_CODE=SMS_175485688
 
-AZURE_OPENAI_KEY=beb32e4625a94b65ba8bc0ba1688c4d2
-AZURE_OPENAI_ENDPOINT=https://zouma.openai.azure.com
-AZURE_OPENAI_DEPLOYMENT=gpt35
+# AZURE_OPENAI_KEY=beb32e4625a94b65ba8bc0ba1688c4d2
+# AZURE_OPENAI_ENDPOINT=https://zouma.openai.azure.com
+AZURE_OPENAI_KEY=62dd8a1466524c64967810c692f0197e
+AZURE_OPENAI_ENDPOINT=https://zouma1.openai.azure.com
+AZURE_OPENAI_DEPLOYMENT=gpt-35-turbo
+AZURE_OPENAI_VERSION=2023-03-15-preview
+
+AZURE_EMBEDDING_KEY=beb32e4625a94b65ba8bc0ba1688c4d2
+AZURE_EMBEDDING_INSTANCE=zouma
+AZURE_EMBEDDING_DEPLOYMENT=embedding
 AZURE_OPENAI_VERSION=2023-03-15-preview
 
 OPENAI_API_KEY=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
@@ -71,3 +78,5 @@ PG_PORT=5432
 PG_USERNAME=postgres
 PG_PASSWORD=D$&g3a9BCJH&$Nzh
 PG_DATABASE=gpt_test
+
+UNSTRUCTURED_API=http://192.168.6.19:8000/general/v0/general

+ 8 - 3
.env.production

@@ -48,9 +48,14 @@ ALIYUN_OSS_CDN=https://cdn.raex.vip
 ALIYUN_SMS_SIGN=走马信息
 ALIYUN_SMS_TEMPLATE_CODE=SMS_175485688
 
-AZURE_OPENAI_KEY=beb32e4625a94b65ba8bc0ba1688c4d2
-AZURE_OPENAI_ENDPOINT=https://zouma.openai.azure.com
-AZURE_OPENAI_DEPLOYMENT=gpt35
+AZURE_OPENAI_KEY=62dd8a1466524c64967810c692f0197e
+AZURE_OPENAI_ENDPOINT=https://zouma1.openai.azure.com
+AZURE_OPENAI_DEPLOYMENT=gpt-35-turbo
+AZURE_OPENAI_VERSION=2023-03-15-preview
+
+AZURE_EMBEDDING_KEY=beb32e4625a94b65ba8bc0ba1688c4d2
+AZURE_EMBEDDING_INSTANCE=zouma
+AZURE_EMBEDDING_DEPLOYMENT=embedding
 AZURE_OPENAI_VERSION=2023-03-15-preview
 
 OPENAI_API_KEY=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

+ 4 - 0
.vscode/settings.json

@@ -0,0 +1,4 @@
+{
+
+    "debug.javascript.autoAttachFilter": "smart",
+}

+ 12 - 1
fix.js

@@ -3,4 +3,15 @@ const fs = require('fs')
 const package = JSON.parse(fs.readFileSync('./node_modules/bignumber.js/package.json', 'utf8'))
 package.exports['./bignumber'] = './bignumber.js'
 fs.writeFileSync('./node_modules/bignumber.js/package.json', JSON.stringify(package, null, 2))
-console.log('Fixed bignumber.js package.json')
+console.log('Fixed bignumber.js package.json')
+
+const f1 = './node_modules/langchain/dist/document_loaders/fs/unstructured.d.ts'
+const f2 = './node_modules/langchain/dist/document_loaders/fs/directory.d.ts'
+fs.writeFileSync(
+    f1,
+    fs.readFileSync(f1, 'utf8').toString().replaceAll('/// <reference types="node" resolution-mode="require"/>', '')
+)
+fs.writeFileSync(
+    f2,
+    fs.readFileSync(f2, 'utf8').toString().replaceAll('/// <reference types="node" resolution-mode="require"/>', '')
+)

+ 47 - 0
langChain.mjs

@@ -0,0 +1,47 @@
+import { ChatOpenAI } from 'langchain/chat_models/openai'
+import { HumanMessage, ChatMessage, SystemMessage } from 'langchain/schema'
+import { PromptTemplate } from 'langchain/prompts'
+import { LLMChain } from 'langchain/chains'
+import { PDFLoader } from 'langchain/document_loaders/fs/pdf'
+import { UnstructuredLoader } from 'langchain/document_loaders/fs/unstructured'
+import { config } from 'dotenv'
+import { BufferMemory } from 'langchain/memory'
+import { RedisChatMessageHistory } from 'langchain/stores/message/ioredis'
+import { ConversationChain } from 'langchain/chains'
+import { OpenAIEmbeddings } from 'langchain/embeddings/openai'
+import { TypeORMVectorStore } from 'langchain/vectorstores/typeorm'
+
+config()
+
+const loader1 = new UnstructuredLoader('/Users/drew/Downloads/客服的副本.pdf', {
+    apiUrl: 'http://192.168.6.19:8000/general/v0/general'
+})
+const docs1 = await loader1.load()
+console.log(docs1)
+
+const embeddings = new OpenAIEmbeddings({
+    azureOpenAIApiKey: 'beb32e4625a94b65ba8bc0ba1688c4d2',
+    azureOpenAIApiInstanceName: 'zouma',
+    azureOpenAIApiDeploymentName: 'embedding',
+    azureOpenAIApiVersion: '2023-03-15-preview',
+    verbose: true
+})
+
+const typeormVectorStore = await TypeORMVectorStore.fromDataSource(embeddings, {
+    postgresConnectionOptions: {
+        type: 'postgres',
+        host: process.env.PG_HOST,
+        port: process.env.PG_PORT,
+        username: process.env.PG_USERNAME,
+        password: process.env.PG_PASSWORD,
+        database: process.env.PG_DATABASE
+    },
+    verbose: true
+})
+
+await typeormVectorStore.ensureTableInDatabase()
+
+await typeormVectorStore.addDocuments(docs1)
+
+const results = await typeormVectorStore.similaritySearch('包邮', 2)
+console.log(results)

+ 8 - 0
package.json

@@ -43,6 +43,7 @@
     "@nestjs/swagger": "^6.2.1",
     "@nestjs/throttler": "^4.0.0",
     "@nestjs/typeorm": "^9.0.1",
+    "@opensearch-project/opensearch": "^2.3.1",
     "ali-oss": "^6.17.1",
     "axios": "^1.3.6",
     "azure-openai": "^0.9.4",
@@ -55,6 +56,7 @@
     "crypto": "^1.0.1",
     "date-fns": "^2.29.3",
     "dedent": "^0.7.0",
+    "dotenv": "^16.3.1",
     "eventsource-parser": "^1.0.0",
     "express-basic-auth": "^1.2.1",
     "express-handlebars": "^7.0.6",
@@ -64,12 +66,18 @@
     "ioredis": "^5.3.2",
     "isomorphic-fetch": "^3.0.0",
     "keyv": "^4.5.2",
+    "langchain": "^0.0.117",
+    "mime": "^3.0.0",
     "mongodb": "^5.2.0",
     "mongoose": "^7.0.4",
     "mysql2": "^3.1.2",
     "nestjs-typeorm-paginate": "^4.0.3",
+<<<<<<< HEAD
     "node-cron": "^3.0.2",
     "node-schedule": "^2.1.1",
+=======
+    "node-xlsx": "^0.23.0",
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
     "nodemailer": "^6.9.1",
     "p-timeout": "^6.1.1",
     "passport": "^0.6.0",

+ 4 - 3
src/api-users/api-user.controller.ts

@@ -41,9 +41,10 @@ export class ApiUserController {
     @Get('/get/:id')
     public async get(@Param('id') id: string, @Req() req) {
         const chatRole = await this.apiUserService.findById(Number(id))
-        // if (!req.user || req.user.apiUserId != chatRole.id) {
-        //     chatRole.code = ''
-        // }
+        if (!req.user || req.user.id != chatRole.userId) {
+            chatRole.code = ''
+            chatRole.publicCode = ''
+        }
         return chatRole
     }
 

+ 6 - 3
src/app.module.ts

@@ -28,6 +28,8 @@ import { CommentModule } from './comment/comment.module'
 import { ChatPdfModule } from './chat-pdf/chat-pdf.module'
 import { LikesModule } from './likes/likes.module'
 import { ConditionModule } from './condition/condition.module'
+import { OrgModule } from './org/org.module';
+import { KnowledgeBaseModule } from './knowledge-base/knowledge-base.module';
 @Module({
     imports: [
         DevtoolsModule.register({
@@ -108,14 +110,15 @@ import { ConditionModule } from './condition/condition.module'
         ChatPdfModule,
         LikesModule,
         ApiUserModule,
-        ConditionModule
+        ConditionModule,
+        OrgModule,
+        KnowledgeBaseModule
     ],
-    controllers: [],
     providers: [
         {
             provide: APP_FILTER,
             useClass: AllExceptionsFilter
-        }
+        },
     ]
 })
 export class AppModule { }

+ 2 - 1
src/auth/auth.service.ts

@@ -16,7 +16,8 @@ export class AuthService {
         }
         this.usersService.updateIat(user)
         return {
-            access_token: this.jwtService.sign(payload)
+            access_token: this.jwtService.sign(payload),
+            user
         }
     }
 

+ 3 - 2
src/auth/jwt.strategy.ts

@@ -33,14 +33,15 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
                 throw new UnauthorizedException('用户身份已过期,请重新登录')
             }
             if (payload.iat < user.iat) {
-                throw new UnauthorizedException('用户身份已过期,请重新登录')
+                //throw new UnauthorizedException('用户身份已过期,请重新登录')
             }
         }
         return {
             id: payload.sub,
             userId: payload.sub,
             username: payload.username,
-            roles: payload.roles
+            roles: payload.roles,
+            orgId: user.orgId,
         }
     }
 }

+ 11 - 9
src/chat-pdf/chat-pdf.service.ts

@@ -9,7 +9,7 @@ import * as dedent from 'dedent'
 import { DataTypes, Sequelize } from 'sequelize'
 import { ConfigService } from '@nestjs/config'
 import { ChatEmbedding } from './entities/chat-embedding.entity'
-import { VECTOR } from './pgvector'
+import { VECTOR } from '../utils/pgvector'
 import { ApiUserService } from '../api-users/api-user.service'
 import { UsersService } from 'src/users/users.service'
 import { Role } from 'src/model/role.enum'
@@ -48,7 +48,8 @@ export class ChatPdfService {
             username: configService.get<string>('PG_USERNAME'),
             password: configService.get<string>('PG_PASSWORD'),
             database: configService.get<string>('PG_DATABASE'),
-            logging: (msg) => Logger.debug(msg, 'Sequelize')
+            // logging: (msg) => Logger.debug(msg, 'Sequelize')
+            logging: false
         })
         ChatEmbedding.init(
             {
@@ -203,7 +204,7 @@ export class ChatPdfService {
             result[arg.index] = await self.getEmbedding(arg.text)
             Logger.log(`create embedding for ${arg.index + 1}/${content.length}`)
         }
-        const q = queue.promise(worker, 64)
+        const q = queue.promise(worker, 32)
         content.forEach((text, index) => {
             q.push({
                 text,
@@ -211,7 +212,7 @@ export class ChatPdfService {
             })
         })
         await q.drained()
-        return result
+        return result.filter(i=> i && i.text)
     }
 
     async getEmbedding(content: string, retry = 0) {
@@ -227,9 +228,11 @@ export class ChatPdfService {
             }
         } catch (error) {
             if (retry < 3) {
-                await setTimeout(1000)
+                Logger.error(`fetchEmbedding error: ${error.message}, retry ${retry}`, 'fetchEmbedding')
+                await setTimeout(2000)
                 return await this.getEmbedding(content, retry + 1)
             }
+            Logger.error(error.stack, 'fetchEmbedding')
             throw new InternalServerErrorException(error.message)
         }
     }
@@ -283,11 +286,11 @@ export class ChatPdfService {
 
     async getSystemConfig(userId: number, q: string, name: string, code: string, sysMsg: string) {
         const user = await this.userService.findById(userId)
-        if (!!user.apiUserId) {
+        if (!user.apiUserId) {
             return sysMsg
         }
         let apiUser = await this.apiUserService.findById(user.apiUserId)
-        if (user.roles.includes(Role.User) && !!user.apiUserId) {
+        if (user.roles.includes(Role.User)) {
             code = apiUser.publicCode
         } else {
             code = apiUser.code
@@ -301,10 +304,9 @@ export class ChatPdfService {
         //     }
         // }
         return dedent`${apiUser.desc}'
-        根据相关性从高到低排序。 '
         这是用户提出的问题:
         ${q}
-        你只能根据用户的问题,以下面的内容和之前的聊天记录为准结合你的身份进行回答:
+        你只能根据用户的问题,以下面的内容为准结合你的身份进行回答:
         \`\`\`
         ${context.join('\n')}
         \`\`\`

+ 16 - 1
src/chat/chat.controller.ts

@@ -1,10 +1,11 @@
-import { Controller, Get, Post, Req, Res, Sse, HttpCode } from '@nestjs/common'
+import { Controller, Get, Post, Req, Res, Sse, HttpCode, Query } from '@nestjs/common'
 import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'
 import { ChatService } from './chat.service'
 import { Observable } from 'rxjs'
 import { Public } from '../auth/public.decorator'
 import { HasRoles } from '../auth/roles.decorator'
 import { Role } from '../model/role.enum'
+import { format } from 'date-fns'
 
 @ApiTags('chat')
 @Controller('chat')
@@ -52,4 +53,18 @@ export class ChatController {
     public chatProxyStream(@Req() req) {
         return this.chatService.streamChatProxy(req)
     }
+
+    @Get('/getDatas')
+    public async getDatas(@Query() { apiUserId }) {
+        const apiNum = await this.chatService.getCount(apiUserId?Number(apiUserId):undefined, Role.Api)
+        const userNum = await this.chatService.getCount(apiUserId?Number(apiUserId):undefined, Role.User)
+        const todayNum = await this.chatService.getCount(apiUserId?Number(apiUserId):undefined, undefined, format(new Date(), 'yyyy-MM-dd'))
+        const week = await this.chatService.getWeekCount(apiUserId?Number(apiUserId):undefined)
+        return {
+            api: apiNum,
+            user: userNum,
+            today: todayNum,
+            week: week
+        }
+    }
 }

+ 11 - 3
src/chat/chat.module.ts

@@ -7,12 +7,20 @@ import { TokenUsage } from './entities/token-usage.entity'
 import { MembershipModule } from '../membership/membership.module'
 import { HttpModule } from '@nestjs/axios'
 import { SysConfigModule } from '../sys-config/sys-config.module'
-import {ChatPdfModule} from "../chat-pdf/chat-pdf.module";
+import { ChatPdfModule } from '../chat-pdf/chat-pdf.module'
+import { UsersModule } from '../users/users.module'
 
 @Module({
-    imports: [TypeOrmModule.forFeature([ChatHistory, TokenUsage]), MembershipModule, HttpModule, SysConfigModule, ChatPdfModule],
+    imports: [
+        TypeOrmModule.forFeature([ChatHistory, TokenUsage]),
+        MembershipModule,
+        HttpModule,
+        SysConfigModule,
+        ChatPdfModule,
+        UsersModule
+    ],
     providers: [ChatService],
     controllers: [ChatController],
     exports: [ChatService]
 })
-export class ChatModule { }
+export class ChatModule {}

+ 44 - 20
src/chat/chat.service.ts

@@ -3,11 +3,10 @@ import { Observable, interval } from 'rxjs'
 import { ChatGPTAPI, ChatMessage } from '../chatapi'
 import type { RequestProps } from './types'
 import { chatReplyProcess } from './chatgpt'
-import { Repository, MoreThanOrEqual, LessThanOrEqual, And } from 'typeorm'
+import { In, Repository, MoreThanOrEqual, LessThanOrEqual, And } from 'typeorm'
 import { ChatHistory } from './entities/chat.entity'
 import { InjectRepository } from '@nestjs/typeorm'
 import { TokenUsage } from './entities/token-usage.entity'
-import { format } from 'date-fns'
 import { MembershipService } from '../membership/membership.service'
 import { MemberType } from '../membership/entities/membership.entity'
 import { get_encoding } from '@dqbd/tiktoken'
@@ -16,6 +15,9 @@ import { HttpService } from '@nestjs/axios'
 import * as types from '../chatapi/types'
 import { SysConfigService } from '../sys-config/sys-config.service'
 import { ChatPdfService } from '../chat-pdf/chat-pdf.service'
+import { UsersService } from '../users/users.service'
+import { Role } from '../model/role.enum'
+import { startOfDay, endOfDay, addDays, format } from 'date-fns'
 
 @Injectable()
 export class ChatService {
@@ -28,7 +30,8 @@ export class ChatService {
         private readonly membershipService: MembershipService,
         private readonly httpService: HttpService,
         private readonly sysConfigService: SysConfigService,
-        private readonly chatPdfService: ChatPdfService
+        private readonly chatPdfService: ChatPdfService,
+        private readonly usersService: UsersService
     ) {}
 
     public chat(req, res): Observable<any> {
@@ -63,28 +66,21 @@ export class ChatService {
     }
 
     public async chat1(req, res) {
-        const user = this.chatPdfService.getUser(req.user.id)
         res.setHeader('Content-type', 'application/octet-stream')
         const defSysMsg = (await this.sysConfigService.findByName('system_message'))?.value
         const membership = await this.membershipService.getMembership(req.user.id)
-        const { prompt, options = {}, systemMessage, code, temperature, top_p } = req.body as RequestProps
-        if (!code) {
-            if (!membership) {
-                throw new ForbiddenException('请先成为会员')
-            }
-            if (membership.memberType == MemberType.Trial && membership.tokenLeft <= 0) {
-                throw new ForbiddenException('您的试用额度已用完,请升级会员')
-            }
-            if (membership.isExpired) {
-                throw new ForbiddenException('您的会员已过期,请即时续费')
-            }
+        const { prompt, options = {}, systemMessage, temperature, top_p } = req.body as RequestProps
+        if (!membership) {
+            throw new ForbiddenException('请先成为会员')
+        }
+        if (membership.memberType == MemberType.Trial && membership.tokenLeft <= 0) {
+            throw new ForbiddenException('您的试用额度已用完,请升级会员')
+        }
+        if (membership.isExpired) {
+            throw new ForbiddenException('您的会员已过期,请即时续费')
         }
         try {
-            let content = ''
             const promptTime = new Date()
-            if (!!(await user).apiUserId) {
-                content = await this.chatPdfService.getSystemConfig(req.user.id, prompt, code, code, systemMessage)
-            }
             let firstChunk = true
             const result = await chatReplyProcess({
                 message: prompt,
@@ -93,7 +89,7 @@ export class ChatService {
                     res.write(firstChunk ? JSON.stringify(chat) : `\n${JSON.stringify(chat)}`)
                     firstChunk = false
                 },
-                systemMessage: content || defSysMsg,
+                systemMessage: systemMessage || defSysMsg,
                 temperature,
                 top_p
             })
@@ -270,4 +266,32 @@ export class ChatService {
             date: And(MoreThanOrEqual(start || '2023-04-01'), LessThanOrEqual(end || date))
         })
     }
+
+    public async getCount(apiUserId: number, roles?: Role, date?: string) {
+        const userIds = await this.usersService.getUsers(apiUserId, roles)
+
+        return this.chatHistoryRepository.countBy({
+            userId: In(userIds),
+            role: 'user',
+            time: date
+                ? And(MoreThanOrEqual(startOfDay(new Date(date))), LessThanOrEqual(endOfDay(new Date(date))))
+                : undefined
+        })
+    }
+
+    public async getWeekCount(apiUserId: number) {
+        const date1 = new Date()
+        let dayInfo = {}
+        for (let i = 0; i < 7; i++) {
+            let date2 = format(addDays(date1, 0 - i), 'yyyy-MM-dd')
+            let api = await this.getCount(apiUserId, Role.Api, date2)
+            let user = await this.getCount(apiUserId, Role.User, date2)
+            dayInfo[format(addDays(date1, 0 - i), 'MM-dd')] = {
+                api: api,
+                user: user
+            }
+        }
+
+        return dayInfo
+    }
 }

+ 3 - 2
src/chat/chatgpt/index.ts

@@ -56,7 +56,7 @@ let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
                 options.maxResponseTokens = 2048
             }
         }
-
+        // @ts-ignore
         setupProxy(options)
 
         api = new ChatGPTAPI({ ...options })
@@ -70,7 +70,7 @@ let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
             model,
             debug: !disableDebug
         }
-
+        // @ts-ignore
         setupProxy(options)
 
         api = new ChatGPTUnofficialProxyAPI({ ...options })
@@ -172,6 +172,7 @@ async function chatConfig() {
 }
 
 function setupProxy(options: SetProxyOptions) {
+    // @ts-ignore
     options.fetch = (url, options) => {
         return fetch(url, { ...options })
     }

+ 2 - 1
src/file/file.module.ts

@@ -6,6 +6,7 @@ import { AliyunModule } from '../aliyun/aliyun.module'
 @Module({
     imports: [AliyunModule],
     providers: [FileService],
-    controllers: [FileController]
+    controllers: [FileController],
+    exports: [FileService]
 })
 export class FileModule {}

+ 11 - 0
src/file/file.service.ts

@@ -22,6 +22,17 @@ export class FileService {
         return result
     }
 
+    public async uploadBuffer(buffer: Buffer, type, ext) {
+        let path = `file/${type}/`
+        path +=
+            format(new Date(), 'yyyyMMdd/') +
+            randomstring.generate({ length: 8, charset: 'alphanumeric' }).toLowerCase() +
+            '.' +
+            ext
+        const result = await this.aliyunService.uploadFile(path, buffer)
+        return result
+    }
+
     public getFileExt(filename: string) {
         const index = filename.lastIndexOf('.')
         if (index === -1) {

+ 20 - 0
src/knowledge-base/entities/knowledge-base.entity.ts

@@ -0,0 +1,20 @@
+import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'
+
+@Entity()
+export class KnowledgeBase {
+    @PrimaryGeneratedColumn()
+    id: number
+
+    @CreateDateColumn()
+    createdAt: Date
+
+    @Column()
+    orgId: number
+
+    @Column()
+    name: string
+
+    @Column()
+    description: string
+
+}

+ 40 - 0
src/knowledge-base/entities/knowledge-embedding.entity.ts

@@ -0,0 +1,40 @@
+import { Model } from 'sequelize'
+
+export class KnowledgeEmbedding extends Model {
+    id: number
+
+    orgId: number
+
+    knowledgeId: number
+
+    fileId: number
+
+    fileHash: string
+
+    text: string
+
+    embedding: string
+
+    index: number
+
+    constructor(model?: {
+        orgId: number
+        knowledgeId: number
+        fileId: number
+        fileHash: string
+        text: string
+        embedding: string
+        index: number
+    }) {
+        super()
+        if (model) {
+            this.orgId = model.orgId
+            this.knowledgeId = model.knowledgeId
+            this.fileId = model.fileId
+            this.fileHash = model.fileHash
+            this.text = model.text
+            this.embedding = model.embedding
+            this.index = model.index
+        }
+    }
+}

+ 41 - 0
src/knowledge-base/entities/knowledge-file.entity.ts

@@ -0,0 +1,41 @@
+import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'
+import { FileStatus } from '../enums/file-status.enum'
+
+@Entity()
+export class KnowledgeFile {
+    @PrimaryGeneratedColumn()
+    id: number
+
+    @CreateDateColumn()
+    createdAt: Date
+
+    @Column()
+    orgId: number
+
+    @Column()
+    knowledgeId: number
+
+    @Column()
+    fileName: string
+
+    @Column()
+    fileHash: string
+
+    @Column()
+    fileUrl: string
+
+    @Column()
+    fileType: string
+
+    @Column()
+    size: number
+
+    @Column({ nullable: true })
+    description: string
+
+    @Column({ type: 'enum', enum: FileStatus, default: FileStatus.PENDING })
+    status: FileStatus
+
+    @Column({ nullable: true })
+    error: string
+}

+ 6 - 0
src/knowledge-base/enums/file-status.enum.ts

@@ -0,0 +1,6 @@
+export enum FileStatus {
+    PENDING = 'pending',
+    PROCESSING = 'processing',
+    DONE = 'done',
+    FAILED = 'failed'
+}

+ 50 - 0
src/knowledge-base/knowledge-base.controller.ts

@@ -0,0 +1,50 @@
+import { HasAnyRoles } from 'src/auth/roles.decorator'
+import { KnowledgeBaseService } from './knowledge-base.service'
+import { Body, Controller, Delete, Get, Param, Post, Put, UploadedFile, UseInterceptors } from '@nestjs/common'
+import { Role } from 'src/model/role.enum'
+import { PageRequest } from 'src/common/dto/page-request'
+import { KnowledgeBase } from './entities/knowledge-base.entity'
+import { KnowledgeFile } from './entities/knowledge-file.entity'
+import { FileInterceptor } from '@nestjs/platform-express'
+
+@Controller('knowledge')
+@HasAnyRoles(Role.Admin, Role.Org)
+export class KnowledgeBaseController {
+    constructor(private readonly knowledgeBaseService: KnowledgeBaseService) {}
+
+    @Post('/base')
+    async allKnowledgeBase(@Body() pageRequest: PageRequest<KnowledgeBase>) {
+        return await this.knowledgeBaseService.findAllKnowledgeBase(pageRequest)
+    }
+
+    @Put('/base')
+    async createKnowledgeBase(@Body() knowledgeBase: KnowledgeBase) {
+        return await this.knowledgeBaseService.createKnowledgeBase(knowledgeBase)
+    }
+
+    @Put('/base/:id')
+    async updateKnowledgeBase(@Body() knowledgeBase: KnowledgeBase) {
+        return await this.knowledgeBaseService.updateKnowledgeBase(knowledgeBase)
+    }
+
+    @Delete('/base/:id')
+    async deleteKnowledgeBase(@Param('id') id: string) {
+        return await this.knowledgeBaseService.deleteKnowledgeBase(Number(id))
+    }
+
+    @Post('/file')
+    async allKnowledgeFile(@Body() pageRequest: PageRequest<KnowledgeFile>) {
+        return await this.knowledgeBaseService.fileAllKnowledgeFile(pageRequest)
+    }
+
+    @Put('/base/:id/file')
+    @UseInterceptors(FileInterceptor('file'))
+    public async uploadFile(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
+        return await this.knowledgeBaseService.uploadKnowledgeFile(file, Number(id))
+    }
+
+    @Delete('/file/:id')
+    async deleteKnowledgeFile(@Param('id') id: string) {
+        return await this.knowledgeBaseService.deleteKnowledgeFile(Number(id))
+    }
+}

+ 16 - 0
src/knowledge-base/knowledge-base.module.ts

@@ -0,0 +1,16 @@
+import { Module } from '@nestjs/common'
+import { KnowledgeBaseController } from './knowledge-base.controller'
+import { KnowledgeBaseService } from './knowledge-base.service'
+import { TypeOrmModule } from '@nestjs/typeorm'
+import { KnowledgeBase } from './entities/knowledge-base.entity'
+import { KnowledgeEmbedding } from './entities/knowledge-embedding.entity'
+import { KnowledgeFile } from './entities/knowledge-file.entity'
+import { FileModule } from 'src/file/file.module'
+
+@Module({
+    imports: [TypeOrmModule.forFeature([KnowledgeBase, KnowledgeFile]), FileModule],
+    controllers: [KnowledgeBaseController],
+    providers: [KnowledgeBaseService],
+    exports: [KnowledgeBaseService]
+})
+export class KnowledgeBaseModule {}

+ 477 - 0
src/knowledge-base/knowledge-base.service.ts

@@ -0,0 +1,477 @@
+import {
+    BadRequestException,
+    ConflictException,
+    Injectable,
+    InternalServerErrorException,
+    Logger,
+    OnModuleInit
+} from '@nestjs/common'
+import { InjectRepository } from '@nestjs/typeorm'
+import { KnowledgeBase } from './entities/knowledge-base.entity'
+import { In, Repository } from 'typeorm'
+import { Tiktoken, get_encoding } from '@dqbd/tiktoken'
+import { Configuration, OpenAIApi } from 'azure-openai'
+import { DataTypes, Sequelize } from 'sequelize'
+import { ConfigService } from '@nestjs/config'
+import { KnowledgeEmbedding } from './entities/knowledge-embedding.entity'
+import { VECTOR } from '../utils/pgvector'
+import * as queue from 'fastq'
+import { setTimeout } from 'timers/promises'
+import * as PdfParse from '@cyber2024/pdf-parse-fixed'
+import { createHash } from 'crypto'
+import { PageRequest } from '../common/dto/page-request'
+import { Pagination, paginate } from 'nestjs-typeorm-paginate'
+import { KnowledgeFile } from './entities/knowledge-file.entity'
+import { FileService } from 'src/file/file.service'
+import { FileStatus } from './enums/file-status.enum'
+import xlsx from 'node-xlsx'
+import * as mime from 'mime'
+import { OpenAIEmbeddings } from 'langchain/embeddings/openai'
+import { TypeORMVectorStore } from 'langchain/vectorstores/typeorm'
+import { UnstructuredLoader } from 'langchain/document_loaders/fs/unstructured'
+import { mkdtempSync, unlinkSync, rmdirSync, writeFileSync, rmSync } from 'fs'
+import { join } from 'path'
+function formatEmbedding(embedding: number[]) {
+    return `[${embedding.join(', ')}]`
+}
+
+@Injectable()
+export class KnowledgeBaseService implements OnModuleInit {
+    private readonly tokenizer: Tiktoken
+    private readonly openai: OpenAIApi
+    private readonly embeddingApi: OpenAIApi
+    private readonly sequelize: Sequelize
+    private embeddings: OpenAIEmbeddings
+    private vectorStore: TypeORMVectorStore
+    constructor(
+        @InjectRepository(KnowledgeBase)
+        private readonly knowledgeBaseRepository: Repository<KnowledgeBase>,
+        @InjectRepository(KnowledgeFile)
+        private readonly knowledgeFileRepository: Repository<KnowledgeFile>,
+        private readonly configService: ConfigService,
+        private readonly fileService: FileService
+    ) {
+        this.tokenizer = get_encoding('cl100k_base')
+        this.openai = new OpenAIApi(
+            new Configuration({
+                apiKey: process.env.AZURE_OPENAI_KEY,
+                // add azure info into configuration
+                azure: {
+                    apiKey: process.env.AZURE_OPENAI_KEY,
+                    endpoint: process.env.AZURE_OPENAI_ENDPOINT,
+                    deploymentName: process.env.AZURE_OPENAI_DEPLOYMENT
+                }
+            })
+        )
+        this.embeddingApi = new OpenAIApi(
+            new Configuration({
+                apiKey: process.env.AZURE_EMBEDDING_KEY,
+                // add azure info into configuration
+                azure: {
+                    apiKey: process.env.AZURE_EMBEDDING_KEY,
+                    endpoint: `https://${process.env.AZURE_EMBEDDING_INSTANCE}.openai.azure.com`,
+                    deploymentName: process.env.AZURE_EMBEDDING_DEPLOYMENT
+                }
+            })
+        )
+        this.sequelize = new Sequelize({
+            dialect: 'postgres',
+            host: configService.get<string>('PG_HOST'),
+            port: configService.get<number>('PG_PORT'),
+            username: configService.get<string>('PG_USERNAME'),
+            password: configService.get<string>('PG_PASSWORD'),
+            database: configService.get<string>('PG_DATABASE'),
+            // logging: (msg) => Logger.debug(msg, 'Sequelize')
+            logging: false
+        })
+        KnowledgeEmbedding.init(
+            {
+                id: {
+                    primaryKey: true,
+                    autoIncrement: true,
+                    type: DataTypes.INTEGER
+                },
+                orgId: {
+                    type: DataTypes.INTEGER
+                },
+                knowledgeId: {
+                    type: DataTypes.INTEGER
+                },
+                fileId: {
+                    type: DataTypes.INTEGER
+                },
+                fileHash: {
+                    type: DataTypes.STRING
+                },
+                text: {
+                    type: DataTypes.TEXT({
+                        length: 'long'
+                    })
+                },
+                embedding: {
+                    type: new VECTOR(1536)
+                },
+                index: {
+                    type: DataTypes.INTEGER
+                }
+            },
+            { sequelize: this.sequelize }
+        )
+        this.sequelize.sync()
+    }
+
+    async onModuleInit() {
+        this.embeddings = new OpenAIEmbeddings({
+            azureOpenAIApiKey: process.env.AZURE_EMBEDDING_KEY,
+            azureOpenAIApiInstanceName: process.env.AZURE_EMBEDDING_INSTANCE,
+            azureOpenAIApiDeploymentName: process.env.AZURE_EMBEDDING_DEPLOYMENT,
+            azureOpenAIApiVersion: process.env.AZURE_OPENAI_VERSION,
+            verbose: true,
+            maxConcurrency: 8
+        })
+        this.vectorStore = await TypeORMVectorStore.fromDataSource(this.embeddings, {
+            postgresConnectionOptions: {
+                type: 'postgres',
+                host: process.env.PG_HOST,
+                port: Number(process.env.PG_PORT),
+                username: process.env.PG_USERNAME,
+                password: process.env.PG_PASSWORD,
+                database: process.env.PG_DATABASE
+            },
+            verbose: true
+        })
+        await this.vectorStore.ensureTableInDatabase()
+    }
+
+    async findAllKnowledgeBase(req: PageRequest<KnowledgeBase>): Promise<Pagination<KnowledgeBase>> {
+        return await paginate<KnowledgeBase>(this.knowledgeBaseRepository, req.page, req.search)
+    }
+
+    async createKnowledgeBase(knowledgeBase: Partial<KnowledgeBase>): Promise<KnowledgeBase> {
+        return await this.knowledgeBaseRepository.save(knowledgeBase)
+    }
+
+    async updateKnowledgeBase(knowledgeBase: Partial<KnowledgeBase>): Promise<KnowledgeBase> {
+        return await this.knowledgeBaseRepository.save(knowledgeBase)
+    }
+
+    async deleteKnowledgeBase(knowledgeBaseId: number): Promise<void> {
+        await this.knowledgeBaseRepository.delete(knowledgeBaseId)
+        await this.knowledgeFileRepository.delete({ knowledgeId: knowledgeBaseId })
+        await KnowledgeEmbedding.destroy({
+            where: {
+                knowledgeId: knowledgeBaseId
+            }
+        })
+    }
+
+    async getKnowledgeBaseById(knowledgeBaseId: number): Promise<KnowledgeBase> {
+        return await this.knowledgeBaseRepository.findOneOrFail({ where: { id: knowledgeBaseId } })
+    }
+
+    async fileAllKnowledgeFile(req: PageRequest<KnowledgeFile>): Promise<Pagination<KnowledgeFile>> {
+        return await paginate<KnowledgeFile>(this.knowledgeFileRepository, req.page, req.search)
+    }
+
+    async updateKnowledgeFile(knowledgeFile: Partial<KnowledgeFile>): Promise<KnowledgeFile> {
+        return await this.knowledgeFileRepository.save(knowledgeFile)
+    }
+
+    async deleteKnowledgeFile(knowledgeFileId: number): Promise<void> {
+        await this.knowledgeFileRepository.delete(knowledgeFileId)
+        await KnowledgeEmbedding.destroy({
+            where: {
+                fileId: knowledgeFileId
+            }
+        })
+    }
+
+    public async uploadKnowledgeFile(file: Express.Multer.File, knowledgeId: number) {
+        const knowledgeBase = await this.getKnowledgeBaseById(knowledgeId)
+        const { originalname, buffer, mimetype, size } = file
+        const fileName = Buffer.from(originalname, 'latin1').toString('utf8')
+        let fileHash = this.calculateMD5(buffer)
+        let knowledgeFile = await this.knowledgeFileRepository.findOneBy({
+            fileHash
+        })
+        if (knowledgeFile) {
+            throw new ConflictException(`File ${fileName} already exists`)
+        }
+        const { url: fileUrl } = await this.fileService.uploadBuffer(
+            buffer,
+            mimetype.split('/')[1],
+            mime.getExtension(mimetype)
+        )
+        knowledgeFile = new KnowledgeFile()
+        knowledgeFile.orgId = knowledgeBase.orgId
+        knowledgeFile.knowledgeId = knowledgeId
+        knowledgeFile.fileHash = fileHash
+        knowledgeFile.fileType = mimetype
+        knowledgeFile.fileName = fileName
+        knowledgeFile.size = size
+        knowledgeFile.fileUrl = fileUrl
+        await this.knowledgeFileRepository.save(knowledgeFile)
+        switch (mimetype) {
+            case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
+                this.processExcelKnowledgeFile(knowledgeFile, buffer)
+                break
+            case 'application/pdf':
+                this.processPdfKnowledgeFile(knowledgeFile, buffer)
+                // this.processPdfKnowledgeFile1(knowledgeFile, buffer)
+                break
+        }
+        return knowledgeFile
+    }
+
+    public async processPdfKnowledgeFile(knowledgeFile: KnowledgeFile, buffer: Buffer) {
+        knowledgeFile.status = FileStatus.PROCESSING
+        try {
+            await this.knowledgeFileRepository.save(knowledgeFile)
+            const pdf = await PdfParse(buffer)
+            const contents = []
+            let paragraph = ''
+            pdf.text
+                .trim()
+                .split('\n')
+                .forEach((line) => {
+                    line = line.trim()
+                    paragraph += line
+                    if (this.isFullSentence(line)) {
+                        contents.push(paragraph)
+                        paragraph = ''
+                    }
+                })
+            if (paragraph) {
+                contents.push(paragraph)
+            }
+
+            const embeddings = await this.createEmbeddings(
+                contents.map((i) => {
+                    return { text: i }
+                })
+            )
+            Logger.log(
+                `create embeddings finished, total token usage: ${embeddings.reduce((acc, cur) => acc + cur.token, 0)}`
+            )
+            await KnowledgeEmbedding.destroy({
+                where: {
+                    fileHash: knowledgeFile.fileHash
+                }
+            })
+            let i = 0
+            for (const item of embeddings) {
+                try {
+                    await KnowledgeEmbedding.create({
+                        orgId: knowledgeFile.orgId,
+                        knowledgeId: knowledgeFile.knowledgeId,
+                        fileId: knowledgeFile.id,
+                        fileHash: knowledgeFile.fileHash,
+                        text: item.text,
+                        embedding: formatEmbedding(item.embedding),
+                        index: i++
+                    })
+                } catch (error) {
+                    Logger.error(error.message)
+                }
+            }
+            knowledgeFile.status = FileStatus.DONE
+            await this.knowledgeFileRepository.save(knowledgeFile)
+        } catch (e) {
+            knowledgeFile.status = FileStatus.FAILED
+            knowledgeFile.error = e.message
+            await this.knowledgeFileRepository.save(knowledgeFile)
+        }
+    }
+
+    public async processPdfKnowledgeFile1(knowledgeFile: KnowledgeFile, buffer: Buffer) {
+        knowledgeFile.status = FileStatus.PROCESSING
+        try {
+            const tmpDir = mkdtempSync('file')
+            const tmpFile = join(tmpDir, knowledgeFile.fileName)
+            writeFileSync(tmpFile, buffer)
+            const loader = new UnstructuredLoader(tmpFile, {
+                apiUrl: process.env.UNSTRUCTURED_API,
+                strategy: 'fast'
+            })
+            const docs = await loader.load()
+            rmSync(tmpDir, { recursive: true })
+            docs.forEach((doc) => {
+                doc.metadata.orgId = knowledgeFile.orgId
+                doc.metadata.knowledgeId = knowledgeFile.knowledgeId
+                doc.metadata.fileId = knowledgeFile.id
+                doc.metadata.fileHash = knowledgeFile.fileHash
+            })
+            await this.vectorStore.addDocuments(docs)
+            knowledgeFile.status = FileStatus.DONE
+            await this.knowledgeFileRepository.save(knowledgeFile)
+        } catch (e) {
+            Logger.error(e)
+            Logger.error(e.stack)
+            knowledgeFile.status = FileStatus.FAILED
+            knowledgeFile.error = e.message
+            await this.knowledgeFileRepository.save(knowledgeFile)
+        }
+    }
+
+    public async processExcelKnowledgeFile(knowledgeFile: KnowledgeFile, buffer: Buffer) {
+        knowledgeFile.status = FileStatus.PROCESSING
+        try {
+            await this.knowledgeFileRepository.save(knowledgeFile)
+            const sheets = xlsx.parse(buffer)
+            for (let sheet of sheets) {
+                const data = sheet.data.filter((i) => i[0].length && i[1].length)
+                const contents = data.map((i) => {
+                    return {
+                        text: i[0],
+                        detail: i[1]
+                    }
+                })
+                const embeddings = await this.createEmbeddings(contents)
+                Logger.log(
+                    `create embeddings finished, total token usage: ${embeddings.reduce(
+                        (acc, cur) => acc + cur.token,
+                        0
+                    )}`
+                )
+                await KnowledgeEmbedding.destroy({
+                    where: {
+                        fileHash: knowledgeFile.fileHash
+                    }
+                })
+                let i = 0
+                for (const item of embeddings) {
+                    try {
+                        await KnowledgeEmbedding.create({
+                            orgId: knowledgeFile.orgId,
+                            knowledgeId: knowledgeFile.knowledgeId,
+                            fileId: knowledgeFile.id,
+                            fileHash: knowledgeFile.fileHash,
+                            text: item.text + '\n' + item.detail,
+                            embedding: formatEmbedding(item.embedding),
+                            index: i++
+                        })
+                    } catch (error) {
+                        Logger.error(error.message)
+                    }
+                }
+                knowledgeFile.status = FileStatus.DONE
+                await this.knowledgeFileRepository.save(knowledgeFile)
+            }
+        } catch (e) {
+            knowledgeFile.status = FileStatus.FAILED
+            knowledgeFile.error = e.message
+            await this.knowledgeFileRepository.save(knowledgeFile)
+        }
+    }
+
+    isFullSentence(str) {
+        return /[.!?。!?…;;::”’)】》」』〕〉》〗〞〟»"'\])}]+$/.test(str)
+    }
+
+    calculateMD5(buffer) {
+        const hash = createHash('md5')
+        hash.update(buffer)
+        return hash.digest('hex')
+    }
+
+    async createEmbeddings(content: { text: string }[]) {
+        const self = this
+        const result = Array(content.length)
+        async function worker(arg) {
+            result[arg.index] = {
+                ...arg,
+                ...(await self.getEmbedding(arg.text))
+            }
+            Logger.log(`create embedding for ${arg.index + 1}/${content.length}`)
+        }
+        const q = queue.promise(worker, 32)
+        content.forEach((item, index) => {
+            q.push({
+                ...item,
+                index
+            })
+        })
+        await q.drained()
+        return result.filter((i) => i && i.embedding)
+    }
+
+    async getEmbedding(content: string, retry = 0) {
+        try {
+            const response = await this.embeddingApi.createEmbedding({
+                model: 'embedding',
+                input: content
+            })
+            return {
+                text: content,
+                embedding: response.data.data[0].embedding,
+                token: response.data.usage.total_tokens
+            }
+        } catch (error) {
+            if (retry < 3) {
+                Logger.error(`fetchEmbedding error: ${error.message}, retry ${retry}`, 'fetchEmbedding')
+                await setTimeout(2000)
+                return await this.getEmbedding(content, retry + 1)
+            }
+            Logger.error(error.stack, 'fetchEmbedding')
+            throw new InternalServerErrorException(error.message)
+        }
+    }
+
+    async getKeywords(text: string) {
+        try {
+            const res = await this.openai.createChatCompletion({
+                model: 'gpt-35-turbo',
+                messages: [
+                    {
+                        role: 'user',
+                        content: `You need to extract keywords from the statement or question and return a series of keywords separated by commas.\ncontent: ${text}\nkeywords: `
+                    }
+                ]
+            })
+            return res.data.choices[0].message.content
+        } catch (error) {
+            Logger.error(error.message)
+            if (error.response) {
+                Logger.error(error.response.data)
+            }
+            throw new InternalServerErrorException(error.message)
+        }
+    }
+
+    cutContext(context: string[]) {
+        if (!context || !context.length) return []
+        let max = 4096 - 1024
+        for (let i = 0; i < context.length; i++) {
+            max -= this.tokenizer.encode(context[i]).length
+            if (max < 0) {
+                return context.slice(0, i)
+            }
+        }
+        return context
+    }
+
+    async askKnowledge(question: string, orgId: number, knowledgeId?: number, fileId?: number) {
+        const keywords = await this.getKeywords(question)
+        const { embedding: keywordEmbedding } = await this.getEmbedding(keywords)
+        const where = { orgId, knowledgeId, fileId }
+        Object.keys(where).forEach((key) => (where[key] === undefined ? delete where[key] : {}))
+        const relatedEmbeddings = await KnowledgeEmbedding.findAll({
+            where,
+            order: this.sequelize.literal(`embedding <-> '${formatEmbedding(keywordEmbedding)}'`),
+            limit: 100
+        })
+        const files = await this.knowledgeFileRepository.findBy({
+            fileHash: In([...new Set(relatedEmbeddings.map((item) => item.fileHash))])
+        })
+        const context = await this.cutContext(
+            relatedEmbeddings.map((item) => {
+                const file = files.find((i) => i.fileHash === item.fileHash)
+                if (file) {
+                    return `***\n[${file.fileName}](${file.fileUrl}):\n\n${item.text}`
+                }
+                return item.text
+            })
+        )
+        return context
+    }
+}

+ 2 - 1
src/model/role.enum.ts

@@ -1,5 +1,6 @@
 export enum Role {
     User = 'user',
     Admin = 'admin',
-    Api = 'api'
+    Api = 'api',
+    Org = 'org'
 }

+ 22 - 0
src/org/entities/org-user.entity.ts

@@ -0,0 +1,22 @@
+import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'
+
+@Entity()
+export class OrgUser {
+    @PrimaryGeneratedColumn()
+    id: number
+
+    @CreateDateColumn()
+    createdAt: Date
+
+    @Column()
+    orgId: number
+
+    @Column()
+    userId: number
+
+    @Column()
+    phone: string
+
+    @Column()
+    name: string
+}

+ 37 - 0
src/org/entities/org.entity.ts

@@ -0,0 +1,37 @@
+import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'
+
+@Entity()
+export class Org {
+    @PrimaryGeneratedColumn()
+    id: number
+
+    @CreateDateColumn()
+    createdAt: Date
+
+    @Column()
+    name: string
+
+    @Column({ nullable: true })
+    assistantName: string
+
+    @Column()
+    description: string
+
+    @Column({ nullable: true })
+    logo: string
+
+    @Column({ type: 'text' })
+    systemPrompt: string
+
+    @Column({ type: 'text' })
+    contextTemplate: string
+
+    @Column({ unique: true, length: 120 })
+    subdomain: string
+    
+    @Column({ nullable: true })
+    customDomain: string
+
+    @Column({ nullable: true })
+    questionTemplate: string
+}

+ 46 - 0
src/org/org.admin.controller.ts

@@ -0,0 +1,46 @@
+import { Body, Controller, Delete, Param, Post, Put } from '@nestjs/common'
+import { Org } from './entities/org.entity'
+import { PageRequest } from '../common/dto/page-request'
+import { OrgService } from './org.service'
+import { HasAnyRoles, HasRoles } from '../auth/roles.decorator'
+import { Role } from '../model/role.enum'
+import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'
+import { OrgUser } from './entities/org-user.entity'
+
+@ApiTags('org.admin')
+@Controller('/admin/org')
+@ApiBearerAuth()
+@HasAnyRoles(Role.Admin, Role.Org)
+export class OrgAdminController {
+    constructor(private readonly orgService: OrgService) {}
+
+    @Post('/users')
+    async orgUsers(@Body() page: PageRequest<OrgUser>) {
+        return await this.orgService.findAllUsers(page)
+    }
+
+    @Put('/users')
+    async addUsers(@Body() orgUsers: OrgUser[]) {
+        return await this.orgService.addUsers(orgUsers)
+    }
+
+    @Delete('/users/:id')
+    async removeUser(@Param('id') id) {
+        return await this.orgService.removeUser(id)
+    }
+
+    @Post()
+    async list(@Body() page: PageRequest<Org>) {
+        return await this.orgService.findAll(page)
+    }
+
+    @Put()
+    async create(@Body() org: Org) {
+        return await this.orgService.create(org)
+    }
+
+    @Put('/:orgId')
+    async update(@Body() org: Org) {
+        return await this.orgService.update(org)
+    }
+}

+ 72 - 0
src/org/org.controller.ts

@@ -0,0 +1,72 @@
+import { Body, Controller, ForbiddenException, Get, Param, Post, Put, Req, Res, Sse, Headers } from '@nestjs/common'
+import { Org } from './entities/org.entity'
+import { PageRequest } from '../common/dto/page-request'
+import { OrgService } from './org.service'
+import { HasRoles } from '../auth/roles.decorator'
+import { Role } from '../model/role.enum'
+import { Public } from 'src/auth/public.decorator'
+
+@Controller('org')
+export class OrgController {
+    constructor(private readonly orgService: OrgService) {}
+
+    @Get()
+    @Public()
+    async getOrgFromUrl(@Headers('host') host) {
+        return await this.orgService.findByUrl(host.replace(/^http(s?):\/\//, '').replace(/\/$/, ''))
+    }
+
+    @Get('/my')
+    async my(@Req() req) {
+        if (!req.user.orgId) {
+            throw new ForbiddenException('You are not a member of any organization')
+        }
+        return await this.orgService.findById(req.user.orgId)
+    }
+
+    @Get('/:id')
+    @Public()
+    async get(@Param('id') id: string) {
+        return await this.orgService.findById(Number(id))
+    }
+
+    @Put('/my')
+    async update(@Req() req, @Body() org: Org) {
+        if (!req.user.orgId) {
+            throw new ForbiddenException('You are not a member of any organization')
+        }
+        org.id = req.user.orgId
+        return await this.orgService.update(org)
+    }
+
+    @Post('/:id/ask')
+    async ask(
+        @Req() req,
+        @Param('id') id: string,
+        @Body()
+        body: {
+            prompt: string
+            options: {
+                parentMessageId?: string
+            }
+            knowledgeId?: number
+            fileId?: number
+        }
+    ) {
+        const orgId = Number(id)
+        // if (req.user.orgId != orgId) {
+        //     throw new ForbiddenException('You are not a member of this organization')
+        // }
+        return await this.orgService.ask(body.prompt, orgId, body.options?.parentMessageId, body.knowledgeId, body.fileId)
+    }
+
+    @Post('/:id/streamAsk')
+    @Sse()
+    async streamAsk(@Req() req, @Res() res, @Param('id') id: string, @Body() body: { prompt: string }) {
+        const orgId = Number(id)
+        // if (req.user.orgId != orgId) {
+        //     throw new ForbiddenException('You are not a member of this organization')
+        // }
+        await this.orgService.streamAsk(req, res, body.prompt, orgId)
+    }
+}

+ 16 - 0
src/org/org.module.ts

@@ -0,0 +1,16 @@
+import { Module } from '@nestjs/common'
+import { TypeOrmModule } from '@nestjs/typeorm'
+import { Org } from './entities/org.entity'
+import { OrgController } from './org.controller'
+import { OrgService } from './org.service'
+import { OrgAdminController } from './org.admin.controller'
+import { UsersModule } from 'src/users/users.module'
+import { OrgUser } from './entities/org-user.entity'
+import { KnowledgeBaseModule } from 'src/knowledge-base/knowledge-base.module'
+
+@Module({
+    imports: [TypeOrmModule.forFeature([Org, OrgUser]), UsersModule, KnowledgeBaseModule],
+    controllers: [OrgController, OrgAdminController],
+    providers: [OrgService]
+})
+export class OrgModule {}

+ 296 - 0
src/org/org.service.ts

@@ -0,0 +1,296 @@
+import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
+import { InjectRepository } from '@nestjs/typeorm'
+import { Org } from './entities/org.entity'
+import { Repository } from 'typeorm'
+import { PageRequest } from '../common/dto/page-request'
+import { Pagination, paginate } from 'nestjs-typeorm-paginate'
+import { OrgUser } from './entities/org-user.entity'
+import { UsersService } from 'src/users/users.service'
+import * as randomstring from 'randomstring'
+import { Role } from '../model/role.enum'
+import { KnowledgeBaseService } from 'src/knowledge-base/knowledge-base.service'
+import { OpenAIApi, Configuration } from 'azure-openai'
+import * as dedent from 'dedent'
+import { chatReplyProcess } from '../chat/chatgpt'
+import { fetchSSE } from '../chatapi/fetch-sse'
+import { v4 as uuidv4 } from 'uuid'
+import Redis from 'ioredis'
+
+@Injectable()
+export class OrgService {
+    private readonly openai: OpenAIApi
+    private readonly redis: Redis
+    constructor(
+        @InjectRepository(Org)
+        private readonly orgRepository: Repository<Org>,
+        @InjectRepository(OrgUser)
+        private readonly orgUserRepository: Repository<OrgUser>,
+        private readonly userService: UsersService,
+        private readonly knowledgeService: KnowledgeBaseService
+    ) {
+        this.openai = new OpenAIApi(
+            new Configuration({
+                apiKey: process.env.AZURE_OPENAI_KEY,
+                // add azure info into configuration
+                azure: {
+                    apiKey: process.env.AZURE_OPENAI_KEY,
+                    endpoint: process.env.AZURE_OPENAI_ENDPOINT,
+                    deploymentName: 'gpt-35-turbo-16k'
+                }
+            })
+        )
+        this.redis = new Redis(process.env.REDIS_URI)
+    }
+
+    async findByUrl(url: string): Promise<Org> {
+        const match = /^(\w+)\.org\.gpt\.izouma\.com$/.exec(url)
+        if (match) {
+            const subdomain = match[1]
+            return await this.orgRepository.findOneOrFail({
+                where: {
+                    subdomain
+                }
+            })
+        } else {
+            return await this.orgRepository.findOneOrFail({
+                where: {
+                    customDomain: url
+                }
+            })
+        }
+    }
+
+    async findById(orgId: number): Promise<Org> {
+        return await this.orgRepository.findOneOrFail({
+            where: {
+                id: orgId
+            }
+        })
+    }
+
+    async findAll(req: PageRequest<Org>): Promise<Pagination<Org>> {
+        return await paginate<Org>(this.orgRepository, req.page, req.search)
+    }
+
+    async create(org: Org): Promise<Org> {
+        return await this.orgRepository.save(org)
+    }
+
+    async update(org: Org): Promise<Org> {
+        return await this.orgRepository.save(org)
+    }
+
+    async findAllUsers(req: PageRequest<OrgUser>): Promise<Pagination<OrgUser>> {
+        return await paginate<OrgUser>(this.orgUserRepository, req.page, req.search)
+    }
+
+    async addUsers(orgUsers: OrgUser[]): Promise<void> {
+        const users = await this.userService.findByPhone(orgUsers.map((x) => x.phone))
+        for (let x of orgUsers) {
+            let user = users.find((y) => y.phone === x.phone)
+            if (!user) {
+                const name = '0x' + randomstring.generate({ length: 8, charset: 'alphanumeric' })
+                user = await this.userService.create({
+                    name: name,
+                    username: name,
+                    phone: x.phone,
+                    roles: [Role.User]
+                })
+            }
+            await this.userService.updateUser(user.id, { orgId: x.orgId })
+            x.userId = user.id
+            await this.orgUserRepository.save(x)
+        }
+    }
+
+    async removeUser(orgUserId): Promise<void> {
+        const orgUser = await this.orgUserRepository.findOneOrFail({ where: { id: orgUserId } })
+        await this.orgUserRepository.delete(orgUserId)
+        await this.userService.updateUser(orgUser.userId, { orgId: null })
+    }
+
+    async buildMessages(
+        question: string,
+        orgId: number,
+        parentMessageId?: string,
+        knowledgeId?: number,
+        fileId?: number
+    ): Promise<any[]> {
+        const org = await this.findById(orgId)
+        const context = await this.knowledgeService.askKnowledge(question, orgId, knowledgeId, fileId)
+        if (org.questionTemplate) {
+            question = org.questionTemplate.replace('${question}', question)
+        }
+        const messages = []
+        if (org.systemPrompt) {
+            messages.push({
+                role: 'system',
+                content: org.systemPrompt.replace('${context}', context.join('\n')).replace('${question}', question)
+            })
+        }
+        if (parentMessageId) {
+            let history = (await this.getChatHistory(parentMessageId)).map((i) => ({
+                role: i.role,
+                content: i.text
+            }))
+            messages.push(...history)
+        }
+        if (!/\$\{context\}/.test(org.systemPrompt)) {
+            messages.push({
+                role: 'user',
+                content: (
+                    org.contextTemplate ||
+                    dedent`You are a helpful AI article assistant. 
+                    The following are the relevant article content fragments found from the article. 
+                    The relevance is sorted from high to low. 
+                    You can only answer according to the following content:
+                    \`\`\`
+                    ${context}
+                    \`\`\`
+                    You need to carefully consider your answer to ensure that it is based on the context. 
+                    If the context does not mention the content or it is uncertain whether it is correct, 
+                    please answer "Current context cannot provide effective information.
+                    You must use Chinese to respond.`
+                )
+                    .replace('${context}', context.join('\n'))
+                    .replace('${question}', question)
+            })
+        }
+        if (!/\$\{question\}/.test('' + org.systemPrompt + org.contextTemplate)) {
+            messages.push({
+                role: 'user',
+                content: question
+            })
+        }
+        return messages
+    }
+
+    async getChatHistory(parentMessageId, history = []) {
+        let parent: any = await this.redis.get(parentMessageId)
+        if (!parent) {
+            return history
+        }
+        parent = JSON.parse(parent)
+        history.unshift(parent)
+        if (parent.parentMessageId) {
+            return await this.getChatHistory(parent.parentMessageId, history)
+        }
+        return history
+    }
+
+    async ask(question: string, orgId: number, parentMessageId?: string, knowledgeId?: number, fileId?: number) {
+        try {
+            const response = await this.openai.createChatCompletion({
+                model: 'gpt-35-turbo-16k',
+                messages: await this.buildMessages(question, orgId, parentMessageId, knowledgeId, fileId),
+                temperature: 0.1,
+            })
+            const id = uuidv4()
+            await this.redis.set(
+                id,
+                JSON.stringify({
+                    role: 'user',
+                    id,
+                    parentMessageId,
+                    text: question
+                })
+            )
+            await this.redis.set(
+                response.data.id,
+                JSON.stringify({
+                    role: 'assistant',
+                    id: response.data.id,
+                    parentMessageId: id,
+                    text: response.data.choices[0].message.content
+                })
+            )
+            return {
+                role: 'assistant',
+                id: response.data.id,
+                text: response.data.choices[0].message.content,
+                detail: response.data
+            }
+            // return { answer: response.data.choices[0].message.content }
+        } catch (error) {
+            Logger.error(error.message)
+            if (error.response) {
+                Logger.error(error.response.data)
+            }
+            throw new InternalServerErrorException(error.message)
+        }
+    }
+
+    async streamAsk(
+        req,
+        res,
+        question: string,
+        orgId: number,
+        parentMessageId?: string,
+        knowledgeId?: number,
+        fileId?: number
+    ) {
+        res.setHeader('Content-type', 'application/octet-stream')
+        try {
+            try {
+                const url = `${process.env.AZURE_OPENAI_ENDPOINT}/openai/deployments/${process.env.AZURE_OPENAI_DEPLOYMENT}/chat/completions?api-version=${process.env.AZURE_OPENAI_VERSION}`
+                let firstChunk = true
+                const result: any = {
+                    role: 'assistant',
+                    id: uuidv4(),
+                    text: ''
+                }
+                await fetchSSE(url, {
+                    body: JSON.stringify({
+                        messages: await this.buildMessages(question, orgId, parentMessageId, knowledgeId, fileId),
+                        stream: true
+                    }),
+                    headers: {
+                        'Content-Type': 'application/json',
+                        'api-key': `${process.env.AZURE_OPENAI_KEY}`
+                    },
+                    method: 'POST',
+                    onMessage: (msg: string) => {
+                        if (msg === '[DONE]') return
+                        const response = JSON.parse(msg)
+
+                        result.id = response.id
+                        const delta = response.choices[0].delta
+                        result.delta = delta.content
+                        if (delta?.content) result.text += delta.content
+
+                        if (delta.role) {
+                            result.role = delta.role
+                        }
+
+                        result.detail = response
+                        res.write(firstChunk ? JSON.stringify(result) : `\n${JSON.stringify(result)}`)
+                        firstChunk = false
+                    },
+                    onError: (err) => {
+                        res.write(JSON.stringify(err))
+                    }
+                })
+
+                // const response = await this.openai.createChatCompletion({
+                //     model: 'gpt35',
+                //     messages: [
+                //         { role: 'system', content: org.systemPrompt },
+                //         { role: 'user', content },
+                //         { role: 'user', content: question }
+                //     ]
+                // })
+                // return { answer: response.data.choices[0].message.content }
+            } catch (error) {
+                Logger.error(error.message)
+                if (error.response) {
+                    Logger.error(error.response.data)
+                }
+                throw new InternalServerErrorException(error.message)
+            }
+        } catch (e) {
+            res.write(JSON.stringify(e))
+        } finally {
+            res.end()
+        }
+    }
+}

+ 0 - 6
src/redis.ts

@@ -1,6 +0,0 @@
-const Redis = require("ioredis");
-
-// Create a Redis instance.
-// By default, it will connect to localhost:6379.
-// We are going to cover how to specify connection options soon.
-const redis = new Redis();

+ 7 - 2
src/users/dto/user-create.dto.ts

@@ -26,10 +26,15 @@ export class UserCreateDto {
     @IsNotEmpty()
     @IsString()
     @MaxLength(60)
-    password: string
+    @IsOptional()
+    password?: string
 
-    apiUserId: number
+    @IsOptional()
+    apiUserId?: number
 
     @IsArray()
     readonly roles: Role[]
+
+    @IsOptional()
+    orgId?: number
 }

+ 44 - 1
src/users/dto/user-update.dto.ts

@@ -1,4 +1,47 @@
 import { PartialType } from '@nestjs/swagger'
 import { UserCreateDto } from './user-create.dto'
+import {
+    ArrayMinSize,
+    IsArray,
+    IsEmail,
+    IsEnum,
+    IsNotEmpty,
+    IsNumber,
+    IsOptional,
+    IsString,
+    Matches,
+    MaxLength,
+    ValidateNested
+} from 'class-validator'
+import { Role } from '../../model/role.enum'
+import { Type } from 'class-transformer'
 
-export class UserUpdateDto extends PartialType(UserCreateDto) {}
+export class UserUpdateDto {
+    @IsString()
+    @MaxLength(30)
+    @IsOptional()
+    readonly name?: string
+
+    @IsString()
+    @MaxLength(40)
+    @IsOptional()
+    readonly username?: string
+
+    @IsString()
+    @IsOptional()
+    readonly avatar?: string
+
+    @IsEmail()
+    @IsString()
+    @IsOptional()
+    readonly email?: string
+
+    @IsArray()
+    @ArrayMinSize(1)
+    @IsOptional()
+    readonly roles?: Role[]
+
+    @IsNumber()
+    @IsOptional()
+    readonly orgId?: number
+}

+ 4 - 1
src/users/entities/users.entity.ts

@@ -19,7 +19,7 @@ export class Users {
     @Column({ length: 100, unique: true, nullable: true })
     email?: string
 
-    @Column({ nullable: true })
+    @Column({ nullable: true, select: false })
     @Exclude({ toPlainOnly: true })
     password?: string
 
@@ -40,4 +40,7 @@ export class Users {
 
     @Column({ nullable: true })
     apiUserId: number
+    
+    @Column({ nullable: true })
+    orgId: number
 }

+ 22 - 2
src/users/users.admin.controller.ts

@@ -9,7 +9,8 @@ import {
     Delete,
     BadRequestException,
     Req,
-    Post
+    Post,
+    Query
 } from '@nestjs/common'
 import { UsersService } from './users.service'
 import { UserProfileDto } from './dto/user-profile.dto'
@@ -23,11 +24,12 @@ import { IPaginationOptions } from 'nestjs-typeorm-paginate'
 import { PageRequest } from '../common/dto/page-request'
 import { Users } from './entities/users.entity'
 import { UserCreateDto } from './dto/user-create.dto'
+import { format } from 'date-fns'
 
 @ApiTags('users.admin')
 @Controller('/admin/users')
 @ApiBearerAuth()
-@HasAnyRoles(Role.Admin, Role.Api)
+@HasAnyRoles(Role.Admin, Role.Api, Role.Org)
 export class UsersAdminController {
     constructor(private readonly usersService: UsersService) {}
 
@@ -68,4 +70,22 @@ export class UsersAdminController {
     public async deleteUser(@Param('userId') userId: number): Promise<void> {
         await this.usersService.deleteUser(userId)
     }
+
+    @Get('/getDatas')
+    public async getDatas(@Query() { apiUserId }) {
+        const apiNum = await this.usersService.getCount(apiUserId ? Number(apiUserId) : undefined, Role.Api)
+        const userNum = await this.usersService.getCount(apiUserId ? Number(apiUserId) : undefined, Role.User)
+        const todayNum = await this.usersService.getCount(
+            apiUserId ? Number(apiUserId) : undefined,
+            undefined,
+            format(new Date(), 'yyyy-MM-dd')
+        )
+        const week = await this.usersService.getWeekCount(apiUserId ? Number(apiUserId) : undefined)
+        return {
+            api: apiNum,
+            user: userNum,
+            today: todayNum,
+            week: week
+        }
+    }
 }

+ 1 - 1
src/users/users.controller.ts

@@ -27,7 +27,7 @@ export class UsersController {
     }
 
     @Get('/get')
-    public async get(@Req() req) {
+    public async get(@Req() req): Promise<IUsers> {
         const user = await this.usersService.findById(req.user.userId)
         return user
     }

+ 55 - 10
src/users/users.service.ts

@@ -7,7 +7,7 @@ import {
     InternalServerErrorException,
     UnauthorizedException
 } from '@nestjs/common'
-import { In, Repository, UpdateResult } from 'typeorm'
+import { In, Repository, UpdateResult, MoreThanOrEqual, LessThanOrEqual, And } from 'typeorm'
 import { InjectRepository } from '@nestjs/typeorm'
 import { Users } from './entities/users.entity'
 import { IUsers } from './interfaces/users.interface'
@@ -23,6 +23,8 @@ import { paginate, Pagination } from 'nestjs-typeorm-paginate'
 import { Role } from '../model/role.enum'
 import { PageRequest } from '../common/dto/page-request'
 import { th } from 'date-fns/locale'
+import { startOfDay, endOfDay, addDays, format } from 'date-fns'
+import { where } from 'sequelize'
 
 @Injectable()
 export class UsersService {
@@ -102,7 +104,11 @@ export class UsersService {
     }
 
     public async loginAdmin(username: string, password: string): Promise<Users> {
-        let user = await this.userRepository.findOneBy({ username })
+        let user = await this.userRepository
+            .createQueryBuilder('users')
+            .where({ username })
+            .addSelect('users.password')
+            .getOne()
         if (!user) {
             throw new UnauthorizedException('用户名或密码错误')
         }
@@ -110,19 +116,13 @@ export class UsersService {
         if (!isMatch) {
             throw new UnauthorizedException('用户名或密码错误')
         }
-        if (!user.roles.includes(Role.Admin) && !user.roles.includes(Role.Api)) {
+        if (!user.roles.includes(Role.Admin) && !user.roles.includes(Role.Api) && !user.roles.includes(Role.Org)) {
             throw new UnauthorizedException('用户名或密码错误')
         }
-        if (user.roles.includes(Role.Api)) {
-            let apiUser = await this.apiUserService.findById(user.apiUserId)
-            if (apiUser.userId != user.id) {
-                throw new UnauthorizedException('用户名或密码错误')
-            }
-        }
         return user
     }
 
-    public async create(userDto: UserCreateDto): Promise<IUsers> {
+    public async create(userDto: UserCreateDto) {
         try {
             if (userDto.password) {
                 userDto.password = await this.hashingService.hash(userDto.password)
@@ -232,4 +232,49 @@ export class UsersService {
         user.iat = iat || Math.floor(new Date().getTime() / 1000) - 1
         return await this.userRepository.save(user)
     }
+
+    public async getCount(apiUserId: number, roles?: Role, date?: string) {
+        return await this.userRepository.count({
+            where: {
+                orgId: apiUserId,
+                roles: roles,
+                createdAt: date
+                    ? And(MoreThanOrEqual(startOfDay(new Date(date))), LessThanOrEqual(endOfDay(new Date(date))))
+                    : undefined
+            }
+        })
+    }
+
+    public async getWeekCount(apiUserId: number) {
+        const date1 = new Date()
+        let dayInfo = {}
+        for (let i = 0; i < 7; i++) {
+            let date2 = format(addDays(date1, 0 - i), 'yyyy-MM-dd')
+            let api = await this.getCount(apiUserId, Role.Api, date2)
+            let user = await this.getCount(apiUserId, Role.User, date2)
+            dayInfo[format(addDays(date1, 0 - i), 'MM-dd')] = {
+                api: api,
+                user: user
+            }
+        }
+
+        return dayInfo
+    }
+
+    public async getUsers(apiUserId: number, roles?: Role) {
+        let users = await this.userRepository.findBy({
+            orgId: apiUserId,
+            roles: roles
+        })
+
+        return users.map((item) => {
+            return item.id
+        })
+    }
+
+    public async findByPhone(phone: string[]) {
+        return await this.userRepository.findBy({
+            phone: In(phone)
+        })
+    }
 }

+ 0 - 0
src/chat-pdf/pgvector/index.ts → src/utils/pgvector/index.ts


+ 13 - 13
tsconfig.json

@@ -1,15 +1,15 @@
 {
-  "compilerOptions": {
-    "module": "commonjs",
-    "declaration": true,
-    "removeComments": true,
-    "emitDecoratorMetadata": true,
-    "experimentalDecorators": true,
-    "target": "es2017",
-    "sourceMap": true,
-    "outDir": "./dist",
-    "baseUrl": "./",
-    "incremental": true,
-  },
-  "exclude": ["node_modules", "dist"]
+    "compilerOptions": {
+        "module": "commonjs",
+        "declaration": true,
+        "removeComments": true,
+        "emitDecoratorMetadata": true,
+        "experimentalDecorators": true,
+        "target": "es2017",
+        "sourceMap": true,
+        "outDir": "./dist",
+        "baseUrl": "./",
+        "incremental": true
+    },
+    "exclude": ["node_modules", "dist"]
 }

+ 410 - 4
yarn.lock

@@ -150,10 +150,31 @@
     ora "5.4.1"
     rxjs "6.6.7"
 
+<<<<<<< HEAD
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4":
   version "7.21.4"
   resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.21.4.tgz"
   integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
+=======
+"@anthropic-ai/sdk@^0.5.7":
+  version "0.5.8"
+  resolved "https://registry.npmmirror.com/@anthropic-ai/sdk/-/sdk-0.5.8.tgz#cc363c9a22510cd7f5e470e68e94b843d688e3c1"
+  integrity sha512-iHenjcE2Q/az6VZiP1DueOSvKNRmxsly6Rx2yjJBoy7OBYVFGVjEdgs2mPQHtTX0ibKAR7tPq6F6MQbKDPWcKg==
+  dependencies:
+    "@types/node" "^18.11.18"
+    "@types/node-fetch" "^2.6.4"
+    abort-controller "^3.0.0"
+    agentkeepalive "^4.2.1"
+    digest-fetch "^1.3.0"
+    form-data-encoder "1.7.2"
+    formdata-node "^4.3.2"
+    node-fetch "^2.6.7"
+
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6":
+  version "7.18.6"
+  resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz"
+  integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
   dependencies:
     "@babel/highlight" "^7.18.6"
 
@@ -1135,6 +1156,17 @@
     consola "^2.15.0"
     node-fetch "^2.6.1"
 
+"@opensearch-project/opensearch@^2.3.1":
+  version "2.3.1"
+  resolved "https://registry.npmmirror.com/@opensearch-project/opensearch/-/opensearch-2.3.1.tgz#3596e2f1f0615a7555102f6f941f0e0ec645c2cd"
+  integrity sha512-Kg8tddAx6sinStnNi6IeGilfvLWlonIxaRdVNiJcNPr1yMqd0c9TSegn18zKr0Pb0IM9xBIGBSkRPuh67ZN6Hw==
+  dependencies:
+    aws4 "^1.11.0"
+    debug "^4.3.1"
+    hpagent "^1.2.0"
+    ms "^2.1.3"
+    secure-json-parse "^2.4.0"
+
 "@pkgjs/parseargs@^0.11.0":
   version "0.11.0"
   resolved "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
@@ -1434,6 +1466,14 @@
   dependencies:
     "@types/express" "*"
 
+"@types/node-fetch@^2.6.4":
+  version "2.6.4"
+  resolved "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.4.tgz#1bc3a26de814f6bf466b25aeb1473fa1afe6a660"
+  integrity sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==
+  dependencies:
+    "@types/node" "*"
+    form-data "^3.0.0"
+
 "@types/node@*", "@types/node@^18.14.1":
   version "18.16.0"
   resolved "https://registry.npmmirror.com/@types/node/-/node-18.16.0.tgz"
@@ -1449,6 +1489,11 @@
   resolved "https://registry.npmmirror.com/@types/node/-/node-14.18.42.tgz"
   integrity sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==
 
+"@types/node@^18.11.18":
+  version "18.17.1"
+  resolved "https://registry.npmmirror.com/@types/node/-/node-18.17.1.tgz#84c32903bf3a09f7878c391d31ff08f6fe7d8335"
+  integrity sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==
+
 "@types/nodemailer-express-handlebars@^4.0.2":
   version "4.0.2"
   resolved "https://registry.npmmirror.com/@types/nodemailer-express-handlebars/-/nodemailer-express-handlebars-4.0.2.tgz"
@@ -1484,6 +1529,11 @@
   resolved "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.4.tgz"
   integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
 
+"@types/retry@0.12.0":
+  version "0.12.0"
+  resolved "https://registry.npmmirror.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
+  integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
+
 "@types/semver@^7.3.12":
   version "7.3.13"
   resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.3.13.tgz"
@@ -1900,6 +1950,13 @@ abbrev@1:
   resolved "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz"
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
+abort-controller@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
+  integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
+  dependencies:
+    event-target-shim "^5.0.0"
+
 accepts@~1.3.8:
   version "1.3.8"
   resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz"
@@ -1947,6 +2004,15 @@ agentkeepalive@^3.4.1:
   dependencies:
     humanize-ms "^1.2.1"
 
+agentkeepalive@^4.2.1:
+  version "4.3.0"
+  resolved "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255"
+  integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==
+  dependencies:
+    debug "^4.1.0"
+    depd "^2.0.0"
+    humanize-ms "^1.2.1"
+
 ajv-formats@2.1.1:
   version "2.1.1"
   resolved "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz"
@@ -2182,7 +2248,7 @@ aws-sign2@~0.7.0:
   resolved "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz"
   integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==
 
-aws4@^1.8.0:
+aws4@^1.11.0, aws4@^1.8.0:
   version "1.12.0"
   resolved "https://registry.npmmirror.com/aws4/-/aws4-1.12.0.tgz"
   integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==
@@ -2283,7 +2349,12 @@ balanced-match@^1.0.0:
   resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
-base64-js@^1.3.1:
+base-64@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.npmmirror.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
+  integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
+
+base64-js@^1.3.1, base64-js@^1.5.1:
   version "1.5.1"
   resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz"
   integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -2320,11 +2391,16 @@ bignumber.js@^9.1.1:
   resolved "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.1.1.tgz"
   integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==
 
-binary-extensions@^2.0.0:
+binary-extensions@^2.0.0, binary-extensions@^2.2.0:
   version "2.2.0"
   resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
+binary-search@^1.3.5:
+  version "1.3.6"
+  resolved "https://registry.npmmirror.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c"
+  integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==
+
 bl@^4.1.0:
   version "4.1.0"
   resolved "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz"
@@ -2487,16 +2563,24 @@ callsites@^3.0.0:
   resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz"
   integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
 
+camelcase@6, camelcase@^6.2.0:
+  version "6.3.0"
+  resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz"
+  integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
 camelcase@^5.3.1:
   version "5.3.1"
   resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
+<<<<<<< HEAD
 camelcase@^6.2.0:
   version "6.3.0"
   resolved "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz"
   integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
 
+=======
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
 caniuse-lite@^1.0.30001449:
   version "1.0.30001481"
   resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz"
@@ -2718,6 +2802,19 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
+<<<<<<< HEAD
+=======
+commander@4.1.1:
+  version "4.1.1"
+  resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
+  integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+
+commander@^10.0.1:
+  version "10.0.1"
+  resolved "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
+  integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
+
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
 commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz"
@@ -2911,6 +3008,7 @@ debug@^2.2.0:
   dependencies:
     ms "2.0.0"
 
+<<<<<<< HEAD
 debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz"
@@ -2926,6 +3024,9 @@ debug@^3.1.0:
     ms "^2.1.1"
 
 debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@4, debug@4.x:
+=======
+debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
   version "4.3.4"
   resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz"
   integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -2946,6 +3047,11 @@ debug@2.6.9:
   dependencies:
     ms "2.0.0"
 
+decamelize@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+  integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
+
 dedent@^0.7.0:
   version "0.7.0"
   resolved "https://registry.npmmirror.com/dedent/-/dedent-0.7.0.tgz"
@@ -3000,7 +3106,7 @@ denque@^2.1.0:
   resolved "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz"
   integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
 
-depd@2.0.0:
+depd@2.0.0, depd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz"
   integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@@ -3038,6 +3144,14 @@ diff@^4.0.1:
   resolved "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz"
   integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
 
+digest-fetch@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.npmmirror.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661"
+  integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==
+  dependencies:
+    base-64 "^0.1.0"
+    md5 "^2.3.0"
+
 digest-header@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/digest-header/-/digest-header-1.0.0.tgz"
@@ -3069,6 +3183,11 @@ dotenv@^16.0.3, dotenv@16.0.3:
   resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.3.tgz"
   integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
 
+dotenv@^16.3.1:
+  version "16.3.1"
+  resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
+  integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
+
 dottie@^2.0.2:
   version "2.0.3"
   resolved "https://registry.npmjs.org/dottie/-/dottie-2.0.3.tgz"
@@ -3333,6 +3452,16 @@ etag@~1.8.1:
   resolved "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz"
   integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
 
+event-target-shim@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
+  integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
+
+eventemitter3@^4.0.4:
+  version "4.0.7"
+  resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
 events@^3.2.0:
   version "3.3.0"
   resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz"
@@ -3389,6 +3518,11 @@ expect@^29.0.0, expect@^29.5.0:
     jest-message-util "^29.5.0"
     jest-util "^29.5.0"
 
+expr-eval@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/expr-eval/-/expr-eval-2.0.2.tgz#fa6f044a7b0c93fde830954eb9c5b0f7fbc7e201"
+  integrity sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg==
+
 express-basic-auth@^1.2.1:
   version "1.2.1"
   resolved "https://registry.npmmirror.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz"
@@ -3597,6 +3731,11 @@ flat-cache@^3.0.4:
     flatted "^3.1.0"
     rimraf "^3.0.2"
 
+flat@^5.0.2:
+  version "5.0.2"
+  resolved "https://registry.npmmirror.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
+  integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+
 flatted@^3.1.0:
   version "3.2.7"
   resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.7.tgz"
@@ -3657,6 +3796,20 @@ fork-ts-checker-webpack-plugin@8.0.0:
     semver "^7.3.5"
     tapable "^2.2.1"
 
+form-data-encoder@1.7.2:
+  version "1.7.2"
+  resolved "https://registry.npmmirror.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040"
+  integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
+
+form-data@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.npmmirror.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
+  integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.8"
+    mime-types "^2.1.12"
+
 form-data@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz"
@@ -3675,6 +3828,14 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
+formdata-node@^4.3.2:
+  version "4.4.1"
+  resolved "https://registry.npmmirror.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2"
+  integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==
+  dependencies:
+    node-domexception "1.0.0"
+    web-streams-polyfill "4.0.0-beta.3"
+
 formidable@^2.1.2:
   version "2.1.2"
   resolved "https://registry.npmmirror.com/formidable/-/formidable-2.1.2.tgz"
@@ -4056,6 +4217,11 @@ highlight.js@^10.7.1:
   resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz"
   integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
 
+hpagent@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/hpagent/-/hpagent-1.2.0.tgz#0ae417895430eb3770c03443456b8d90ca464903"
+  integrity sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==
+
 html-escaper@^2.0.0:
   version "2.0.2"
   resolved "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz"
@@ -4289,6 +4455,11 @@ ipaddr.js@1.9.1:
   resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
   integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
 
+is-any-array@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/is-any-array/-/is-any-array-2.0.1.tgz#9233242a9c098220290aa2ec28f82ca7fa79899e"
+  integrity sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==
+
 is-arguments@^1.0.4:
   version "1.1.1"
   resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz"
@@ -4889,6 +5060,13 @@ js-sdsl@^4.1.4:
   resolved "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.4.0.tgz"
   integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==
 
+js-tiktoken@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.npmmirror.com/js-tiktoken/-/js-tiktoken-1.0.7.tgz#56933fcd2093e8304060dfde3071bda91812e6f5"
+  integrity sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw==
+  dependencies:
+    base64-js "^1.5.1"
+
 js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz"
@@ -4980,7 +5158,16 @@ jsonfile@^6.0.1:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+<<<<<<< HEAD
 jsonwebtoken@^9.0.0, jsonwebtoken@9.0.0:
+=======
+jsonpointer@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.npmmirror.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
+  integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==
+
+jsonwebtoken@9.0.0, jsonwebtoken@^9.0.0:
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
   version "9.0.0"
   resolved "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz"
   integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==
@@ -5053,6 +5240,44 @@ ko-sleep@^1.0.3:
   dependencies:
     ms "*"
 
+langchain@^0.0.117:
+  version "0.0.117"
+  resolved "https://registry.npmmirror.com/langchain/-/langchain-0.0.117.tgz#95997f372dbae7c6a839add6dc31ea19e97956ad"
+  integrity sha512-muoMhEGErDsN/D1+dUbpToo0SOBi6mz9IeG735tE57n2W7h0fUrhvmWKI66pm7uR7cE8sojBLKTjHLWZqJy6CQ==
+  dependencies:
+    "@anthropic-ai/sdk" "^0.5.7"
+    ansi-styles "^5.0.0"
+    binary-extensions "^2.2.0"
+    camelcase "6"
+    decamelize "^1.2.0"
+    expr-eval "^2.0.2"
+    flat "^5.0.2"
+    js-tiktoken "^1.0.7"
+    js-yaml "^4.1.0"
+    jsonpointer "^5.0.1"
+    langsmith "~0.0.11"
+    ml-distance "^4.0.0"
+    object-hash "^3.0.0"
+    openai "^3.3.0"
+    openapi-types "^12.1.3"
+    p-queue "^6.6.2"
+    p-retry "4"
+    uuid "^9.0.0"
+    yaml "^2.2.1"
+    zod "^3.21.4"
+    zod-to-json-schema "^3.20.4"
+
+langsmith@~0.0.11:
+  version "0.0.14"
+  resolved "https://registry.npmmirror.com/langsmith/-/langsmith-0.0.14.tgz#60351387464e095eb087a775a8964d5b5f90d688"
+  integrity sha512-56K2AkhxsbniK2beCn28zRdHtlRlnVBWnKPsjAhSRBFESNZ0X00J2BXM/Z55M5Rt+Fpud0VldK8V+F25htYTDw==
+  dependencies:
+    "@types/uuid" "^9.0.1"
+    commander "^10.0.1"
+    p-queue "^6.6.2"
+    p-retry "4"
+    uuid "^9.0.0"
+
 leven@^3.1.0:
   version "3.1.0"
   resolved "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz"
@@ -5287,6 +5512,19 @@ mime@1.6.0:
   resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
+<<<<<<< HEAD
+=======
+mime@2.6.0, mime@^2.4.5, mime@^2.5.2:
+  version "2.6.0"
+  resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
+  integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+
+mime@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
+  integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
+
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
 mimic-fn@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz"
@@ -5372,6 +5610,42 @@ mkdirp@^2.1.3:
   resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-2.1.6.tgz"
   integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
 
+ml-array-mean@^1.1.6:
+  version "1.1.6"
+  resolved "https://registry.npmmirror.com/ml-array-mean/-/ml-array-mean-1.1.6.tgz#d951a700dc8e3a17b3e0a583c2c64abd0c619c56"
+  integrity sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ==
+  dependencies:
+    ml-array-sum "^1.1.6"
+
+ml-array-sum@^1.1.6:
+  version "1.1.6"
+  resolved "https://registry.npmmirror.com/ml-array-sum/-/ml-array-sum-1.1.6.tgz#d1d89c20793cd29c37b09d40e85681aa4515a955"
+  integrity sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw==
+  dependencies:
+    is-any-array "^2.0.0"
+
+ml-distance-euclidean@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/ml-distance-euclidean/-/ml-distance-euclidean-2.0.0.tgz#3a668d236649d1b8fec96380b9435c6f42c9a817"
+  integrity sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q==
+
+ml-distance@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.npmmirror.com/ml-distance/-/ml-distance-4.0.1.tgz#4741d17a1735888c5388823762271dfe604bd019"
+  integrity sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw==
+  dependencies:
+    ml-array-mean "^1.1.6"
+    ml-distance-euclidean "^2.0.0"
+    ml-tree-similarity "^1.0.0"
+
+ml-tree-similarity@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/ml-tree-similarity/-/ml-tree-similarity-1.0.0.tgz#24705a107e32829e24d945e87219e892159c53f0"
+  integrity sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg==
+  dependencies:
+    binary-search "^1.3.5"
+    num-sort "^2.0.0"
+
 moment-timezone@^0.5.35:
   version "0.5.43"
   resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz"
@@ -5439,10 +5713,17 @@ mquery@5.0.0:
   dependencies:
     debug "4.x"
 
+<<<<<<< HEAD
 ms@*, ms@^2.0.0, ms@^2.1.1, ms@2.1.2:
   version "2.1.2"
   resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+=======
+ms@*, ms@2.1.3, ms@^2.0.0, ms@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
 
 ms@2.0.0:
   version "2.0.0"
@@ -5563,12 +5844,19 @@ node-addon-api@*, node-addon-api@^5.0.0:
   resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-5.1.0.tgz"
   integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==
 
+<<<<<<< HEAD
 node-cron@^3.0.2:
   version "3.0.2"
   resolved "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz"
   integrity sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==
   dependencies:
     uuid "8.3.2"
+=======
+node-domexception@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
+  integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
 
 node-emoji@1.11.0:
   version "1.11.0"
@@ -5599,10 +5887,24 @@ node-releases@^2.0.8:
   resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.10.tgz"
   integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
 
+<<<<<<< HEAD
 node-schedule@^2.1.1:
   version "2.1.1"
   resolved "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz"
   integrity sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==
+=======
+node-xlsx@^0.23.0:
+  version "0.23.0"
+  resolved "https://registry.npmmirror.com/node-xlsx/-/node-xlsx-0.23.0.tgz#0c4b642f9457712d68f30e1e30351d640cc37e90"
+  integrity sha512-r3KaSZSsSrK92rbPXnX/vDdxURmPPik0rjJ3A+Pybzpjyrk4G6WyGfj8JIz5dMMEpCmWVpmO4qoVPBxnpLv/8Q==
+  dependencies:
+    xlsx "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"
+
+nodemailer-express-handlebars@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.npmjs.org/nodemailer-express-handlebars/-/nodemailer-express-handlebars-6.0.0.tgz"
+  integrity sha512-xo5nVCn2GDaE8o0ppOWVq0s3Tt5xLn+R2pDROQrZALKCoP6WsEOXrNGQwH/tYJ4vucWtWd+zdcHm734S6CSA7w==
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
   dependencies:
     cron-parser "^4.2.0"
     long-timeout "0.1.1"
@@ -5647,6 +5949,11 @@ npmlog@^5.0.1:
     gauge "^3.0.0"
     set-blocking "^2.0.0"
 
+num-sort@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/num-sort/-/num-sort-2.1.0.tgz#1cbb37aed071329fdf41151258bc011898577a9b"
+  integrity sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg==
+
 oauth-sign@~0.9.0:
   version "0.9.0"
   resolved "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz"
@@ -5657,6 +5964,11 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1:
   resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz"
   integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
 
+object-hash@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
+  integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
+
 object-inspect@^1.9.0:
   version "1.12.3"
   resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz"
@@ -5683,6 +5995,19 @@ onetime@^5.1.0, onetime@^5.1.2:
   dependencies:
     mimic-fn "^2.1.0"
 
+openai@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.npmmirror.com/openai/-/openai-3.3.0.tgz#a6408016ad0945738e1febf43f2fccca83a3f532"
+  integrity sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==
+  dependencies:
+    axios "^0.26.0"
+    form-data "^4.0.0"
+
+openapi-types@^12.1.3:
+  version "12.1.3"
+  resolved "https://registry.npmmirror.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3"
+  integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==
+
 optionator@^0.8.1:
   version "0.8.3"
   resolved "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz"
@@ -5750,6 +6075,11 @@ osx-release@^1.0.0:
   dependencies:
     minimist "^1.1.0"
 
+p-finally@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+  integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
+
 p-limit@^2.2.0:
   version "2.3.0"
   resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz"
@@ -5778,6 +6108,29 @@ p-locate@^5.0.0:
   dependencies:
     p-limit "^3.0.2"
 
+p-queue@^6.6.2:
+  version "6.6.2"
+  resolved "https://registry.npmmirror.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
+  integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
+  dependencies:
+    eventemitter3 "^4.0.4"
+    p-timeout "^3.2.0"
+
+p-retry@4:
+  version "4.6.2"
+  resolved "https://registry.npmmirror.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16"
+  integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==
+  dependencies:
+    "@types/retry" "0.12.0"
+    retry "^0.13.1"
+
+p-timeout@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.npmmirror.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
+  integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
+  dependencies:
+    p-finally "^1.0.0"
+
 p-timeout@^6.1.1:
   version "6.1.1"
   resolved "https://registry.npmmirror.com/p-timeout/-/p-timeout-6.1.1.tgz"
@@ -6404,6 +6757,11 @@ retry-as-promised@^7.0.3:
   resolved "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz"
   integrity sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==
 
+retry@^0.13.1:
+  version "0.13.1"
+  resolved "https://registry.npmmirror.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
+  integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
 reusify@^1.0.4:
   version "1.0.4"
   resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz"
@@ -6521,6 +6879,7 @@ sdk-base@^2.0.1:
   dependencies:
     get-ready "~1.0.0"
 
+<<<<<<< HEAD
 semver@^5.0.1:
   version "5.7.1"
   resolved "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz"
@@ -6540,6 +6899,17 @@ semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@7.x:
   version "7.5.0"
   resolved "https://registry.npmmirror.com/semver/-/semver-7.5.0.tgz"
   integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==
+=======
+secure-json-parse@^2.4.0:
+  version "2.7.0"
+  resolved "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
+  integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==
+
+semver@7.x, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8:
+  version "7.3.8"
+  resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz"
+  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
   dependencies:
     lru-cache "^6.0.0"
 
@@ -7510,6 +7880,11 @@ wcwidth@^1.0.1:
   dependencies:
     defaults "^1.0.3"
 
+web-streams-polyfill@4.0.0-beta.3:
+  version "4.0.0-beta.3"
+  resolved "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38"
+  integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==
+
 webidl-conversions@^3.0.0:
   version "3.0.1"
   resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
@@ -7699,7 +8074,15 @@ write-file-atomic@^4.0.2:
     imurmurhash "^0.1.4"
     signal-exit "^3.0.7"
 
+<<<<<<< HEAD
 xml2js@^0.4.16, xml2js@^0.4.22:
+=======
+"xlsx@https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz":
+  version "0.19.3"
+  resolved "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz#f804c1850e2da5260165db0a059dc2a6099d55f3"
+
+xml2js@^0.4.16, xml2js@^0.4.22, xml2js@^0.4.23:
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
   version "0.4.23"
   resolved "https://registry.npmmirror.com/xml2js/-/xml2js-0.4.23.tgz"
   integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
@@ -7742,6 +8125,19 @@ yaml@^1.10.0:
   resolved "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz"
   integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
 
+<<<<<<< HEAD
+=======
+yaml@^2.2.1:
+  version "2.3.1"
+  resolved "https://registry.npmmirror.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
+  integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
+
+yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1:
+  version "21.1.1"
+  resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
+  integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+>>>>>>> 9c70b579dd00d79c345cb91552da11e0ea667f25
 yargs-parser@^20.2.2:
   version "20.2.9"
   resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz"
@@ -7797,3 +8193,13 @@ yup@^1.0.0:
     tiny-case "^1.0.3"
     toposort "^2.0.2"
     type-fest "^2.19.0"
+
+zod-to-json-schema@^3.20.4:
+  version "3.21.4"
+  resolved "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz#de97c5b6d4a25e9d444618486cb55c0c7fb949fd"
+  integrity sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==
+
+zod@^3.21.4:
+  version "3.21.4"
+  resolved "https://registry.npmmirror.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
+  integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==