Forráskód Böngészése

feat: chatgpt powered ticket reply

M1Screw 2 éve
szülő
commit
0b81a35e82

+ 1 - 0
app/routes.php

@@ -137,6 +137,7 @@ return static function (Slim\App $app): void {
         $group->get('/ticket/{id}/view', App\Controllers\Admin\TicketController::class . ':ticketView');
         $group->put('/ticket/{id}/close', App\Controllers\Admin\TicketController::class . ':close');
         $group->put('/ticket/{id}', App\Controllers\Admin\TicketController::class . ':update');
+        $group->put('/ticket/{id}/ai', App\Controllers\Admin\TicketController::class . ':updateAI');
         $group->delete('/ticket/{id}', App\Controllers\Admin\TicketController::class . ':delete');
         $group->post('/ticket/ajax', App\Controllers\Admin\TicketController::class . ':ajax');
         // Shop Mange

+ 1 - 0
composer.json

@@ -22,6 +22,7 @@
         "league/html-to-markdown": "^5.1",
         "league/omnipay": "^3.2.1",
         "mailgun/mailgun-php": "^3",
+        "openai-php/client": "^0.4.2",
         "ozdemir/datatables": "^2",
         "phpmailer/phpmailer": "^6",
         "postal/postal": "^1.0",

+ 92 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "9735ac4ca5eeae105f3dda31ee943500",
+    "content-hash": "5f32c0e5640b4b66d13d4dd4b4bfb773",
     "packages": [
         {
             "name": "anankke/omnipay-alipay",
@@ -2592,6 +2592,97 @@
             ],
             "time": "2021-12-30T11:32:00+00:00"
         },
+        {
+            "name": "openai-php/client",
+            "version": "v0.4.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/openai-php/client.git",
+                "reference": "bbfbc0a28872d679d6990712d7feaae2c9f96fc2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/openai-php/client/zipball/bbfbc0a28872d679d6990712d7feaae2c9f96fc2",
+                "reference": "bbfbc0a28872d679d6990712d7feaae2c9f96fc2",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.1.0",
+                "php-http/discovery": "^1.15.3",
+                "php-http/multipart-stream-builder": "^1.2.0",
+                "psr/http-client": "^1.0.2",
+                "psr/http-client-implementation": "^1.0.1",
+                "psr/http-factory-implementation": "*",
+                "psr/http-message": "^1.1.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^7.5.0",
+                "guzzlehttp/psr7": "^2.4.4",
+                "laravel/pint": "^1.8.0",
+                "nunomaduro/collision": "^7.4.0",
+                "pestphp/pest": "^2.4.0",
+                "pestphp/pest-plugin-arch": "^2.1.1",
+                "pestphp/pest-plugin-mock": "^2.0.0",
+                "phpstan/phpstan": "^1.10.11",
+                "rector/rector": "^0.14.8",
+                "symfony/var-dumper": "^6.2.8"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/OpenAI.php"
+                ],
+                "psr-4": {
+                    "OpenAI\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nuno Maduro",
+                    "email": "[email protected]"
+                },
+                {
+                    "name": "Sandro Gehri"
+                }
+            ],
+            "description": "OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API",
+            "keywords": [
+                "GPT-3",
+                "api",
+                "client",
+                "codex",
+                "dall-e",
+                "language",
+                "natural",
+                "openai",
+                "php",
+                "processing",
+                "sdk"
+            ],
+            "support": {
+                "issues": "https://github.com/openai-php/client/issues",
+                "source": "https://github.com/openai-php/client/tree/v0.4.2"
+            },
+            "funding": [
+                {
+                    "url": "https://www.paypal.com/paypalme/enunomaduro",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/gehrisandro",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nunomaduro",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-04-12T04:26:02+00:00"
+        },
         {
             "name": "ozdemir/datatables",
             "version": "2.3.6",

+ 3 - 0
config/.config.example.php

@@ -174,6 +174,9 @@ $_ENV['sentry_dsn'] = '';
 $_ENV['maxmind_license_key'] = '';
 $_ENV['geoip_locale'] = 'en';
 
+// OpenAI API Key for GPT-3.5 powered ticket reply and more
+$_ENV['openai_api_key'] = '';
+
 // ClientDownload 命令解决 API 访问频率高而被限制使用的 Github access token
 $_ENV['github_access_token'] = '';
 

+ 24 - 10
resources/views/tabler/admin/ticket/view.tpl

@@ -20,19 +20,16 @@
                             <i class="icon ti ti-x"></i>
                             关闭
                         </button>
-                        <button href="#" class="btn btn-red d-sm-none btn-icon" data-bs-toggle="modal"
-                            data-bs-target="#close_ticket_confirm_dialog">
-                            <i class="icon ti ti-x"></i>
-                        </button>
                         {/if}
                         <button href="#" class="btn btn-primary d-none d-sm-inline-block" data-bs-toggle="modal"
-                            data-bs-target="#add-reply">
-                            <i class="icon ti ti-plus"></i>
-                            回复
+                                data-bs-target="#add-ai-reply">
+                            <i class="icon ti ti-robot"></i>
+                            AI 回复
                         </button>
-                        <button href="#" class="btn btn-primary d-sm-none btn-icon" data-bs-toggle="modal"
+                        <button href="#" class="btn btn-primary d-none d-sm-inline-block" data-bs-toggle="modal"
                             data-bs-target="#add-reply">
                             <i class="icon ti ti-plus"></i>
+                            回复
                         </button>
                     </div>
                 </div>
@@ -133,7 +130,24 @@
                     comment: $('#reply-comment').val()
                 },
                 success: function(data) {
-                    if (data.ret == 1) {
+                    if (data.ret === 1) {
+                        $('#success-message').text(data.msg);
+                        $('#success-dialog').modal('show');
+                    } else {
+                        $('#fail-message').text(data.msg);
+                        $('#fail-dialog').modal('show');
+                    }
+                }
+            })
+        });
+
+        $("#add-ai-reply").click(function() {
+            $.ajax({
+                url: "/admin/ticket/{$ticket->id}/ai",
+                type: 'PUT',
+                dataType: "json",
+                success: function(data) {
+                    if (data.ret === 1) {
                         $('#success-message').text(data.msg);
                         $('#success-dialog').modal('show');
                     } else {
@@ -150,7 +164,7 @@
                 type: 'PUT',
                 dataType: "json",
                 success: function(data) {
-                    if (data.ret == 1) {
+                    if (data.ret === 1) {
                         $('#success-message').text(data.msg);
                         $('#success-dialog').modal('show');
                     } else {

+ 52 - 0
src/Controllers/Admin/TicketController.php

@@ -7,6 +7,7 @@ namespace App\Controllers\Admin;
 use App\Controllers\BaseController;
 use App\Models\Ticket;
 use App\Models\User;
+use App\Services\ChatGPT;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
@@ -101,6 +102,57 @@ final class TicketController extends BaseController
         ]);
     }
 
+    /**
+     * 喊 ChatGPT 帮忙回复工单
+     */
+    public function updateAI(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
+    {
+        $id = $args['id'];
+
+        $ticket = Ticket::where('id', $id)->first();
+
+        if ($ticket === null) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '工单不存在',
+            ]);
+        }
+
+        $content_old = json_decode($ticket->content, true);
+        // 获取用户的第一个问题,作为 ChatGPT 的输入
+        $user_question = $content_old[0]['comment'];
+        // 这里可能要等4-5秒
+        $ai_reply = ChatGPT::askOnce($user_question);
+
+        $content_new = [
+            [
+                'comment_id' => $content_old[count($content_old) - 1]['comment_id'] + 1,
+                'commenter_name' => 'AI Admin by ChatGPT',
+                'comment' => $ai_reply,
+                'datetime' => time(),
+            ],
+        ];
+
+        $user = User::find($ticket->userid);
+        $user->sendMail(
+            $_ENV['appName'] . '-工单被回复',
+            'news/warn.tpl',
+            [
+                'text' => '您好,ChatGPT 回复了<a href="' . $_ENV['baseUrl'] . '/user/ticket/' . $ticket->id . '/view">工单</a>,请您查看。',
+            ],
+            []
+        );
+
+        $ticket->content = json_encode(array_merge($content_old, $content_new));
+        $ticket->status = 'open_wait_user';
+        $ticket->save();
+
+        return $response->withJson([
+            'ret' => 1,
+            'msg' => '提交成功',
+        ]);
+    }
+
     /**
      * 后台查看指定工单
      *

+ 33 - 0
src/Services/ChatGPT.php

@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services;
+
+use OpenAI\Client;
+
+final class ChatGPT
+{
+    public static function askOnce(string $q): string
+    {
+        if ($_ENV['openai_api_key'] === '') {
+            return 'OpenAI API key not set';
+        }
+
+        if ($q === '') {
+            return 'No question provided';
+        }
+
+        $client = Client::class($_ENV['openai_api_key']);
+        $response = $client->chat()->create([
+            'model' => 'gpt-3.5-turbo',
+            'messages' => [
+                [
+                    'role' => 'user',
+                    'content' => $q,
+                ],
+            ],
+        ]);
+        return $response->choices[0]->message->content;
+    }
+}