岛屿可以找到海
岛屿可以找到海

PHP接入chatgpt

功能:流式返回、用户聊天记录区分、聊天记录保存(10分钟内不提问,自动删除聊天记录)、支持上下文提问(记得用户的提问以及chatgpt的回复内容)

核心代码:

<?php
namespace app\controller;
use GatewayWorker\Lib\Gateway;
use support\Redis;
use support\Request;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\ServerSentEvents;
use Webman\Push\Api;
class IndexController{



   public $api_url="";//chatgpt接口
   public $token="";//请求token

    public $data_buffer;//缓存,有可能一条data被切分成两部分了,无法解析json,所以需要把上一半缓存起来



    public function index(Request $request){
        $uid=$request->get("uid");
        $token=$request->get("token");

        if (!empty($uid)&&$token==getenv("chat_token")){
            if (Redis::hExists($uid,"chat")&&Redis::hExists($uid,"answer")){
                $chat_array=json_decode(Redis::hGet($uid,"chat"),true);//历史聊天记录
                $last_answer=Redis::hGet($uid,"answer");//最后一次chatgpt答复
                array_shift($chat_array);//删除第一次You are a helpful assistant.
            }else{
                $chat_array="";
                $last_answer="";
            }
            return view('index/index',["chat"=>$chat_array,"last_answer"=>$last_answer]);
        }else{
            return json("uid为空,访问时请携带uid参数例如:http://xxx.xxx.com/?uid=10000&token=xxx,若无token请联系作者");
        }



    }



    public function question(Request $request){
        $q=$request->post("q");
        $uid=$request->post("uid");


        $messages=[
            [
                'role' => 'system',
                'content' => 'You are a helpful assistant.',
            ]
        ];



        if (Redis::hExists($uid,"chat")){

            $talk_array=Redis::hGet($uid,"chat");
            $talk_array=json_decode($talk_array,true);
            if (Redis::hExists($uid,"answer")){
                $answer=Redis::hGet($uid,"answer");
                array_push($talk_array,["role"=>"assistant","content"=>$answer]);//先将上次的chagpt回答插入数组
            }
            array_push($talk_array,["role"=>"user","content"=>$q]);//将本次用户问题插入数组
            Redis::hSet($uid,"chat",json_encode($talk_array));//放入redis
            Redis::expire($uid,600);//设置10分钟过期时间,10分钟内不提问自动删除
            $messages=$talk_array;

        }else{

            array_push($messages,["role"=>"user","content"=>$q]);
            Redis::hSet($uid,"chat",json_encode($messages));
            Redis::expire($uid,600);//设置10分钟过期时间,10分钟内不提问自动删除
        }



        Redis::hDel($uid,"answer");//删除上一次的chatgpt回答内容
        $json = json_encode([
            'model' => 'gpt-3.5-turbo-0613',
            'messages' => $messages,
            'temperature' => 0.6,
            'stream' => true
        ]);

        $headers = array(
            "Content-Type:application/json",
            "Authorization: Bearer ".$this->token,
            "Connection:keep-alive",
            "Accept-Encoding:gzip, deflate, br",
            "Accept:*/*"
        );

        $this->curl_openai($json,$headers,$uid);



    }




    public function curl_openai($json, $headers,$uid) {
       
        $api = new Api(
            'http://127.0.0.1:3232',
            config('plugin.webman.push.app.app_key'),
            config('plugin.webman.push.app.app_secret')
        );

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->api_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
//      curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1');
//      curl_setopt($ch, CURLOPT_PROXYPORT, '7890');

        curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $data) use ($api,$uid) {


            // 0、把上次缓冲区内数据拼接上本次的data
            $buffer = $this->data_buffer.$data;


            //拼接完之后,要把缓冲字符串清空
            $this->data_buffer = '';

            // 1、把所有的 'data: {' 替换为 '{' ,'data: [' 换成 '['
            $buffer = str_replace('data: {', '{', $buffer);
            $buffer = str_replace('data: [', '[', $buffer);

            // 2、把所有的 '}\n\n{' 替换维 '}[br]{' , '}\n\n[' 替换为 '}[br]['
            $buffer = str_replace("}\n\n{", '}[br]{', $buffer);
            $buffer = str_replace("}\n\n[", '}[br][', $buffer);

            // 3、用 '[br]' 分割成多行数组
            $lines = explode('[br]', $buffer);

            for ($i = 0; $i < count($lines); $i++) {
                if (trim($lines[$i]) != '[DONE]') {
                    $array = json_decode($lines[$i], true);
                    try {
                        if ($array['choices'][0]['finish_reason'] != 'stop') {
                            //循环逐字存储redis
                            $char = $array['choices'][0]['delta']['content'];

                            if (Redis::hExists($uid,"answer")){
                                $question_char=Redis::hGet($uid,"answer");
                                Redis::hSet($uid,"answer",$question_char.$char);
                            }else{
                                Redis::hSet($uid,"answer",$char);
                            }

                            $api->trigger('user-1', 'message', [
                                'content'  => $array['choices'][0]['delta']['content']
                            ]);
                        }
                    }catch (\Exception $exception){
                       
                    }

                }
            }

            return strlen($data);
        });

        $response = curl_exec($ch);
        curl_close($ch);


    }


}

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="/plugin/webman/push/push.js"> </script>
    <style>
        #nr{
            overflow: scroll;
            overflow-x: hidden;
            width: 1000px;
            border: 1px solid black;
            height: 800px;
            padding-bottom: 20px;
        }
        .ai{
            width: 50%;
            margin-top: 20px;
            background: aliceblue;
            float: left;
            padding: 20px;
        }
        .ai img{
            width: 10%;
            height: 100%;
        }
        .q{
            width: 50%;
           float: right;
            margin-top: 20px;
            background: aliceblue;
            padding: 10px;
        }
    </style>
</head>
<body>


<div id="nr" >
    {if $chat}
    {volist name="chat" id="item"}
    {if $item.role=='user'}
    <div class="q">{$item.content}</div>
    {elseif $item.role=='assistant'}
    <div class="ai"><img src="/images/logo.png">{$item.content}</div>
    {/if}
    {/volist}
    {/if}

    {if $last_answer}
    <div class="ai"><img src="/images/logo.png"> {$last_answer}</div>
    {/if}


</div>
<input type="text" value="" id="text">
<button id="btn">发送</button>
<script>
    // 建立连接
    var connection = new Push({
        url: 'ws://127.0.0.1:3333', // websocket地址  本地就用127.0.0.1:3333  服务器请用服务器ip+端口
        app_key: '07f6e28d9f22f1ef52399979f304b88d',
        auth: '/plugin/webman/push/auth' // 订阅鉴权(仅限于私有频道)
    });
    // 假设用户uid为1
    var uid = 1;
    // 浏览器监听user-1频道的消息,也就是用户uid为1的用户消息
    var user_channel = connection.subscribe('user-' + uid);


    // 当user-1频道有message事件的消息时
    user_channel.on('message', function(data) {
        // data里是消息内容

        $(".ai:last").append(data.content)//给最后一个chatgpt回答的聊天框追加内容

    });

    $("#btn").click(function(){
        var text=$("#text").val();
        if (text!=""){
            $("#text").val("");
            $("#nr").append('<div class=\"q\">'+text+'</div>')
            $("#nr").append('<div class=\"ai\"> <img src="/images/logo.png" alt=""></div>')//提问完问题后,给chatgpt创建好回答元素
            $.ajax({
                url: "http://chat.dykyzdh.cn/q",
                method:"POST",
                data:{"q":text,"uid":10000}
            })
        }else {
            alert("请输入内容")
        }

    });


</script>
</body>
</html>

使用注意:请安装redis扩展及redis本身,redis配置在项目根目录下.env文件修改

岛屿可以找到海

PHP接入chatgpt
功能:流式返回、用户聊天记录区分、聊天记录保存(10分钟内不提问,自动删除聊天记录)、支持上下文提问(记得用户的提问以及chatgpt的回复内容) 核心代码: <?php namespace app\…
扫描二维码继续阅读
2023-11-08