原生JS实现ChatGPT API流式输出
B站链接:https://www.bilibili.com/video/BV1u1421D723
无需库依赖!通过原生JS的接口来调用ChatGPT的API,并实现流式输出AI的回复内容。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>ChatGPT with Element UI</title>
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div id="app">
<div class="response-area" id="response">
<div style="padding: 10px; overflow: auto">
<div class="messages" v-for="(item,index) in messages" v-if="index > 0">
<div class="message" :style="item.role === 'user' ? {float: 'right'} : {float: 'left'}">
<div>
<div style="margin-bottom: 10px; font-weight: bold">{{ item.role === 'user' ? '你:' : 'AI:' }}</div>
</div>
<div style="color: gray" v-if="item.role === 'assistant'" v-html="formatContent(item.content)"></div>
<div style="color: gray" v-else>{{ item.content }}<p></p></div>
</div>
</div>
</div>
</div>
<div class="request-area">
<el-input type="textarea" @keyup.enter.native="askChatGPT" :autofocus="true"
:rows="5" v-model="userInput" placeholder="输入你的问题"></el-input>
<div style="position: absolute; right: 10px; bottom: 15px">
<el-button @click="askChatGPT" type="primary" v-loading="loading">发送</el-button>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
loading: false,
userInput: '',
apiKey: 'apiKey', // 替换为你的API密钥
messages: [
{
"role": "system",
"content": "You are a helpful assistant."
}
],
currentIndex: 0
}
},
methods: {
formatContent(content) {
return marked.parse(content);
},
scrollToBottom() {
var scrollTarget = document.getElementById("response");
scrollTarget.scrollTop=scrollTarget.scrollHeight;
},
async askChatGPT() {
var _this = this
_this.loading = true
_this.messages.push({
"role": "user",
"content": this.userInput
})
_this.currentIndex += 1
_this.userInput = ''
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: this.messages,
max_tokens: 1000,
stream: true
})
});
if (response.ok) {
_this.scrollToBottom()
_this.loading = false
const reader = response.body.getReader();
const stream = new ReadableStream({
async start(controller) {
function fetchData() {
reader.read().then(({done, value}) => {
if (done) {
controller.close();
return;
}
const chunkText = new TextDecoder().decode(value);
chunkText.split('\n').forEach(line => {
if (line) {
if (line === 'data: [DONE]') {
return
}
line = line.replaceAll('data: ', '');
const data = JSON.parse(line);
if (data.choices[0].finish_reason && data.choices[0].finish_reason === 'stop') {
return;
}
if (data.choices[0].delta.content) {
const text = data.choices[0].delta.content;
_this.messages[_this.currentIndex].content += text;
_this.scrollToBottom()
}
}
});
fetchData();
});
}
_this.messages.push({
"role": "assistant",
"content": ""
})
_this.currentIndex += 1
fetchData();
}
});
await new Response(stream).text();
} else {
this.$message.error('发生错误')
}
}
}
});
</script>
<style>
#app {
width: 800px;
margin: 0 auto;
}
.messages {
overflow: auto;
}
.message {
min-width: 100px;
max-width: 80%;
padding: 10px 10px 0 10px;
background: #FFF;
border-radius: 10px;
overflow: auto;
margin-bottom: 20px;
}
.request-area {
height: 120px;
width: 800px;
position: fixed;
bottom: 0;
}
.response-area {
height: calc(100vh - 140px);
width: 800px;
background: rgba(0, 0, 0, 0.05);
position: fixed;
top: 10px;
border-radius: 5px;
color: #333;
overflow: auto;
}
</style>
</body>
</html>
License:
CC BY 4.0