├── .env.example ├── README.md ├── api └── index.js ├── app.js ├── chat ├── chat.js ├── session.js ├── template.js └── text.js ├── comm └── debug.js ├── ding └── accesstoken.js ├── handler └── conversation.js ├── package.json ├── public └── .gitkeep ├── render.yaml ├── route └── conversation.js ├── service └── openai.js └── vercel.json /.env.example: -------------------------------------------------------------------------------- 1 | PORT = 7070 2 | APPKEY = your appkey 3 | APPSECRET = your appsecret 4 | OPENAI_MODEL = gpt-3.5-turbo 5 | OPENAI_API_KEY = sk- -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 零代码 一键部署ChatGPT到钉钉 无须翻墙
3 | 4 | 5 |
6 | 7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 | ChatGPT 接入钉钉, 赋能商业成功 15 |
16 | 17 | ## 大模型AI客服邀请您体验 18 | 我们基于chatgpt 大模型, 开发了Ai智能客服,Ai智能客服7*24小时服务能力,大大节省客服成本,提高公司服务效率。 19 | 接入场景包括 *微信*,*公众号*,*视频号小店*,*小程序*等 20 | 需要体验的企业欢迎聊系我,名额有限。 21 | 22 | 23 |
24 | 25 | 26 | 27 |
28 |
https://www.youtube.com/watch?v=Wd6zc7WmeUI
29 | 30 | 31 | 32 | ## 关于本项目 33 | 本项目可以实现一键部署ChatGPT到钉钉中,使ChatGPT与钉钉完美融合,手机或电脑上,打开钉钉,就可以使用强大的ChatGPT智能问答。截止目前,本项目可以提供两个能力:
34 | 35 | 1. 功能集成,将ChatGPT问答功能集成到钉钉,借助钉钉机器人功能,我们可以与ChatGPT一对一问答,或者在群里让ChatGPT参与问答,安装请参照下面的**部署方法** 36 | 2. 更强大的功能扩展,本项目为开源项目,有开发能力的小伙伴可以Fork到自己的仓库,根据自己企业业务需要,比如结合钉钉开放的API,二次开发一些其他功能。 37 | 38 | 39 | 40 | 41 | 1. 创建钉钉应用
42 | 43 | ## 部署方式一 Vercel方式(推荐) 44 | 1. 创建钉钉应用
45 | 46 | 47 | 第一步,创建应用。
48 | 1、登录[钉钉开发者后台](https://open-dev.dingtalk.com/#/),选择应用开发 > 企业内部开发 > 创建应用,单击创建应用;创建应用后,进入机器人与消息推送页面,进入机器人配置页面。 49 | 50 | ![image](https://user-images.githubusercontent.com/12178686/235679150-828883cb-213c-4d66-8059-6a2fc0015219.png) 51 | 52 |
53 | 54 |
55 | 56 | 2、单击应用功能 > 机器人与消息推送。 57 | ![image](https://user-images.githubusercontent.com/12178686/235680489-906ff1f9-57b6-4964-bba0-9f98667917c7.png) 58 | 点亮此按扭 59 | 60 | 3、打开机器人配置开关后,填写机器人相关配置信息,除了**消息接收地址**,信息完善后,请点<发布>,成功会看到“编辑成功”提示。 61 |
62 | 63 |
64 |

65 | 66 | 4、配置机器人权限,单击权限管理 > 机器人,将相关权限开通,操作如下图, 67 | 68 | 69 | 70 | 71 | 72 | 第二步,部署前的准备工作 73 | 74 | 1、**open-api-key** 75 | 76 | 这个需要在ChatGPT账号里生成 77 | 78 | 79 | [申请网址API KEY](https://platform.openai.com/account/api-keys) 80 | 81 | 2、**AppKey AppSecret** 82 | 83 | 84 | 3、**要有一个自己的域名** 85 | 自己已经注册好的一个域名。 86 | 87 | 第三步,一键部署安装服务 88 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsytpb%2Fchatgpt-dingtalk-robot%2Ftree%2Fmain&env=PORT,APPKEY,APPSECRET,OPENAI_MODEL,OPENAI_API_KEY&project-name=chatgpt-dingtalk-robot&repository-name=chatgpt-dingtalk-robot) 89 | 请点右键 > 新标签页打开
90 | 91 | 92 | 93 | 94 |

95 | 请将*Create private Git Repository* 勾点掉,然后点击 Create 96 |

97 | 98 | 99 | 100 | 101 |

102 | 这一步要填入相关参数,注意,前后不要加入多余的空格, OPENAI_MODEL, 可以填入gpt-3.5-turbo或者gpt-4, ** 注意账号不支持gpt4,要填入 gpt-3.5-turbo,否则无法使用 **。 然后点击 Deploy。 103 | # 参数选项请参考下面参数表格说明 104 |

105 | 106 | 107 | 108 | 109 |

110 | 部署成功,如图所示。 111 |

112 | 113 | 114 | 115 | 116 |

117 | 绑定自己的域名,填入域名,点 Add。 118 |

119 | 120 | 121 | 122 | 123 | 124 |

125 | 保持默认,点 Add 126 |

127 | 128 | 129 | 130 | 131 |

132 | 复制 IP地址 133 |

134 | 135 | 136 | 137 | 138 |

139 | 到自己购买域名的控制台,我这里是腾讯云控制台,给域名增加解析记录,如图所示,一条A记录,一条CNAME记录。 140 |

141 | 142 | 143 | 144 | 145 |

146 | 配置成功,Vercel 页面会自动出现所示标志。 147 |

148 | 149 | **把域名加上/message**, 比如域名是abc.com URL: https://www.abc.com/message, 粘贴到上面**消息接收地址**页面里,点击<调试>,然后再次点击<发布>即可。到此部署完成! 150 | 151 | ## 部署方式二 Render方式 152 |
153 | 点击查看详细 154 |
155 | [指导视频](https://youtu.be/JgBNsWQcSqw) 156 | 157 | 1. 创建钉钉应用
158 | 159 | 160 | 第一步,创建应用。
161 | 1、登录[钉钉开发者后台](https://open-dev.dingtalk.com/#/),选择应用开发 > 企业内部开发 > 创建应用,单击创建应用;创建应用后,进入机器人与消息推送页面,进入机器人配置页面。 162 | 163 | ![image](https://user-images.githubusercontent.com/12178686/235679150-828883cb-213c-4d66-8059-6a2fc0015219.png) 164 | 165 |
166 | 167 |
168 | 169 | 2、单击应用功能 > 机器人与消息推送。 170 | ![image](https://user-images.githubusercontent.com/12178686/235680489-906ff1f9-57b6-4964-bba0-9f98667917c7.png) 171 | 点亮此按扭 172 | 173 | 3、打开机器人配置开关后,填写机器人相关配置信息,除了**消息接收地址**,信息完善后,请点<发布>,成功会看到“编辑成功”提示。 174 |
175 | 176 |
177 |

178 | 179 | 4、配置机器人权限,单击权限管理 > 机器人,将相关权限开通,操作如下图, 180 | 181 | 182 | 183 | 184 | 185 | 186 | 第二步,部署前的准备工作 187 | 188 | 1、**open-api-key** 189 | 190 | 这个需要在ChatGPT账号里生成 191 | 192 | 193 | [申请网址API KEY](https://platform.openai.com/account/api-keys) 194 | 195 | 2、**AppKey AppSecret** 196 | 197 | 198 | 199 | 200 | 201 | 第三步,一键部署安装服务 202 | 203 | Deploy to Render 204 | 205 | 请点右键 > 新标签页打开 206 | 207 | 208 | 209 | 210 | 如图所示,将上面的字段信息填入,端口填入4位数,比如7070,然后点击Apply。 注意现更新增加了OPENAI_MODEL 值可以是gpt-3.5-turbo 或者gpt-4(如果你的key支持可填)

211 | 212 | 213 | 214 | 215 |
216 | 217 |
218 |

219 | 220 | 需要等1-5分钟部署,完成后复制生成的服务的URL,如下图,**然后拷贝URL后面拼接上/message**, 比如URL是 https://abc.com 拼接成 https://abc.com/message, 粘贴到上面**消息接收地址**页面里,点击<调试>,然后再次点击<发布>即可。 221 | 222 | 223 |
224 | 225 | ## 部署方式三 Docker方式 226 |
227 | 服务器docker部署 228 | 229 | 1. 前提条件: 230 | - 一台服务器 231 | - 一个域名 232 | 233 | 2. 复制变量文件 `.env.example`,填写自己的配置 234 | 235 | 3. 运行docker 236 | 假设新变量文件名为 `.env.local` 237 | 238 | ```bash 239 | # docker4bill/ww-openai-node:alpine 为构建好的镜像,你也可以利用本仓库的 Dockerfile 构建自己的镜像 240 | docker run --env-file .env.local -p 6060:6060 -d docker4bill/ww-openai-node:alpine 241 | ``` 242 | 243 | 4. 用 `caddy` 或者 `nginx` 给以上服务做个反代即可 244 | 245 |
246 | 247 | ## 参数请参照下表完成,注意值前后不要有空格 248 | 249 | | Key | value | 说明 | 250 | | --------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | 251 | | APPKEY | | | 252 | | APPSECRET | | | 253 | | OPENAI_API_KEY | | | 254 | | OPENAI_MODEL | gpt-3.5-turbo | gpt-3.5-turbo 或者gpt-4 注意:不支持gpt4填入gpt-4无效 | 255 | | PORT | 7070 | 可以改成其他 | 256 | |CHAT_HISTORY | no | yes 或者 no yes支持上下文会话,no 不支持上下文,区别上下文对话token 成本高 | 257 | 258 | ## 功能支持 259 | 部署完成,:100: 下面就可以直接使用了,支持两种聊天模式,一是一对一单聊,另一个是群里添加此机器人,@他的名字,发消息让ChatGPT 回答,如文档开头的两个图片,第一张是一对一单聊,第二张是群里与ChatGPT聊天,更多使用场景请加群讨论。(有问题请提issue) 260 | 261 | 262 | ## 新功能调查 263 | 264 | 您的工作场景,最想要Chatgpt为您做什么?除了现有的问答模式。假如需要以下功能, 265 | 266 | 1、语音对话,什么场景用? 267 | 268 | 2、图片生成,什么场景用? 269 | 270 | 3、其他,请列举 271 | 272 | 欢迎来群里讨论! 273 | 274 | 275 |
276 | group 277 |
278 | 279 | 280 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | import app from '../app.js'; 2 | 3 | export default app; -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { config } from "dotenv"; 3 | import debug from "./comm/debug.js"; 4 | import { initAccessToken } from "./ding/accesstoken.js"; 5 | 6 | import conversation from "./route/conversation.js"; 7 | 8 | 9 | config(); 10 | 11 | const app = express(); 12 | const PORT = process.env.PORT; 13 | 14 | /*message.log();*/ 15 | app.use(express.json()); 16 | app.use(express.urlencoded({ extended: false })); 17 | 18 | /*health check for render*/ 19 | app.get('/healthz', function (req, res, next) { 20 | res.status(200).end(); 21 | }); 22 | 23 | app.use('/message', conversation); 24 | 25 | 26 | /*init access_token*/ 27 | initAccessToken(); 28 | 29 | app.listen(PORT, () => { 30 | debug.out(`Server Running on Port:${PORT}`); 31 | }); 32 | 33 | export default app; -------------------------------------------------------------------------------- /chat/chat.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const types = {text: "TEXT", image: "IMAGE", voice: "AUDIO"}; 4 | 5 | export default class Chat { 6 | #type = null; 7 | constructor(name) { 8 | this.#type = types[name]; 9 | } 10 | 11 | type() { 12 | return this.#type; 13 | } 14 | 15 | process(xml, res) { 16 | 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /chat/session.js: -------------------------------------------------------------------------------- 1 | 2 | export default class Session { 3 | 4 | static session = null; 5 | static _cache = null; 6 | 7 | static { 8 | this.session = new Map(); 9 | this._cache = new this(); 10 | } 11 | /*singleton*/ 12 | constructor() { 13 | return this.constructor._cache; 14 | } 15 | 16 | invoke() {}; 17 | 18 | static push(id, item) { 19 | 20 | const list = this.session.get(id); 21 | if(!list) { 22 | this.session.set(id, [item]); 23 | } else { 24 | list.push(item); 25 | } 26 | } 27 | 28 | static shift(id) { 29 | 30 | const list = this.session.get(id); 31 | if(list.length === 4) { 32 | list.shift(); 33 | } 34 | } 35 | 36 | static update(id, item) { 37 | 38 | this.push(id, item); 39 | this.shift(id); 40 | return this.session.get(id); 41 | } 42 | 43 | static get(id) { 44 | 45 | return this.session.get(id); 46 | } 47 | } -------------------------------------------------------------------------------- /chat/template.js: -------------------------------------------------------------------------------- 1 | export const MDUserMsg = (title, content) => { 2 | 3 | const data = { 4 | "msgtype": "markdown", 5 | "markdown": { 6 | "title": title, 7 | "text": content 8 | } 9 | }; 10 | return data; 11 | } 12 | 13 | 14 | export const MDGroupMsg = (title, senderId, content) => { 15 | 16 | const text = `@${senderId} \n\n ${content}`; 17 | 18 | const data = { 19 | "msgtype": "markdown", 20 | "markdown": { 21 | "title": title, 22 | "text": text 23 | }, 24 | "at": { 25 | "atDingtalkIds": [ 26 | senderId 27 | ], 28 | } 29 | }; 30 | return data; 31 | } -------------------------------------------------------------------------------- /chat/text.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | import axios from "axios"; 3 | import Chat from "./chat.js"; 4 | import Session from "./session.js"; 5 | import debug from "../comm/debug.js"; 6 | import { OpenAI } from "../service/openai.js"; 7 | import { MDUserMsg, MDGroupMsg } from "./template.js"; 8 | import { getAccessToken } from "../ding/accesstoken.js"; 9 | 10 | export default class TextChat extends Chat { 11 | 12 | constructor(name) { 13 | super(name); 14 | this.host = 'https://api.dingtalk.com'; 15 | } 16 | 17 | async toUser(staffID, robotCode, answer, res) { 18 | /*response to dingtalk*/ 19 | const token = await getAccessToken(); 20 | debug.out(answer); 21 | 22 | const data = { 23 | "robotCode": robotCode, 24 | "userIds": [staffID], 25 | "msgKey": "sampleText", 26 | "msgParam": JSON.stringify({ "content": answer }) 27 | }; 28 | const url = this.host + '/v1.0/robot/oToMessages/batchSend'; 29 | 30 | const config = { 31 | headers: { 32 | 'Accept': "application/json", 33 | 'Content-Type': "application/json", 34 | 'x-acs-dingtalk-access-token': token 35 | } 36 | }; 37 | 38 | await axios.post(url, data, config); 39 | res.send("OK"); 40 | } 41 | 42 | async toGroup(conversationID, robotCode, answer) { 43 | /*response to dingtalk*/ 44 | const token = await getAccessToken(); 45 | debug.out(answer); 46 | 47 | const data = { 48 | "robotCode": robotCode, 49 | "openConversationId": conversationID, 50 | "msgKey": "sampleText", 51 | "msgParam": JSON.stringify({ "content": answer }) 52 | }; 53 | 54 | const url = this.host + '/v1.0/robot/groupMessages/send'; 55 | 56 | const config = { 57 | headers: { 58 | 'Accept': "application/json", 59 | 'Content-Type': "application/json", 60 | 'x-acs-dingtalk-access-token': token 61 | } 62 | }; 63 | 64 | return axios.post(url, data, config); 65 | } 66 | 67 | async reply(info, answer, res) { 68 | const senderId = info.senderId; 69 | const webHook = info.sessionWebhook; 70 | 71 | let markdown = null; 72 | if (info.conversationType === '1') 73 | markdown = MDUserMsg(answer.slice(0,30), answer); 74 | else if (info.conversationType === '2') 75 | markdown = MDGroupMsg(answer.slice(0,30), senderId, answer); 76 | 77 | res.set({ 78 | 'Content-Type': 'application/json', 79 | 'url': webHook 80 | }); 81 | const result = res.send(JSON.stringify(markdown)); 82 | debug.log(result); 83 | } 84 | 85 | 86 | process(info, res) { 87 | 88 | const question = info?.text?.content; 89 | let context = [{"role":"user" ,"content":question}]; 90 | //const staffID = info?.senderStaffId; 91 | const robotCode = info?.robotCode; 92 | 93 | const openai = new OpenAI(); 94 | if(process.env.CHAT_HISTORY === "yes") 95 | context = Session.update(info.conversationId, {"role":"user" ,"content":question}); 96 | debug.out(context); 97 | 98 | openai.ctChat(context).then(result => { 99 | const message = result?.data?.choices[0]?.message; 100 | debug.log(message?.content); 101 | if (!message?.content) 102 | return; 103 | 104 | const answer = message.content; 105 | this.reply(info, answer, res); 106 | }); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /comm/debug.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default class debug { 4 | 5 | constructor() { 6 | } 7 | 8 | static stack() { 9 | 10 | const e = new Error(); 11 | const regex = /\/([^\/]+\.*):(\d+):(\d+)/; 12 | const match = regex.exec(e.stack.split("\n")[3]); 13 | 14 | return { 15 | name: match[1], 16 | line: match[2], 17 | column: match[3] 18 | }; 19 | } 20 | 21 | static log(...info) { 22 | 23 | const s = this.stack(); 24 | const name = s.name; 25 | const line = s.line; 26 | console.log(`<${name}:${line}>`, ...info); 27 | } 28 | 29 | static out(...info) { 30 | console.log(...info); 31 | } 32 | } 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ding/accesstoken.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | let accessToken = { token: 'NO_TICKET', expire: 0 }; 4 | 5 | 6 | function newAccessToken() { 7 | 8 | const appKey = process.env.APPKEY; 9 | const appSecret = process.env.APPSECRET; 10 | const url = 'https://api.dingtalk.com/v1.0/oauth2/accessToken'; 11 | 12 | const body = { 13 | "appKey": appKey, 14 | "appSecret": appSecret 15 | }; 16 | 17 | const config = { 18 | headers:{ 19 | 'Accept': "application/json", 20 | 'Content-Type': "application/json" 21 | } 22 | }; 23 | 24 | return axios.post(url, body, config); 25 | } 26 | 27 | function setAccessToken(token) { 28 | 29 | const expire = new Date().getTime() + 2 * 60 * 60 * 1000; 30 | accessToken = { ...accessToken, token, expire } 31 | } 32 | 33 | async function getAccessToken() { 34 | 35 | const current = new Date().getTime(); 36 | if (accessToken.expire > current) /*timeout*/ 37 | return accessToken.token; 38 | else { 39 | console.log("access token expired , refresh...") 40 | const token = await newAccessToken(); 41 | setAccessToken(token); 42 | return token; 43 | } 44 | } 45 | 46 | async function initAccessToken() { 47 | const result = await newAccessToken(); 48 | const token = result?.data?.accessToken; 49 | console.log("accesstoken === ", token); 50 | setAccessToken(token); 51 | } 52 | 53 | export { 54 | initAccessToken, 55 | getAccessToken 56 | }; 57 | -------------------------------------------------------------------------------- /handler/conversation.js: -------------------------------------------------------------------------------- 1 | import debug from "../comm/debug.js"; 2 | import TextChat from "../chat/text.js"; 3 | 4 | export default class Conversation { 5 | 6 | constructor() { 7 | } 8 | 9 | urlconfig(req, res) { 10 | res.send("OK"); 11 | } 12 | 13 | process(body, res) { 14 | 15 | let chat = null; 16 | const info = body; 17 | 18 | const msgtype = info.msgtype; 19 | 20 | if(msgtype === "text") { 21 | chat = new TextChat(msgtype); 22 | } 23 | 24 | if(!!chat) { 25 | chat.process(info, res); 26 | /*res.send("OK");*/ 27 | return; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "messagerobot", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "node app.js", 8 | "vercel-build": "echo hello" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^1.4.0", 15 | "dotenv": "^16.0.3", 16 | "express": "^4.18.2", 17 | "openai": "^3.2.1" 18 | }, 19 | "engines": { 20 | "node": ">=17.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sytpb/chatgpt-dingtalk-robot/28a8aa0d8761a9f318535d602dcb86e18fdb162e/public/.gitkeep -------------------------------------------------------------------------------- /render.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | # node web service 3 | - type: web 4 | name: chatgpt-dingtalk-robot 5 | env: node 6 | rootDir: . 7 | autoDeploy: false 8 | repo: https://github.com/sytpb/chatgpt-dingtalk-robot.git # optional 9 | region: singapore # optional (defaults to oregon) 10 | plan: free # optional (defaults to starter instance type) 11 | branch: beta # optional (defaults to master) 12 | healthCheckPath: /healthz 13 | buildCommand: npm install 14 | startCommand: node app.js 15 | envVars: 16 | - key: PORT 17 | sync: false 18 | - key: APPKEY 19 | sync: false 20 | - key: APPSECRET 21 | sync: false 22 | - key: OPENAI_MODEL 23 | sync: false 24 | - key: OPENAI_API_KEY 25 | sync: false 26 | - key: CHAT_HISTORY 27 | sync: false -------------------------------------------------------------------------------- /route/conversation.js: -------------------------------------------------------------------------------- 1 | 2 | import debug from "../comm/debug.js"; 3 | import Conversation from "../handler/conversation.js"; 4 | 5 | import express from "express"; 6 | const router = express.Router(); 7 | 8 | router.use('/', function(req, res, next) { 9 | let method = req.method; 10 | if(method == 'GET') { 11 | debug.out(`--------------ROUTER MSG [URL SETTING]--------------`); 12 | const conversation = new Conversation(); 13 | conversation.urlconfig(req, res); 14 | } 15 | else if(method == 'POST') { 16 | debug.out(`--------------ROUTER MSG [CONVERSATION]--------------`); 17 | const conversation = new Conversation(); 18 | conversation.process(req.body, res); 19 | } 20 | }); 21 | 22 | export default router; 23 | 24 | -------------------------------------------------------------------------------- /service/openai.js: -------------------------------------------------------------------------------- 1 | 2 | import { Configuration, OpenAIApi } from "openai"; 3 | 4 | const models = ['text-davinci-003','code-davinci-002','gpt-3.5-turbo','gpt-4']; 5 | 6 | 7 | export class OpenAI { 8 | #configuration = null; 9 | #openai = null; 10 | 11 | constructor() { 12 | this.#configuration = new Configuration( {apiKey: process.env.OPENAI_API_KEY} ); 13 | this.#openai = new OpenAIApi(this.#configuration); 14 | } 15 | 16 | static create() { 17 | } 18 | 19 | async ctChat(context) { 20 | 21 | try { 22 | const res = await this.#openai.createChatCompletion({ 23 | model: process.env.OPENAI_MODEL, 24 | messages: context 25 | }); 26 | 27 | return res; 28 | } 29 | catch(error) { 30 | console.log("OpenAI happen error!"); 31 | console.log(error?.response?.data?.error); 32 | } 33 | } 34 | 35 | async ctText(question) { 36 | 37 | try { 38 | const res = await this.#openai.createChatCompletion({ 39 | model: process.env.OPENAI_MODEL, 40 | messages:[{role:"user",content: question}] 41 | }); 42 | 43 | return res; 44 | } 45 | catch(error) { 46 | console.log("OpenAI happen error!"); 47 | console.log(error); 48 | } 49 | } 50 | 51 | static ctImage() { 52 | 53 | } 54 | 55 | static ctVoice() { 56 | 57 | } 58 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | 2 | 3 | { 4 | "rewrites": [{ "source": "/(.*)", "destination": "/api" }] 5 | } --------------------------------------------------------------------------------