ChatGPT - 利用 ChatGPT 的 api 编写 AI 问答界面

接入 ChatGPT 快速实现的 MVP 版本的AI问答界面

背景

最近发现 ChatGPT 有提供了一套 api 以及 node 工具,于是就想着能不能利用 ChatGPT 的 api 写一个 AI 工具。

准备

实现一个最小可行性产品 MVP 版本

  • 前端界面:简单的对话框即可,用 vue 快速实现
  • 后台设计:使用官方提供的 node 包以及 api,用 nestjs 快速实现

前端界面编写

  1. 使用 vue-cli 快速生成一个前端项目,命名为 dh-chat-ui 如图:
    在这里插入图片描述

  2. 简单编写界面,包含标题、输入框、提问按钮、提问问题展示、答案展示,界面元素使用 naive-ui, 预览如下:
    在这里插入图片描述

代码非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<template>
<header>
<n-gradient-text type="info" :size="30"> DH&nbsp;</n-gradient-text>
<n-gradient-text type="danger" :size="30"> CHAT AI </n-gradient-text>
</header>
<main>
<n-input
v-model:value="content"
round
class="dh-input"
type="textarea"
size="large"
placeholder="请输入提问"
>
<!-- <template #prefix>
<n-icon :component="FlashOutline" />
</template> -->
</n-input>
<n-button v-if="!isLoading" type="primary" @click="handleAsk">提问</n-button>
<n-button v-else type="primary" disabled>请稍候</n-button>
<n-divider> 提问 </n-divider>
<div class="answer">
{{ curAsk }}
</div>
<n-divider> 答案 </n-divider>
<div class="answer">
{{ answer }}
</div>
</main>
</template>
<script lang="ts">
import { ref } from 'vue'
import { NButton, NInput, NDivider, NGradientText } from 'naive-ui'
import { FlashOutline } from '@vicons/ionicons5'
import axios from 'axios'
export default {
components: { NButton, NInput, NDivider, NGradientText },
setup() {
const content = ref('')
const answer = ref('-')
const curAsk = ref('您还没有提问')
const isLoading = ref(false)
const handleAsk = async () => {
if (!content.value) {
return
}
isLoading.value = true
curAsk.value = content.value
content.value = ''
answer.value = '思考中'
try {
const response = await axios.post('http://222.125.89.250:70/ask', {
content: curAsk.value
})
answer.value = response?.data.content || '-'
} finally {
isLoading.value = false
}
}
return {
content,
answer,
handleAsk,
FlashOutline,
curAsk,
isLoading
}
}
}
</script>
<style>
.dh-input {
margin: 10px 0;
}
.dh-answer {
padding: 0 10px;
}
</style>

后台设计

  1. 首先创建一个简单的 nest 项目,如图:
    在这里插入图片描述

  2. 到 openai 官网创建一个 api 密钥、组织 ID,存放到常量文件,模型我选择 gpt-3.5-turbo:
    在这里插入图片描述

1
2
3
4
5
6
// 密钥
export const OPENAI_API_KEY = 'sk-mTkhGQJfdTd0zX4iaFrYT3BlbkFJr2kTg1yxxxxxxxxx';
// 组织ID
export const ORGANIZATION_ID = 'org-wBO3nA6YWjAN3Z8Cngxxxxxxxx';
// 默认模型
export const DEFAULT_MODEL = 'gpt-3.5-turbo';
  1. 接着使用 openai 提供的 node 包初始化, 得到一个对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Configuration, OpenAIApi } from 'openai';
import { OPENAI_API_KEY, ORGANIZATION_ID } from './const';

/** init openai */
export const initOpenai = () => {
const configuration = new Configuration({
organization: ORGANIZATION_ID,
apiKey: OPENAI_API_KEY,
});
return new OpenAIApi(configuration);
};

/** openai instance */
export const openai = initOpenai();
  1. 编写接口,此处只提供一个 ask 接口用于返回答案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// controller 层,向外暴露接口
import { Controller, Body, Post } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Post('/ask')
async postCompletion(@Body('content') content: string): Promise<string> {
console.log(content);
const data = await this.appService.createChatCompletion(content);
console.log(data);
return data;
}
}

// service 层,调用 openai 接口
import { Injectable } from '@nestjs/common';
import { openai } from '../ai';
import { DEFAULT_MODEL } from 'ai/const';

@Injectable()
export class AppService {
async createChatCompletion(content: string): Promise<any> {
const response = await openai.createChatCompletion({
model: DEFAULT_MODEL,
messages: [{ role: 'user', content }],
});
return response.data.choices[0].message;
}
}

  1. 跨域怎么解决? 直接 cors 允许即可
1
2
3
4
5
6
7
8
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 允许跨域
app.enableCors();
await app.listen(3000);
}

bootstrap();

完成

简单的回答,很 OK
在这里插入图片描述

简单的 MVP 版本

以上。