Django实现chatgpt的SSE数据流响应

2023年12月23日 16:27 ry 387

随着最近的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的回应可以在对话框中像打印机那样逐字逐字显示即可,同时支持代码高亮,代码高亮我的只会全部返回数据一起高亮,不会渐进式得高亮显示

如果上述代码帮助您很多,可以打赏下以减少服务器的开支吗,万分感谢!

欢迎发表评论~

点击此处登录后即可评论


评论列表
暂时还没有任何评论哦...

赣ICP备2021001574号-1

赣公网安备 36092402000079号