├── .assets
├── chat.png
├── file.jpg
├── param.png
├── qq.jpg
├── role.jpg
├── translate.png
├── web.jpg
└── wx.jpg
├── .eslintrc.js
├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── README.md
├── apps
├── server
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .prettierrc
│ ├── README.md
│ ├── nest-cli.json
│ ├── package.json
│ ├── src
│ │ ├── app.controller.ts
│ │ ├── app.module.ts
│ │ ├── app.service.ts
│ │ ├── chat
│ │ │ ├── chat.controller.ts
│ │ │ ├── chat.dto.ts
│ │ │ └── chat.service.ts
│ │ ├── global-exception.filter.ts
│ │ ├── main.ts
│ │ ├── translate
│ │ │ ├── translate.controller.ts
│ │ │ ├── translate.dto.ts
│ │ │ └── translate.service.ts
│ │ └── upload
│ │ │ ├── upload.controller.ts
│ │ │ └── upload.service.ts
│ ├── test
│ │ ├── app.e2e-spec.ts
│ │ └── jest-e2e.json
│ ├── tsconfig.build.json
│ └── tsconfig.json
└── web
│ ├── .eslintrc.js
│ ├── README.md
│ ├── global-store.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ ├── _app.page.js
│ ├── chat
│ │ ├── chat-panel.tsx
│ │ ├── config-modal.tsx
│ │ ├── index.page.tsx
│ │ ├── sidebar.tsx
│ │ └── store
│ │ │ ├── chat.ts
│ │ │ ├── index.ts
│ │ │ └── session-list.ts
│ ├── index.page.tsx
│ └── translate.page.tsx
│ ├── postcss.config.js
│ ├── public
│ ├── bg.png
│ ├── subset-Kingsoft_Cloud_Font.woff
│ └── subset-Kingsoft_Cloud_Font.woff2
│ ├── styles.css
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── utils.ts
├── ecosystem.config.js
├── package.json
├── packages
├── eslint-config-custom
│ ├── index.js
│ └── package.json
└── tsconfig
│ ├── README.md
│ ├── base.json
│ ├── nextjs.json
│ ├── package.json
│ └── react-library.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json
/.assets/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/chat.png
--------------------------------------------------------------------------------
/.assets/file.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/file.jpg
--------------------------------------------------------------------------------
/.assets/param.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/param.png
--------------------------------------------------------------------------------
/.assets/qq.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/qq.jpg
--------------------------------------------------------------------------------
/.assets/role.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/role.jpg
--------------------------------------------------------------------------------
/.assets/translate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/translate.png
--------------------------------------------------------------------------------
/.assets/web.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/web.jpg
--------------------------------------------------------------------------------
/.assets/wx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/.assets/wx.jpg
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | settings: {
6 | next: {
7 | rootDir: ["apps/*/"],
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 |
4 | .store
5 | .uploads
6 |
7 | # dependencies
8 | node_modules
9 | .pnp
10 | .pnp.js
11 |
12 | # testing
13 | coverage
14 |
15 | # next.js
16 | .next/
17 | out/
18 | build
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 |
24 | # debug
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 | .pnpm-debug.log*
29 |
30 | # local env files
31 | .env.local
32 | .env.development.local
33 | .env.test.local
34 | .env.production.local
35 |
36 | # turbo
37 | .turbo
38 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "attach",
10 | "name": "Attach NestJS WS",
11 | "port": 9229,
12 | "restart": true
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "files.associations": {
4 | "*.css": "tailwindcss"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AI 实验室
2 |
3 | AI 实验室(https://www.ailab.fit/)
4 | 是一个开源的 AI 探索广场,为 AI 从业者、研究人员或者 AI 爱好者们提供了一个可以针对 Open API 进行实验的能力,目前探索的场景有:
5 |
6 | - 实时翻译
7 | - 个人助理
8 | - AI 调参
9 | - 智能客服
10 | - 更多能力场景持续建设中...
11 |
12 | ## 功能详情
13 |
14 | ### 实时翻译
15 |
16 | 这里提供了一个利用 Open AI 的 API 进行翻译的平台,支持 `prompt` 的自定义,您可以让 AI 返回更适合自己场景的翻译方案。
17 | 与直接使用`ChatGPT`不同的是,这里对文本内容进行了自动分割,可以突破 AI 的上下文设置,您可以将大段长文本输入进来:
18 |
19 | 
20 |
21 | ### 个人助理
22 |
23 | 与`ChatGPT`类似,AI 实现了一个可供聊天的会话交互界面,可以保持会话上下文,与 AI 进行对话,只需要有`API KEY`,对网络环境无特殊要求:
24 |
25 | 
26 |
27 | #### 系统角色扮演
28 |
29 | 在助手里支持预设角色扮演,与`ChatGPT`不同的是,在整个会话过程中,AI 会保持一致的系统角色行为,而不会因上下文过长而丢失。
30 |
31 | 
32 |
33 | ### AI 调参
34 |
35 | 在会话配置界面,可以配置不同的 AI 参数,支持 OpenAI 所有模型,用于调试不同参数组合下的 AI 回复的质量是否满足要求,这在我们做应用开发的场景十分有用。
36 |
37 | 
38 |
39 | ### 智能客服
40 |
41 | 在智能助手里,还支持了基于提供语料环境进行客服问答的能力,目前支持在线网页、PDF 上传、Github 等。
42 |
43 | #### 基于网页的内容问答
44 |
45 | 输入网页可以在线解析网页内容,基于内容做问答:
46 |
47 | 
48 |
49 | #### 基于 PDF 的内容问答
50 |
51 | 上传 PDF 文件,可以基于 PDF 文件本身来进行问答:
52 |
53 | 
54 |
55 | ## 动机
56 |
57 | 2022 年 11 月 30 日,ChatGPT 发布,人工智能迎来了 Netscape Navigator 时刻。它不但可以实现多轮文本对话,也可以写代码、写营销文案、写诗歌、写商业计划书、写电影剧本。虽然并不完美、也会出错,但看起来无所不能。随着后面 GPT-4 的发布,拥有更强的推理能力,一个崭新的时代即将到来。连埃隆·马斯克都评价道:“ChatGPT 好得吓人,我们离危险的强人工智能不远了。”
58 |
59 | 2023 年 2 月 10 日,比尔盖茨在接受采访时表示,像 ChatGPT 这样的人工智能的崛起,与互联网的诞生和个人计算机的发展同样重要。
60 |
61 | 作为一名前端开发人员,我感到了深深的危机和不确定性,随着微软、Google、百度、阿里、腾讯等大公司先后入局,目前 AI 俨然已经对各行各业产生了不同的冲击,从客观来看,人工智能时代来临是必然的趋势。
62 |
63 | 相比于其他行业来说,程序员有一个天然的优势,可以用自己的编程能力更快速地连接 AI,对于 AI 的研发目前主要分为两类:
64 |
65 | - 基础研发人员,主要负责大模型开发
66 | - 应用开发人员,将 AI 落地到实际应用场景当中
67 |
68 | 第一类人员目前还是非常稀少的,需要有很强的理论研究基础,目前主要分布在一些顶尖的高校人才和大厂的少部分研究院当中。
69 |
70 | 我们绝大多数人都处于第二类人群中,不论是前端、后端、数据、算法、测试,掌握不同的编程语言和技能,可以将一些关于 AI 的想法快速落地到生产项目中。这也是十分重要的一环,从目前来看,微软和阿里先后都将它们都产品全家桶加入 AI 的能力,后续产品 AI 化也将成为必然的趋势,Maas 的时代已经到来。
71 |
72 | 作为应用开发的我们,应当坦然接受这一轮 AI 的洗礼,其实现在大模型对开发人员已经十分友好了,不需要太多地知识储备,我们可以直接用现有的技能连接 AI,占取先机。
73 |
74 | 那么为什么选择本项目呢?
75 |
76 | 对于开发人员,这个项目有如下优势:
77 |
78 | - 结合我自身 7 年的大厂研发经验,目前也是部门相关项目的落地负责人,相信能够给你带来一定的帮助。
79 | - 目前 AI 的研发生态主要集中在 Python,而想落地到产品应用,前端技术是必不可少的,目前整个 JS 生态对 AI 的探索还是偏少,本项目可以提供这方面的思路。
80 | - 后续我也会持续参与到 langchain 这类工具的建设中,在项目落地过程中也能触发更多的灵感和思路。
81 |
82 | 对于其他人员,你可以直接使用产品:
83 |
84 | - 增强版 chatGPT,基于 GPT 的能力,提供了调参、智能客服、论文/pdf 阅读、网页理解、智能翻译等多种工具,更多的能力还在支持中。
85 | - 无需复杂的网络环境,只需要 Open AI 的 Token 即可访问。
86 |
87 | 本项目不以盈利为目标,有一个真诚的愿景:建立起 AI 应用探索的生态,帮助大家快速将 AI 想法落地,结合社区地力量让开发更美好。欢迎有志之士加入共建!
88 |
89 | ## 后续规划
90 |
91 | - 高级版的一些尝试探索(可能包括插件机制、IFTTT 等,以开放的形式接入)
92 | - 分发平台,与企微、QQ、微信机器人打通,用户可借助平台能力快速开发管理机器人,形成生态
93 | - 探索更多借助 AI 能做的事情
94 |
95 | ## 如何贡献
96 |
97 | 本项目采用前端最新的技术栈搭建而成,建议先了解:
98 |
99 | - pnpm(https://pnpm.io/)
100 | - turborepo(https://turbo.build/)
101 | - nextjs(https://nextjs.org/)
102 | - nestjs(https://nestjs.com/)
103 |
104 | 在项目工程运行开发环境:
105 |
106 | ```
107 | pnpm i
108 | ```
109 |
110 | ```
111 | pnpm dev
112 | ```
113 |
114 | 配置 whistle 代理规则:
115 |
116 | ```
117 | ailib.fit/api 127.0.0.1:3100
118 | ailib.fit 127.0.0.1:3000
119 | ```
120 |
121 | 浏览器访问 ailib.fit 即可。
122 |
123 | ## 更多交流
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/apps/server/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | tsconfigRootDir: __dirname,
6 | sourceType: 'module',
7 | },
8 | plugins: ['@typescript-eslint/eslint-plugin'],
9 | extends: [
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | ignorePatterns: ['.eslintrc.js'],
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/explicit-module-boundary-types': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/apps/server/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | pnpm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # OS
15 | .DS_Store
16 |
17 | # Tests
18 | /coverage
19 | /.nyc_output
20 |
21 | # IDEs and editors
22 | /.idea
23 | .project
24 | .classpath
25 | .c9/
26 | *.launch
27 | .settings/
28 | *.sublime-workspace
29 |
30 | # IDE - VSCode
31 | .vscode/*
32 | !.vscode/settings.json
33 | !.vscode/tasks.json
34 | !.vscode/launch.json
35 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/apps/server/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/apps/server/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
6 | [circleci-url]: https://circleci.com/gh/nestjs/nest
7 |
8 | A progressive Node.js framework for building efficient and scalable server-side applications.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 | ## Description
26 |
27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
28 |
29 | ## Installation
30 |
31 | ```bash
32 | $ pnpm install
33 | ```
34 |
35 | ## Running the app
36 |
37 | ```bash
38 | # development
39 | $ pnpm run start
40 |
41 | # watch mode
42 | $ pnpm run start:dev
43 |
44 | # production mode
45 | $ pnpm run start:prod
46 | ```
47 |
48 | ## Test
49 |
50 | ```bash
51 | # unit tests
52 | $ pnpm run test
53 |
54 | # e2e tests
55 | $ pnpm run test:e2e
56 |
57 | # test coverage
58 | $ pnpm run test:cov
59 | ```
60 |
61 | ## Support
62 |
63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
64 |
65 | ## Stay in touch
66 |
67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
68 | - Website - [https://nestjs.com](https://nestjs.com/)
69 | - Twitter - [@nestframework](https://twitter.com/nestframework)
70 |
71 | ## License
72 |
73 | Nest is [MIT licensed](LICENSE).
74 |
--------------------------------------------------------------------------------
/apps/server/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/nest-cli",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "src",
5 | "compilerOptions": {
6 | "deleteOutDir": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "private": true,
7 | "license": "UNLICENSED",
8 | "scripts": {
9 | "build": "nest build",
10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11 | "start": "nest start",
12 | "dev": "nest start --debug --watch",
13 | "start:debug": "nest start --debug --watch",
14 | "start:prod": "node dist/main",
15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
16 | "test": "jest",
17 | "test:watch": "jest --watch",
18 | "test:cov": "jest --coverage",
19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20 | "test:e2e": "jest --config ./test/jest-e2e.json"
21 | },
22 | "dependencies": {
23 | "@dqbd/tiktoken": "^1.0.4",
24 | "@nestjs/common": "^9.0.0",
25 | "@nestjs/core": "^9.0.0",
26 | "@nestjs/platform-express": "^9.0.0",
27 | "@nestjs/serve-static": "^3.0.1",
28 | "cheerio": "1.0.0-rc.12",
29 | "class-transformer": "^0.5.1",
30 | "class-validator": "^0.14.0",
31 | "hnswlib-node": "^1.4.2",
32 | "langchain": "^0.0.49",
33 | "multer": "1.4.5-lts.1",
34 | "pdfjs-dist": "^3.5.141",
35 | "reflect-metadata": "^0.1.13",
36 | "rxjs": "^7.2.0"
37 | },
38 | "devDependencies": {
39 | "@nestjs/cli": "^9.0.0",
40 | "@nestjs/schematics": "^9.0.0",
41 | "@nestjs/testing": "^9.0.0",
42 | "@types/express": "^4.17.13",
43 | "@types/jest": "29.5.0",
44 | "@types/node": "18.15.11",
45 | "@types/supertest": "^2.0.11",
46 | "@typescript-eslint/eslint-plugin": "^5.0.0",
47 | "@typescript-eslint/parser": "^5.0.0",
48 | "eslint": "^8.0.1",
49 | "eslint-config-prettier": "^8.3.0",
50 | "eslint-plugin-prettier": "^4.0.0",
51 | "jest": "29.5.0",
52 | "prettier": "^2.3.2",
53 | "source-map-support": "^0.5.20",
54 | "supertest": "^6.1.3",
55 | "ts-jest": "29.0.5",
56 | "ts-loader": "^9.2.3",
57 | "ts-node": "^10.0.0",
58 | "tsconfig-paths": "4.2.0",
59 | "typescript": "^4.7.4"
60 | },
61 | "jest": {
62 | "moduleFileExtensions": [
63 | "js",
64 | "json",
65 | "ts"
66 | ],
67 | "rootDir": "src",
68 | "testRegex": ".*\\.spec\\.ts$",
69 | "transform": {
70 | "^.+\\.(t|j)s$": "ts-jest"
71 | },
72 | "collectCoverageFrom": [
73 | "**/*.(t|j)s"
74 | ],
75 | "coverageDirectory": "../coverage",
76 | "testEnvironment": "node"
77 | }
78 | }
--------------------------------------------------------------------------------
/apps/server/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 | import { AppService } from './app.service';
3 |
4 | @Controller()
5 | export class AppController {
6 | constructor(private readonly appService: AppService) {}
7 |
8 | @Get()
9 | getHello(): string {
10 | return this.appService.getHello();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/apps/server/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, ValidationPipe } from '@nestjs/common';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 | import { TranslateController } from './translate/translate.controller';
5 | import { TranslateService } from './translate/translate.service';
6 | import { APP_PIPE } from '@nestjs/core';
7 | import { ServeStaticModule } from '@nestjs/serve-static';
8 | import { join } from 'path';
9 | import { ChatController } from './chat/chat.controller';
10 | import { ChatService } from './chat/chat.service';
11 | import { UploadController } from './upload/upload.controller';
12 | import { MulterModule } from '@nestjs/platform-express';
13 |
14 | @Module({
15 | imports: [
16 | ServeStaticModule.forRoot({
17 | rootPath: join(__dirname, '../../web', 'out'),
18 | }),
19 | MulterModule.register({
20 | dest: './.uploads',
21 | }),
22 | ],
23 | controllers: [
24 | AppController,
25 | TranslateController,
26 | ChatController,
27 | UploadController,
28 | ],
29 | providers: [
30 | {
31 | provide: APP_PIPE,
32 | useClass: ValidationPipe,
33 | },
34 | AppService,
35 | TranslateService,
36 | ChatService,
37 | ],
38 | })
39 | export class AppModule {}
40 |
--------------------------------------------------------------------------------
/apps/server/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | @Injectable()
4 | export class AppService {
5 | getHello(): string {
6 | return 'Hello World!';
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/server/src/chat/chat.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Post, Res } from '@nestjs/common';
2 | import { Response } from 'express';
3 | import { ChatMessageDto } from './chat.dto';
4 | import { ChatService } from './chat.service';
5 |
6 | @Controller('chat')
7 | export class ChatController {
8 | constructor(private readonly chatService: ChatService) {}
9 |
10 | @Post()
11 | chat(@Body() data: ChatMessageDto, @Res() res: Response) {
12 | res.setHeader('Content-Type', 'text/event-stream');
13 | res.setHeader('Cache-Control', 'no-cache');
14 | res.setHeader('Connection', 'keep-alive');
15 | res.flushHeaders(); // 发送这些头信息到客户端
16 |
17 | const observable = this.chatService.sendMessage(data);
18 | observable.subscribe((token) => {
19 | res.write(`${token}`);
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apps/server/src/chat/chat.dto.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IsArray,
3 | IsBoolean,
4 | IsNotEmpty,
5 | IsNumber,
6 | IsOptional,
7 | IsString,
8 | } from 'class-validator';
9 |
10 | export class ChatMessageDto {
11 | @IsNotEmpty()
12 | @IsString()
13 | id: string;
14 |
15 | @IsString()
16 | @IsOptional()
17 | text?: string;
18 |
19 | @IsNotEmpty()
20 | @IsString()
21 | openAIToken: string;
22 |
23 | @IsNumber()
24 | temperature: number;
25 |
26 | @IsNumber()
27 | topP: number;
28 |
29 | @IsNumber()
30 | frequencyPenalty: number;
31 |
32 | @IsNumber()
33 | presencePenalty: number;
34 |
35 | @IsNumber()
36 | n: number;
37 |
38 | @IsString()
39 | modelName: string;
40 |
41 | @IsString()
42 | @IsOptional()
43 | systemPrompt?: string;
44 |
45 | @IsArray()
46 | @IsOptional()
47 | stop?: string[];
48 |
49 | @IsNumber()
50 | @IsOptional()
51 | timeout?: number;
52 |
53 | @IsNumber()
54 | @IsOptional()
55 | maxTokens?: number;
56 |
57 | @IsBoolean()
58 | @IsOptional()
59 | isAbstract?: boolean;
60 |
61 | @IsString()
62 | @IsOptional()
63 | extraDataUrl?: string;
64 |
65 | @IsString()
66 | @IsOptional()
67 | extraDataGithub?: string;
68 |
69 | @IsString()
70 | @IsOptional()
71 | githubToken?: string;
72 |
73 | @IsArray()
74 | messageList: Array;
75 |
76 | @IsString()
77 | @IsOptional()
78 | filename?: string;
79 |
80 | @IsString()
81 | @IsOptional()
82 | fileMimeType?: string;
83 | }
84 |
--------------------------------------------------------------------------------
/apps/server/src/chat/chat.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { ChatOpenAI } from 'langchain/chat_models';
3 | import { CallbackManager } from 'langchain/callbacks';
4 | import { Observable, Subscriber } from 'rxjs';
5 | import { ChatMessageDto } from './chat.dto';
6 | import { ConversationalRetrievalQAChain } from 'langchain/chains';
7 | import {
8 | CheerioWebBaseLoader,
9 | GithubRepoLoader,
10 | PDFLoader,
11 | TextLoader,
12 | } from 'langchain/document_loaders';
13 | import * as fs from 'fs';
14 | import { HNSWLib } from 'langchain/vectorstores';
15 | import { OpenAIEmbeddings } from 'langchain/embeddings';
16 |
17 | import * as crypto from 'crypto';
18 | import { OpenAIChat } from 'langchain/llms';
19 | import {
20 | AIChatMessage,
21 | HumanChatMessage,
22 | SystemChatMessage,
23 | } from 'langchain/schema';
24 |
25 | // 提问模板
26 | const questionGeneratorTemplate = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question(use chinese).
27 |
28 | Chat History:
29 | {chat_history}
30 | Follow Up Input: {question}
31 | Standalone question:`;
32 |
33 | // 回答模板
34 | const qaTemplate = `Use the following pieces of context to answer the question at the end(use chinese)
35 |
36 | {context}
37 |
38 | Question: {question}
39 | Helpful Answer:`;
40 |
41 | @Injectable()
42 | export class ChatService {
43 | sendMessage(data: ChatMessageDto) {
44 | let observer: Subscriber = null;
45 | const observable = new Observable((ob) => {
46 | observer = ob;
47 |
48 | const callbackManager = CallbackManager.fromHandlers({
49 | async handleLLMStart(llm, prompts) {
50 | console.log('handleLLMStart', prompts);
51 | },
52 | async handleLLMNewToken(token: string) {
53 | observer.next(token);
54 | },
55 | });
56 |
57 | if (data.extraDataUrl || data.filename || data.extraDataGithub) {
58 | this.qaWithContent(data, callbackManager);
59 | } else {
60 | this.chat(data, callbackManager);
61 | }
62 | });
63 |
64 | return observable;
65 | }
66 |
67 | private async chat(data: ChatMessageDto, callbackManager: CallbackManager) {
68 | const model = new ChatOpenAI({
69 | streaming: true,
70 | openAIApiKey: data.openAIToken,
71 | callbackManager,
72 | ...data,
73 | });
74 |
75 | model.call([
76 | new SystemChatMessage(
77 | data.systemPrompt +
78 | 'The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.',
79 | ),
80 | ...data.messageList.map((m) => {
81 | if (m.type === 0) {
82 | return new HumanChatMessage(m.text);
83 | }
84 | return new AIChatMessage(m.text);
85 | }),
86 | new HumanChatMessage(data.text),
87 | ]);
88 | }
89 |
90 | private async qaWithContent(
91 | data: ChatMessageDto,
92 | callbackManager: CallbackManager,
93 | ) {
94 | let loader;
95 | if (data.filename) {
96 | loader = data.fileMimeType.includes('pdf')
97 | ? new PDFLoader('./.uploads/' + data.filename, {
98 | pdfjs: () => import('pdfjs-dist/legacy/build/pdf.js'),
99 | })
100 | : new TextLoader('./.uploads/' + data.filename);
101 | } else if (data.extraDataGithub) {
102 | loader = new GithubRepoLoader(data.extraDataGithub, {
103 | accessToken: data.githubToken,
104 | });
105 | } else {
106 | loader = new CheerioWebBaseLoader(data.extraDataUrl);
107 | }
108 | const docs = await loader.loadAndSplit();
109 |
110 | const path =
111 | './.store/' +
112 | crypto
113 | .createHash('md5')
114 | .update(data.filename || data.extraDataGithub || data.extraDataUrl)
115 | .digest('hex');
116 |
117 | let vectorStore;
118 | if (fs.existsSync(path)) {
119 | vectorStore = await HNSWLib.load(
120 | path,
121 | new OpenAIEmbeddings({ openAIApiKey: data.openAIToken }),
122 | );
123 | } else {
124 | vectorStore = await HNSWLib.fromDocuments(
125 | docs,
126 | new OpenAIEmbeddings({ openAIApiKey: data.openAIToken }),
127 | );
128 | await vectorStore.save(path);
129 | }
130 |
131 | const model = new OpenAIChat({
132 | streaming: true,
133 | openAIApiKey: data.openAIToken,
134 | callbackManager,
135 | ...data,
136 | });
137 |
138 | const chain = ConversationalRetrievalQAChain.fromLLM(
139 | model,
140 | vectorStore.asRetriever(),
141 | {
142 | qaTemplate,
143 | questionGeneratorTemplate,
144 | },
145 | );
146 |
147 | chain.call({
148 | question: data.text,
149 | chat_history: data.messageList.map((m) => m.text),
150 | });
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/apps/server/src/global-exception.filter.ts:
--------------------------------------------------------------------------------
1 | import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
2 | import { HttpException } from '@nestjs/common';
3 | @Catch()
4 | export class GlobalExceptionFilter implements ExceptionFilter {
5 | catch(exception: any, host: ArgumentsHost) {
6 | const ctx = host.switchToHttp();
7 | const response = ctx.getResponse();
8 | const request = ctx.getRequest();
9 | const status =
10 | exception instanceof HttpException ? exception.getStatus() : 500;
11 | const message =
12 | exception.getResponse()?.message ||
13 | exception.message ||
14 | 'Internal Server Error';
15 | console.error(
16 | `error, status=${status}, message=${message}, stack=${
17 | (exception as any).stack
18 | }`,
19 | );
20 | response.status(status).json({
21 | statusCode: status,
22 | timestamp: new Date().toISOString(),
23 | path: request.url,
24 | message: message,
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apps/server/src/main.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import { NestFactory } from '@nestjs/core';
3 | import { AppModule } from './app.module';
4 | import { Logger } from '@nestjs/common';
5 | import { GlobalExceptionFilter } from './global-exception.filter';
6 |
7 | async function bootstrap() {
8 | const httpsOptions = {
9 | key: fs.readFileSync('/etc/ssl/certs/ailab.fit.key'),
10 | cert: fs.readFileSync('/etc/ssl/certs/ailab.fit_bundle.crt'),
11 | };
12 | const app = await NestFactory.create(AppModule, {
13 | httpsOptions,
14 | });
15 | global.console = new Logger('console') as any;
16 | app.setGlobalPrefix('api');
17 | app.useGlobalFilters(new GlobalExceptionFilter());
18 | await app.listen(process.env.PORT || 3100);
19 | }
20 | bootstrap();
21 |
--------------------------------------------------------------------------------
/apps/server/src/translate/translate.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, MessageEvent, Post, Res } from '@nestjs/common';
2 | import { TranslateService } from './translate.service';
3 | import { TranslateDto } from './translate.dto';
4 | import { Response } from 'express';
5 |
6 | @Controller('translate')
7 | export class TranslateController {
8 | constructor(private readonly translateService: TranslateService) {}
9 |
10 | @Post()
11 | translate(@Body() data: TranslateDto, @Res() res: Response) {
12 | res.setHeader('Content-Type', 'text/event-stream');
13 | res.setHeader('Cache-Control', 'no-cache');
14 | res.setHeader('Connection', 'keep-alive');
15 | res.flushHeaders(); // 发送这些头信息到客户端
16 |
17 | const observable = this.translateService.translate(data);
18 | observable.subscribe((token) => {
19 | res.write(`${token}`);
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apps/server/src/translate/translate.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
2 |
3 | export class TranslateDto {
4 | @IsNotEmpty()
5 | @IsString()
6 | text: string;
7 |
8 | @IsNotEmpty()
9 | @IsString()
10 | token: string;
11 |
12 | @IsString()
13 | @IsNotEmpty()
14 | prompt?: string;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/server/src/translate/translate.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, MessageEvent } from '@nestjs/common';
2 | import { ChatOpenAI } from 'langchain/chat_models';
3 | import { HumanChatMessage, SystemChatMessage } from 'langchain/schema';
4 | import { TranslateDto } from './translate.dto';
5 | import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
6 | import { CallbackManager } from 'langchain/callbacks';
7 | import { Observable, Subscriber } from 'rxjs';
8 |
9 | @Injectable()
10 | export class TranslateService {
11 | translate(data: TranslateDto) {
12 | let observer: Subscriber = null;
13 | const observable = new Observable((ob) => {
14 | observer = ob;
15 | });
16 | const chatStreaming = new ChatOpenAI({
17 | openAIApiKey: data.token,
18 | streaming: true,
19 | callbackManager: CallbackManager.fromHandlers({
20 | async handleLLMNewToken(token: string) {
21 | observer.next(token);
22 | },
23 | }),
24 | });
25 | const splitter = new RecursiveCharacterTextSplitter({
26 | chunkSize: 2000,
27 | chunkOverlap: 100,
28 | });
29 | splitter.createDocuments([data.text]).then(async (docs) => {
30 | for (const doc of docs) {
31 | await chatStreaming.call([
32 | new SystemChatMessage('You are a helpful assistant for translating.'),
33 | new HumanChatMessage(data.prompt + doc.pageContent),
34 | ]);
35 | }
36 | });
37 |
38 | return observable;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/apps/server/src/upload/upload.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Post,
4 | UploadedFile,
5 | UseInterceptors,
6 | } from '@nestjs/common';
7 | import { FileInterceptor } from '@nestjs/platform-express';
8 |
9 | @Controller('upload')
10 | export class UploadController {
11 | @Post()
12 | @UseInterceptors(FileInterceptor('file'))
13 | async uploadFile(@UploadedFile() file) {
14 | return file;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/server/src/upload/upload.service.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/apps/server/src/upload/upload.service.ts
--------------------------------------------------------------------------------
/apps/server/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from './../src/app.module';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/apps/server/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/server/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/apps/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "es2017",
10 | "sourceMap": true,
11 | "outDir": "./dist",
12 | "baseUrl": "./",
13 | "incremental": true,
14 | "skipLibCheck": true,
15 | "strictNullChecks": false,
16 | "noImplicitAny": false,
17 | "strictBindCallApply": false,
18 | "forceConsistentCasingInFileNames": false,
19 | "noFallthroughCasesInSwitch": false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/web/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ["custom"],
4 | };
5 |
--------------------------------------------------------------------------------
/apps/web/README.md:
--------------------------------------------------------------------------------
1 | ## Getting Started
2 |
3 | First, run the development server:
4 |
5 | ```bash
6 | yarn dev
7 | ```
8 |
9 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
10 |
11 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
12 |
13 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
14 |
15 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
16 |
17 | ## Learn More
18 |
19 | To learn more about Next.js, take a look at the following resources:
20 |
21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
22 | - [Learn Next.js](https://nextjs.org/learn/foundations/about-nextjs) - an interactive Next.js tutorial.
23 |
24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
25 |
26 | ## Deploy on Vercel
27 |
28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
29 |
30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
31 |
--------------------------------------------------------------------------------
/apps/web/global-store.ts:
--------------------------------------------------------------------------------
1 | import { LocalStorageLRU } from "@cocalc/local-storage-lru";
2 |
3 | const OPEN_AI_TOKEN_KEY = "OPEN_AI_TOKEN_KEY";
4 |
5 | // 获取openai token
6 | export function getOpenAIToken() {
7 | return localStorage.getItem(OPEN_AI_TOKEN_KEY);
8 | }
9 |
10 | // 设置openai token
11 | export function setOpenAIToken(token: string) {
12 | localStorage.setItem(OPEN_AI_TOKEN_KEY, token);
13 | }
14 |
15 | export const storage = new LocalStorageLRU();
16 |
--------------------------------------------------------------------------------
/apps/web/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/apps/web/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | transpilePackages: ["ui"],
4 | pageExtensions: ["page.tsx", "page.ts", "page.jsx", "page.js"],
5 | };
6 |
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build && next export",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@ant-design/icons": "^5.0.1",
13 | "@cocalc/local-storage-lru": "^2.3.0",
14 | "@emotion/react": "^11.10.6",
15 | "@emotion/styled": "^11.10.6",
16 | "@mui/material": "^5.11.16",
17 | "@reduxjs/toolkit": "^1.9.4",
18 | "antd": "^5.4.0",
19 | "classnames": "^2.3.2",
20 | "github-markdown-css": "^5.2.0",
21 | "immer": "^10.0.0",
22 | "marked": "^4.3.0",
23 | "next": "^13.1.1",
24 | "react": "18.2.0",
25 | "react-dom": "18.2.0",
26 | "react-redux": "^8.0.5",
27 | "uuidv4": "^6.2.13"
28 | },
29 | "devDependencies": {
30 | "@babel/core": "^7.0.0",
31 | "@types/marked": "^4.0.8",
32 | "@types/node": "^17.0.12",
33 | "@types/react": "^18.0.22",
34 | "@types/react-dom": "^18.0.7",
35 | "autoprefixer": "^10.4.14",
36 | "eslint": "7.32.0",
37 | "eslint-config-custom": "workspace:*",
38 | "postcss": "^8.4.21",
39 | "tailwindcss": "^3.3.1",
40 | "tsconfig": "workspace:*",
41 | "typescript": "^4.5.3"
42 | }
43 | }
--------------------------------------------------------------------------------
/apps/web/pages/_app.page.js:
--------------------------------------------------------------------------------
1 | import "github-markdown-css/github-markdown.css";
2 | import "../styles.css";
3 |
4 | // This default export is required in a new `pages/_app.js` file.
5 | export default function MyApp({ Component, pageProps }) {
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/apps/web/pages/chat/chat-panel.tsx:
--------------------------------------------------------------------------------
1 | import { Alert, Avatar, Button, Card, Empty, Input, List } from "antd";
2 | import cn from "classnames";
3 | import { marked } from "marked";
4 | import { SessionConfig } from "./config-modal";
5 | import { useEffect, useState } from "react";
6 | import { addMessage as addMessageToStore, getMessageList } from "./store/chat";
7 | import { uuid } from "uuidv4";
8 | import { isAdvanced } from "../../utils";
9 |
10 | // 会话面板属性
11 | interface ChatPanelProps {
12 | sessionConfig?: SessionConfig;
13 | }
14 |
15 | // 消息类型
16 | enum MessageType {
17 | Send,
18 | Reply,
19 | }
20 |
21 | // 消息
22 | export interface Message {
23 | id: string;
24 | type: MessageType;
25 | text: string;
26 | }
27 |
28 | // 消息配置
29 | const messageConf = {
30 | [MessageType.Send]: {
31 | classNames: {
32 | list: ["flex-row-reverse"],
33 | avatar: ["bg-teal-600", "ml-4"],
34 | card: ["border-teal-600"],
35 | },
36 | avatar: "You",
37 | justify: "end",
38 | },
39 | [MessageType.Reply]: {
40 | classNames: {
41 | list: ["flex-row"],
42 | avatar: ["bg-indigo-600", "mr-4"],
43 | card: ["border-indigo-600"],
44 | },
45 | avatar: "AI",
46 | justify: "start",
47 | },
48 | };
49 |
50 | /**
51 | * 会话面板
52 | * @param props
53 | * @returns
54 | */
55 | export function ChatPanel(props: ChatPanelProps) {
56 | const { sessionConfig } = props;
57 | const [messageList, setMessageList] = useState([]);
58 | const [inputValue, setInputValue] = useState("");
59 |
60 | // 获取消息列表
61 | useEffect(() => {
62 | if (!sessionConfig?.id) {
63 | return;
64 | }
65 | getMessageList(id).then((messageList) => {
66 | if (messageList.length > 0) {
67 | setMessageList(messageList);
68 | // 滚动到底部
69 | scrollToEnd();
70 | } else {
71 | // 如果没有消息,则发送欢迎语
72 | setMessageList([]);
73 | addMessage({
74 | id: uuid(),
75 | type: MessageType.Reply,
76 | text: "您好,欢迎来到AI实验室,请问有什么可以帮您?",
77 | });
78 | }
79 | });
80 | }, [sessionConfig?.id]);
81 |
82 | // 如果没有会话配置,则显示空
83 | if (!sessionConfig) {
84 | return (
85 |
86 |
87 |
88 | );
89 | }
90 |
91 | const { systemPrompt, id, extraDataUrl, filename, extraDataGithub } =
92 | sessionConfig;
93 |
94 | // 滚动到底部
95 | const scrollToEnd = () => {
96 | const chatList = document.getElementById("chat-list");
97 | if (chatList) {
98 | chatList.scrollTop = chatList.scrollHeight;
99 | }
100 | };
101 |
102 | const addMessage = (message: Message) => {
103 | setMessageList((prev) => {
104 | const index = prev.findIndex((item) => item.id === message.id);
105 | // 如果已经存在,则替换
106 | if (prev[index]) {
107 | prev[index] = message;
108 | return [...prev];
109 | }
110 | return [...prev, message];
111 | });
112 | addMessageToStore(id, message);
113 | };
114 |
115 | const sendMessage = async (isAbstract = false) => {
116 | // 首先,当前消息上屏
117 | !isAbstract &&
118 | addMessage({
119 | type: MessageType.Send,
120 | text: inputValue,
121 | id: uuid(),
122 | });
123 |
124 | // 然后,清空输入框
125 | setInputValue("");
126 |
127 | // 最后,发送消息
128 | try {
129 | const response = await fetch("/api/chat", {
130 | method: "POST",
131 | headers: {
132 | "Content-Type": "application/json",
133 | },
134 | body: JSON.stringify({
135 | text: inputValue,
136 | ...sessionConfig,
137 | isAbstract,
138 | messageList: messageList.slice(-11).slice(1), // 只取最近10条消息(并去掉欢迎语)
139 | }),
140 | });
141 |
142 | // 处理SSE的返回
143 | const streamReader = response.body!.getReader();
144 | const utf8Decoder = new TextDecoder("utf-8");
145 | let dataBuffer = "";
146 | // 先生成一个消息id
147 | const messageId = uuid();
148 | const readStream = async () => {
149 | const { done, value } = await streamReader.read();
150 |
151 | if (done) {
152 | console.error("Stream closed");
153 | return;
154 | }
155 |
156 | dataBuffer += utf8Decoder.decode(value);
157 | // 这里,我们需要将消息上屏
158 | addMessage({
159 | id: messageId,
160 | type: MessageType.Reply,
161 | text: marked(dataBuffer),
162 | });
163 |
164 | scrollToEnd();
165 | readStream();
166 | };
167 |
168 | readStream();
169 | } catch (e) {
170 | console.error(e);
171 | alert("发送消息失败");
172 | }
173 | };
174 |
175 | const managePlugin = () => {};
176 |
177 | return (
178 |
179 |
180 |
{
185 | const conf = messageConf[item.type];
186 | return (
187 |
191 |
195 | {conf.avatar}
196 |
197 |
198 |
202 |
203 |
204 | );
205 | }}
206 | >
207 |
208 |
209 |
210 | {isAdvanced() && (
211 |
215 |
216 | 您当前访问的是高级版,可以使用插件的能力。
217 |
218 |
225 |
226 | }
227 | type="info"
228 | showIcon
229 | closable
230 | />
231 | )}
232 | {extraDataGithub && (
233 |
237 |
238 | 您当前基于Github({extraDataGithub})内容:
239 | 可对仓库内容进行提问
240 |
241 |
248 |
249 | }
250 | type="info"
251 | showIcon
252 | closable
253 | />
254 | )}
255 | {filename && (
256 |
260 |
261 | 您当前基于文件({filename})内容: 可对文件内容进行提问
262 |
263 |
270 |
271 | }
272 | type="info"
273 | showIcon
274 | closable
275 | />
276 | )}
277 | {extraDataUrl && (
278 |
282 |
283 | 您当前基于Web URL内容:
284 | {extraDataUrl}
285 | ,可对网页内容进行提问
286 |
287 |
294 |
295 | }
296 | type="info"
297 | showIcon
298 | closable
299 | />
300 | )}
301 | {systemPrompt && (
302 |
309 | )}
310 |
311 | {
316 | setInputValue(event.target.value);
317 | }}
318 | onPressEnter={(event) => {
319 | if (
320 | event.altKey ||
321 | event.ctrlKey ||
322 | event.shiftKey ||
323 | event.metaKey
324 | ) {
325 | return;
326 | }
327 | event.preventDefault();
328 | if (!inputValue) {
329 | return alert("请输入内容");
330 | }
331 | sendMessage();
332 | }}
333 | />
334 |
345 |
346 |
347 |
348 | );
349 | }
350 |
--------------------------------------------------------------------------------
/apps/web/pages/chat/config-modal.tsx:
--------------------------------------------------------------------------------
1 | import { Form, Input, Modal, Radio } from "antd";
2 | import { useEffect, useState } from "react";
3 | import { uuid } from "uuidv4";
4 | import { getOpenAIToken } from "../../global-store";
5 | import { InboxOutlined } from "@ant-design/icons";
6 | import { Upload } from "antd";
7 | import { UploadChangeParam, UploadFile } from "antd/es/upload";
8 |
9 | const { Dragger } = Upload;
10 |
11 | /**
12 | * 会话配置弹窗模式
13 | */
14 | export enum ModalMode {
15 | Create, // 创建
16 | Edit, // 修改
17 | }
18 |
19 | /**
20 | * 会话配置弹窗属性
21 | */
22 | interface ConfigModalProps {
23 | mode: ModalMode;
24 | open: boolean;
25 | onOk: (values: SessionConfig) => void;
26 | onCancel: () => void;
27 | initialSessionConfig?: SessionConfig;
28 | }
29 |
30 | /**
31 | * 会话配置
32 | */
33 | export interface SessionConfig {
34 | // 会话id
35 | id: string;
36 | // openAI token
37 | openAIToken: string;
38 | // 会话名称
39 | sessionName: string;
40 | // 采样温度
41 | temperature: number;
42 | // 模型参数
43 | topP: number;
44 | // 模型参数
45 | frequencyPenalty: number;
46 | // 模型参数
47 | presencePenalty: number;
48 | // 模型参数
49 | n: number;
50 | // 模型名称
51 | modelName: string;
52 | // 系统设定的提示语
53 | systemPrompt?: string;
54 | // 模型参数
55 | stop?: string[];
56 | // 模型参数
57 | timeout?: number;
58 | // 模型参数
59 | maxTokens?: number;
60 | extraDataType: ExtraDataType;
61 | extraDataUrl?: string;
62 | extraDataGithub?: string;
63 | githubToken?: string;
64 | filename: string;
65 | fileMimeType?: string;
66 | }
67 |
68 | // 附加数据类型
69 | enum ExtraDataType {
70 | None,
71 | Url,
72 | File,
73 | Github,
74 | }
75 |
76 | /**
77 | * 会话配置弹窗
78 | * @param props
79 | * @returns
80 | */
81 | export function ConfigModal(props: ConfigModalProps) {
82 | const { mode, open, onOk, onCancel, initialSessionConfig } = props;
83 | const [form] = Form.useForm();
84 | const [extraDataType, setExtraDataType] = useState(ExtraDataType.None);
85 | const [file, setFile] = useState | null>(null);
86 |
87 | /**
88 | * 默认会话配置
89 | */
90 | const DEFAULT_SESSION_CONFIG: SessionConfig = {
91 | id: uuid(),
92 | sessionName: "",
93 | systemPrompt: "",
94 | openAIToken: "",
95 | temperature: 1,
96 | topP: 1,
97 | frequencyPenalty: 0,
98 | presencePenalty: 0,
99 | n: 1,
100 | modelName: "gpt-3.5-turbo",
101 | extraDataType: ExtraDataType.None,
102 | extraDataUrl: "",
103 | filename: "",
104 | };
105 |
106 | // 如果是修改模式, 则设置为当前会话配置(为了回显), 否则设置为默认会话配置
107 | const initialValues =
108 | mode === ModalMode.Edit ? initialSessionConfig : DEFAULT_SESSION_CONFIG;
109 |
110 | useEffect(() => {
111 | // 重置表单, 并设置初始值
112 | if (initialValues) {
113 | const values = { ...initialValues };
114 |
115 | if (!values.openAIToken) {
116 | // 如果没有设置openAI token, 则设置为全局的openAI token
117 | values.openAIToken = getOpenAIToken() || "";
118 | }
119 |
120 | form.setFieldsValue(values);
121 | }
122 | }, [initialSessionConfig, mode]);
123 |
124 | const onFileChange = (info: UploadChangeParam>) => {
125 | const { status } = info.file;
126 | if (status === "done") {
127 | setFile(info.file);
128 | }
129 | };
130 |
131 | return (
132 | {
136 | form
137 | .validateFields()
138 | .then((values) => {
139 | onOk({
140 | ...values,
141 | temperature: Number(values.temperature),
142 | topP: Number(values.topP),
143 | frequencyPenalty: Number(values.frequencyPenalty),
144 | presencePenalty: Number(values.presencePenalty),
145 | n: Number(values.n),
146 | filename: file?.response?.filename || "",
147 | fileMimeType: file?.response?.mimetype || "",
148 | });
149 | })
150 | .catch((info) => {
151 | console.log("Validate Failed:", info);
152 | });
153 | }}
154 | onCancel={onCancel}
155 | >
156 |
163 |
164 |
165 |
170 |
171 |
172 |
177 |
178 |
179 |
184 |
185 |
186 |
191 |
192 |
193 |
198 |
199 |
200 |
205 |
206 |
207 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
228 |
229 |
230 |
235 |
236 |
237 |
242 | {
244 | setExtraDataType(e.target.value);
245 | }}
246 | >
247 | 无
248 | 网页
249 | 文件
250 | Github
251 |
252 |
253 | {extraDataType === ExtraDataType.Url && (
254 |
259 |
260 |
261 | )}
262 | {extraDataType === ExtraDataType.File && (
263 |
268 |
273 |
274 |
275 |
276 |
277 | 请点击或拖拽文件到此区域以上传。
278 |
279 | 支持pdf和txt文件。
280 |
281 |
282 | )}
283 | {extraDataType === ExtraDataType.Github && (
284 |
289 |
290 |
291 | )}
292 | {extraDataType === ExtraDataType.Github && (
293 |
298 |
299 |
300 | )}
301 |
302 |
303 | );
304 | }
305 |
--------------------------------------------------------------------------------
/apps/web/pages/chat/index.page.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { ConfigModal, ModalMode, SessionConfig } from "./config-modal";
3 | import { SideBar } from "./sidebar";
4 | import { ChatPanel } from "./chat-panel";
5 | import { setOpenAIToken } from "../../global-store";
6 | import Head from "next/head";
7 | import { RootState, store } from "./store";
8 | import { Provider, useDispatch, useSelector } from "react-redux";
9 | import { addSession, getSessionList, removeSessionById, setSessionList, updateSession } from "./store/session-list";
10 |
11 | /**
12 | * 会话页面
13 | * @returns
14 | */
15 | export function Chat() {
16 | const { sessionList } = useSelector((state: RootState) => state.sessionList)
17 | const dispatch = useDispatch()
18 | const [configModalOpen, setConfigModalOpen] = useState(false);
19 | const [configModalMode, setConfigModalMode] = useState(ModalMode.Create);
20 | const [activeSessionId, setActiveSessionId] = useState(null);
21 |
22 | // 获取会话列表, 并设置当前会话为第一个会话
23 | useEffect(() => {
24 | const list = getSessionList();
25 | dispatch(setSessionList(list));
26 | if (list.length > 0) {
27 | setActiveSessionId(list[0].id);
28 | }
29 | }, []);
30 |
31 | // 创建新会话
32 | const createNewSession = (values: SessionConfig) => {
33 | dispatch(addSession(values))
34 | // 设置当前会话为新创建的会话
35 | setActiveSessionId(values.id);
36 | };
37 |
38 | // 编辑会话
39 | const editSession = (values: SessionConfig) => {
40 | dispatch(updateSession(values))
41 | };
42 |
43 | // 创建新会话按钮点击
44 | const onCreateBtnClick = () => {
45 | setConfigModalOpen(true);
46 | setConfigModalMode(ModalMode.Create);
47 | };
48 |
49 | // 会话菜单被选中
50 | const onMenuSelect = (id: string) => {
51 | setActiveSessionId(id);
52 | };
53 |
54 | // 会话菜单编辑
55 | const onMenuEdit = (id: string) => {
56 | setConfigModalOpen(true);
57 | setConfigModalMode(ModalMode.Edit);
58 | };
59 |
60 | // 会话菜单删除
61 | const onMenuDelete = (id: string) => {
62 | dispatch(removeSessionById(id))
63 | // 如果删除的是当前会话, 则设置当前会话为第一个会话
64 | if (id === activeSessionId) {
65 | setActiveSessionId(sessionList[0]?.id ?? null);
66 | }
67 | };
68 |
69 | // 会话配置弹窗确定
70 | const onConfigModalOk = (values: SessionConfig) => {
71 | // 如果设置了openAI token, 则更新全局openAI token
72 | if (values.openAIToken) {
73 | setOpenAIToken(values.openAIToken);
74 | }
75 |
76 | // 关闭会话配置弹窗
77 | setConfigModalOpen(false);
78 |
79 | // 根据会话配置弹窗模式, 创建新会话或编辑会话
80 | if (configModalMode === ModalMode.Edit) {
81 | editSession(values);
82 | } else {
83 | createNewSession(values);
84 | }
85 | };
86 |
87 | return (
88 | <>
89 |
90 | 聊天助手(普通版)
91 |
92 |
93 |
101 | item.id === activeSessionId
104 | )}
105 | />
106 |
107 | item.id === activeSessionId
111 | )}
112 | open={configModalOpen}
113 | onOk={onConfigModalOk}
114 | onCancel={() => setConfigModalOpen(false)}
115 | />
116 | >
117 | );
118 | }
119 |
120 | export default function ChatWrapper() {
121 | return
122 |
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/apps/web/pages/chat/sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Menu, MenuProps, Popconfirm } from "antd";
2 | import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
3 | import { SessionConfig } from "./config-modal";
4 | import { useState } from "react";
5 |
6 | // 会话侧边栏属性
7 | interface SideBarProps {
8 | onCreateBtnClick: () => void;
9 | onMenuSelect: (id: string) => void;
10 | onMenuEdit: (id: string) => void;
11 | onMenuDelete: (id: string) => void;
12 | sessionList: SessionConfig[];
13 | selectedKeys: string[];
14 | }
15 |
16 | // 会话侧边栏
17 | type MenuItem = Required["items"][number];
18 |
19 | /**
20 | * 获取菜单项
21 | */
22 | function getItem(
23 | label: React.ReactNode,
24 | key?: React.Key | null,
25 | icon?: React.ReactNode,
26 | children?: MenuItem[],
27 | type?: "group"
28 | ): MenuItem {
29 | return {
30 | key,
31 | icon,
32 | children,
33 | label,
34 | type,
35 | } as MenuItem;
36 | }
37 |
38 | /**
39 | * 会话侧边栏
40 | * @param props
41 | * @returns
42 | */
43 | export function SideBar(props: SideBarProps) {
44 | const { sessionList, onMenuEdit, onMenuDelete, selectedKeys } = props;
45 | return (
46 |
47 |
50 |
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/apps/web/pages/chat/store/chat.ts:
--------------------------------------------------------------------------------
1 | import { storage } from "../../../global-store";
2 | import { Message } from "../chat-panel";
3 |
4 | const MESSAGE_LIST_KEY = "MESSAGE_LIST_KEY";
5 |
6 | /**
7 | * 获取消息列表
8 | * @param sessionId
9 | * @returns
10 | */
11 | export async function getMessageList(sessionId: string): Promise {
12 | return (storage.get(`${MESSAGE_LIST_KEY}_${sessionId}`) as Message[]) || [];
13 | }
14 |
15 | /**
16 | * 设置消息列表
17 | * @param sessionId
18 | * @param messageList
19 | */
20 | export async function setMessageList(
21 | sessionId: string,
22 | messageList: Message[]
23 | ) {
24 | storage.set(`${MESSAGE_LIST_KEY}_${sessionId}`, messageList);
25 | }
26 |
27 | /**
28 | * 添加消息
29 | * @param sessionId
30 | * @param message
31 | */
32 | export async function addMessage(sessionId: string, message: Message) {
33 | const messageList = await getMessageList(sessionId);
34 | const index = messageList.findIndex((item) => item.id === message.id);
35 |
36 | // 如果已经存在,则更新消息
37 | if (index !== -1) {
38 | messageList[index] = message;
39 | setMessageList(sessionId, messageList);
40 | return;
41 | }
42 |
43 | messageList.push(message);
44 | setMessageList(sessionId, messageList);
45 | }
46 |
--------------------------------------------------------------------------------
/apps/web/pages/chat/store/index.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import sessionListReducer from "./session-list";
3 |
4 | export const store = configureStore({
5 | reducer: {
6 | sessionList: sessionListReducer,
7 | },
8 | });
9 |
10 | // Infer the `RootState` and `AppDispatch` types from the store itself
11 | export type RootState = ReturnType;
12 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
13 | export type AppDispatch = typeof store.dispatch;
14 |
--------------------------------------------------------------------------------
/apps/web/pages/chat/store/session-list.ts:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { SessionConfig } from "../config-modal";
3 |
4 | const SESSION_LIST_KEY = "SESSION_LIST_KEY";
5 |
6 | export interface SessionListState {
7 | sessionList: SessionConfig[];
8 | }
9 |
10 | const initialState: SessionListState = {
11 | sessionList: [],
12 | };
13 |
14 | export const sessionListSlice = createSlice({
15 | name: "sessionList",
16 | initialState,
17 | reducers: {
18 | setSessionList: (state, action) => {
19 | state.sessionList = action.payload;
20 | localStorage.setItem(SESSION_LIST_KEY, JSON.stringify(state.sessionList));
21 | },
22 |
23 | addSession: (state, action) => {
24 | state.sessionList.push(action.payload);
25 | localStorage.setItem(SESSION_LIST_KEY, JSON.stringify(state.sessionList));
26 | },
27 | updateSession: (state, action) => {
28 | const index = state.sessionList.findIndex(
29 | (item) => item.id === action.payload.id
30 | );
31 | if (index !== -1) {
32 | state.sessionList[index] = action.payload;
33 | }
34 | localStorage.setItem(SESSION_LIST_KEY, JSON.stringify(state.sessionList));
35 | },
36 | removeSessionById: (state, action) => {
37 | const index = state.sessionList.findIndex(
38 | (item) => item.id === action.payload
39 | );
40 | if (index !== -1) {
41 | state.sessionList.splice(index, 1);
42 | }
43 | localStorage.setItem(SESSION_LIST_KEY, JSON.stringify(state.sessionList));
44 | },
45 | },
46 | });
47 |
48 | export const { setSessionList, addSession, updateSession, removeSessionById } =
49 | sessionListSlice.actions;
50 |
51 | export default sessionListSlice.reducer;
52 |
53 | /**
54 | * 获取会话列表
55 | * @returns
56 | */
57 | export function getSessionList(): SessionConfig[] {
58 | const sessionList = localStorage.getItem(SESSION_LIST_KEY);
59 | try {
60 | return JSON.parse(sessionList || "[]");
61 | } catch (e) {
62 | return [];
63 | }
64 | }
65 |
66 | /**
67 | * 根据id获取会话
68 | * @param id
69 | * @returns
70 | */
71 | export function getSessionById(id: string): SessionConfig | undefined {
72 | const sessionList = getSessionList();
73 | return sessionList.find((item) => item.id === id);
74 | }
75 |
--------------------------------------------------------------------------------
/apps/web/pages/index.page.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 |
3 | export default function Web() {
4 | return (
5 | // https://transfonter.org/ 在线字体压缩
6 |
7 |
8 |
AI实验室
9 |
10 |
17 |
18 |
AI 实验室
19 |
为AI赋能,让未来更美好
20 |
21 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/apps/web/pages/translate.page.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Input } from "antd";
2 | import Head from "next/head";
3 | import { useEffect, useState } from "react";
4 | import { getOpenAIToken, setOpenAIToken } from "../global-store";
5 |
6 | export default function Translate() {
7 | const [token, setToken] = useState("");
8 | const [prompt, setPrompt] = useState(
9 | "Translate the following English text to Chinese, ensuring that the translation is accurate, preserves the original meaning, and uses natural, fluent language:"
10 | );
11 | const [sourceText, setSourceText] = useState("");
12 | const [targetText, setTargetText] = useState("");
13 |
14 | useEffect(() => {
15 | setToken(getOpenAIToken() || "");
16 | }, []);
17 |
18 | const translate = async () => {
19 | if (!token) {
20 | return alert("请输入Open AI的token!");
21 | }
22 |
23 | if (!sourceText) {
24 | return alert("请输入要翻译的文本!");
25 | }
26 |
27 | const response = await fetch("/api/translate", {
28 | method: "POST",
29 | headers: {
30 | "Content-Type": "application/json",
31 | },
32 | body: JSON.stringify({ text: sourceText, token, prompt }),
33 | });
34 |
35 | // 处理SSE的返回
36 | const streamReader = response.body!.getReader();
37 | const utf8Decoder = new TextDecoder("utf-8");
38 | let dataBuffer = "";
39 | const readStream = async () => {
40 | const { done, value } = await streamReader.read();
41 |
42 | if (done) {
43 | console.error("Stream closed");
44 | return;
45 | }
46 |
47 | dataBuffer += utf8Decoder.decode(value);
48 | setTargetText(dataBuffer);
49 | const targetElement = document.getElementById("target") as HTMLElement;
50 | targetElement.scrollTop = targetElement?.scrollHeight;
51 | readStream();
52 | };
53 |
54 | readStream();
55 | setOpenAIToken(token);
56 | };
57 |
58 | return (
59 |
60 |
61 |
AI翻译
62 |
63 |
AI翻译
64 |
86 |
87 |
88 | {
91 | setSourceText(e.target.value);
92 | }}
93 | />
94 |
95 |
96 |
97 |
98 |
99 |
100 | );
101 | }
102 |
--------------------------------------------------------------------------------
/apps/web/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/apps/web/public/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/apps/web/public/bg.png
--------------------------------------------------------------------------------
/apps/web/public/subset-Kingsoft_Cloud_Font.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/apps/web/public/subset-Kingsoft_Cloud_Font.woff
--------------------------------------------------------------------------------
/apps/web/public/subset-Kingsoft_Cloud_Font.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mengjian-github/ailab/af4c51408900f2bfc65c21a5c95145eb437ed98a/apps/web/public/subset-Kingsoft_Cloud_Font.woff2
--------------------------------------------------------------------------------
/apps/web/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | * {
6 | padding: 0;
7 | margin: 0;
8 | }
9 |
10 | @font-face {
11 | font-family: "js";
12 | src: url("/subset-Kingsoft_Cloud_Font.woff2") format("woff2"),
13 | url("/subset-Kingsoft_Cloud_Font.woff") format("woff");
14 | font-weight: normal;
15 | font-style: normal;
16 | font-display: swap;
17 | }
18 |
--------------------------------------------------------------------------------
/apps/web/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./app/**/*.{js,ts,jsx,tsx}",
5 | "./pages/**/*.{js,ts,jsx,tsx}",
6 | "./components/**/*.{js,ts,jsx,tsx}",
7 | ],
8 | theme: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | corePlugins: {
13 | preflight: false,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/nextjs.json",
3 | "include": [
4 | "next-env.d.ts",
5 | "**/*.ts",
6 | "**/*.tsx",
7 | "pages/_app.page.jsge.js"
8 | ],
9 | "exclude": ["node_modules"]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/web/utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取URL中的参数
3 | * @param name
4 | * @returns
5 | */
6 | export function getQueryParam(name?: string) {
7 | // 将查询字符串(包含 "?")截取并解析成对象
8 | const queryParamsString = window.location.search.substring(1);
9 | const queryParamsArr = queryParamsString.split("&");
10 |
11 | // 遍历对象,将它们转换成 key-value 键值对形式
12 | const queryParamsObj: any = {};
13 | for (let i = 0, len = queryParamsArr.length; i < len; i++) {
14 | const queryParam = queryParamsArr[i].split("=");
15 | const key = decodeURIComponent(queryParam[0]);
16 | const value = decodeURIComponent(queryParam[1] || "");
17 | queryParamsObj[key] = value;
18 | }
19 |
20 | // 返回指定参数名的值
21 | if (name) {
22 | return queryParamsObj[name] || null;
23 | }
24 | return queryParamsObj;
25 | }
26 |
27 | /**
28 | * 判断是否是高级模式
29 | * @returns
30 | */
31 | export function isAdvanced() {
32 | return getQueryParam("advanced") === "1";
33 | }
34 |
--------------------------------------------------------------------------------
/ecosystem.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | apps: [
3 | {
4 | name: "ailab",
5 | script: "./apps/server/dist/main.js",
6 | env: { PORT: 443, NODE_ENV: "production" },
7 | },
8 | ],
9 | };
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ailib",
3 | "version": "0.0.0",
4 | "private": true,
5 | "workspaces": [
6 | "apps/*",
7 | "packages/*"
8 | ],
9 | "scripts": {
10 | "build": "turbo run build",
11 | "dev": "turbo run dev",
12 | "lint": "turbo run lint",
13 | "format": "prettier --write \"**/*.{ts,tsx,md}\""
14 | },
15 | "devDependencies": {
16 | "eslint-config-custom": "workspace:*",
17 | "prettier": "latest",
18 | "turbo": "latest"
19 | },
20 | "engines": {
21 | "node": ">=14.0.0"
22 | },
23 | "dependencies": {},
24 | "packageManager": "pnpm@8.1.1"
25 | }
--------------------------------------------------------------------------------
/packages/eslint-config-custom/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["next", "turbo", "prettier"],
3 | rules: {
4 | "@next/next/no-html-link-for-pages": "off",
5 | },
6 | parserOptions: {
7 | babelOptions: {
8 | presets: [require.resolve("next/babel")],
9 | },
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-config-custom",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "eslint": "^7.23.0",
8 | "eslint-config-next": "13.0.0",
9 | "eslint-config-prettier": "^8.3.0",
10 | "eslint-plugin-react": "7.31.8",
11 | "eslint-config-turbo": "latest"
12 | },
13 | "devDependencies": {
14 | "typescript": "^4.7.4"
15 | },
16 | "publishConfig": {
17 | "access": "public"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/tsconfig/README.md:
--------------------------------------------------------------------------------
1 | # `tsconfig`
2 |
3 | These are base shared `tsconfig.json`s from which all other `tsconfig.json`'s inherit from.
4 |
--------------------------------------------------------------------------------
/packages/tsconfig/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Default",
4 | "compilerOptions": {
5 | "composite": false,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "inlineSources": false,
11 | "isolatedModules": true,
12 | "moduleResolution": "node",
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": false,
15 | "preserveWatchOutput": true,
16 | "skipLibCheck": true,
17 | "strict": true
18 | },
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/tsconfig/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Next.js",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "target": "es5",
7 | "lib": ["dom", "dom.iterable", "esnext"],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "incremental": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve"
19 | },
20 | "include": ["src", "next-env.d.ts"],
21 | "exclude": ["node_modules"]
22 | }
23 |
--------------------------------------------------------------------------------
/packages/tsconfig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tsconfig",
3 | "version": "0.0.0",
4 | "private": true,
5 | "files": [
6 | "base.json",
7 | "nextjs.json",
8 | "react-library.json"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/tsconfig/react-library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "React Library",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "jsx": "react-jsx",
7 | "lib": ["ES2015"],
8 | "module": "ESNext",
9 | "target": "es6"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "apps/*"
3 | - "packages/*"
4 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": ["**/.env.*local"],
4 | "pipeline": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
8 | },
9 | "lint": {
10 | "outputs": []
11 | },
12 | "dev": {
13 | "cache": false
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------