随着最近的chatgpt的流行,我也想给我的网站搭个chatgpt3.5的功能自己用或者给登录注册的用户免费用,刚开始我实现是前端根据用户的提问,通过post请求返回给django后端,然后django后端调用官网开放的openai接口返回问题的回答,然后一次性返回给前端。后面再用markdown-it和prism进行代码高亮。实现了后但是需要等待全部结果返回这个行为非常耗时,无法像官网那样逐字逐字回答,以流的形式逐字逐字返回。后面再研究了下,可以实现了,我先看django后端代码,如下所示
class ChatGpt_3_5(View):
def get(self,request):
#判断是否登录
if request.user.is_authenticated:
username = request.user.username
user_img_path = UserProfile.objects.filter(username=username).values("image")[0].get('image')
else:
return redirect('/login?next=/chatgpt3.5')
return render(request,'gpt3.5.html',context={'users_img_path':user_img_path})
def sendGptMessage(request,*args,**kwargs):
if request.user.is_authenticated:
question = request.GET.get('message')
print(question)
def event_stream():
data = get_gpt(question)
print(data)
event = 'message'
message = f'{data}\n\n'
yield f'event: {event}\n{message}'
response = StreamingHttpResponse(event_stream(), content_type='text/event-stream')
response['Cache-Control'] = 'no-cache'
return response
else:
def event_stream():
data = '未登录或者请求方式有误!'
event = 'close'
message = f'{data}\n\n'
yield f'event: {event}\n{message}'
response = StreamingHttpResponse(event_stream(), content_type='text/event-stream')
response['Cache-Control'] = 'no-cache'
return response
def get_gpt(question):
url = "https://api.openai-hk.com/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-xxxxx"
}
data = {
"max_tokens": 1200,
"model": "gpt-3.5-turbo",
"temperature": 0.8,
"top_p": 1,
"presence_penalty": 1,
"messages": [
{
"role": "user",
"content": question
}
],
'stream': True,
}
response = requests.post(url, headers=headers, data=json.dumps(data).encode('utf-8'))
result = response.content.decode("utf-8")
return result
这里记得使用SSE将数据以流的形式返回给前端,接下来我们看前端的代码
<script>
// 监听 textarea 的 keydown 事件
$('#content').on('keydown', function(event) {
// 检测是否按下 Enter 键
if (event.keyCode === 13 && !event.shiftKey) {
event.preventDefault(); // 阻止默认的换行行为
// 获取 textarea 内容
var message = $(this).val();
var cleanedString = message.replace(/[\r\n]/g, '');
var html = document.getElementById('chatgpt');
//清空
$('#content').val('');
// 将 textarea 的 scrollTop 设置为 0
$('#content').scrollTop(0);
$('#content').blur().focus();
var contentTextarea = document.getElementById('content');
contentTextarea.readOnly = true;
if(cleanedString)
{
var user_path = "{{users_img_path}}";
html.innerHTML += "<div style=\"margin-top: 5%\">\n" +
" <div>\n" +
"\n" +
" <div id=\"img\" style=\"align-items: flex-start;display: flex\">\n" +
" <img src=\""+user_path+"\" width=\"30px\" height=\"30px\">\n" +
" <p id=\"name\" style=\"color: white;font-weight: bolder;margin-left: 15px\">\n" +
" You\n" +
" </p>\n" +
" </div>\n" +
" <div style=\"color: white\">\n" +
message+
"\n" +
" </div>\n" +
" </div>\n" +
" </div>";
contentTextarea.readOnly = false;
// 处理成功响应
const baseURL = window.location.protocol + '//' + window.location.host;
console.log(message);
const eventSource = new EventSource(baseURL + '/sendMessageGpt/?message=' + message);
eventSource.addEventListener('message', function(event)
{
const data = event.data;
const dataJson = JSON.parse(data);
const flag = dataJson["choices"][0]["finish_reason"];
const gpt3 = dataJson["choices"][0]["delta"]["content"]
console.log(gpt3);
document.getElementById('dg1').append(gpt3);
// 使用匿名函数和setTimeout示例
var preElements = document.getElementsByTagName('pre');
// 遍历 <pre> 元素,并为它们添加类属性
for (var i = 0; i < preElements.length; i++)
{
preElements[i].classList.add('line-numbers');
}
Prism.highlightAll();
// 判断是否收到关闭事件,如果是,则关闭连接
if (flag)
{
eventSource.close();
console.log('Connection closed by the server');
}
// 处理收到的数据
});
eventSource.addEventListener('error', function(event)
{
eventSource.close();
console.error('Error occurred:', event);
// 处理连接错误,可能需要重新建立连接
});
eventSource.addEventListener('close', function(event)
{
console.log('Connection closed:', event);
eventSource.close();
// 处理连接关闭,可以选择重新建立连接或执行其他操作
});
}
}
});
</script>
效果如下所示可以从控制台看到字符是一个一个显示出来的,这样的响应速度比一次性全部返回的数据块多了,但是前端的逐字逐字显示的样式我不太会,前端我太垃圾了,折磨了我一天还是写不出来,看到的大佬可以给个建议。目前的困难是,每次提出一个对话框,gpt的回应可以在对话框中像打印机那样逐字逐字显示即可,同时支持代码高亮,代码高亮我的只会全部返回数据一起高亮,不会渐进式得高亮显示
点击此处登录后即可评论