Selaa lähdekoodia

feat: ticket llm reply context support

Cat 1 vuosi sitten
vanhempi
sitoutus
876acfa997

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
+*~
 .idea
 .htaccess
 */.DS_Store

+ 4 - 4
app/routes.php

@@ -55,7 +55,7 @@ return static function (Slim\App $app): void {
         $group->get('/ticket/create', App\Controllers\User\TicketController::class . ':create');
         $group->post('/ticket', App\Controllers\User\TicketController::class . ':add');
         $group->get('/ticket/{id:[0-9]+}/view', App\Controllers\User\TicketController::class . ':detail');
-        $group->put('/ticket/{id:[0-9]+}', App\Controllers\User\TicketController::class . ':update');
+        $group->post('/ticket/{id:[0-9]+}', App\Controllers\User\TicketController::class . ':reply');
         // 资料编辑
         $group->get('/edit', App\Controllers\User\InfoController::class . ':index');
         $group->post('/edit/email', App\Controllers\User\InfoController::class . ':updateEmail');
@@ -151,9 +151,9 @@ return static function (Slim\App $app): void {
         $group->get('/ticket', App\Controllers\Admin\TicketController::class . ':index');
         $group->post('/ticket', App\Controllers\Admin\TicketController::class . ':add');
         $group->get('/ticket/{id:[0-9]+}/view', App\Controllers\Admin\TicketController::class . ':detail');
-        $group->put('/ticket/{id:[0-9]+}/close', App\Controllers\Admin\TicketController::class . ':close');
-        $group->put('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':update');
-        $group->put('/ticket/{id:[0-9]+}/ai', App\Controllers\Admin\TicketController::class . ':updateAI');
+        $group->post('/ticket/{id:[0-9]+}/close', App\Controllers\Admin\TicketController::class . ':close');
+        $group->post('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':reply');
+        $group->post('/ticket/{id:[0-9]+}/llm_reply', App\Controllers\Admin\TicketController::class . ':llmReply');
         $group->delete('/ticket/{id:[0-9]+}', App\Controllers\Admin\TicketController::class . ':delete');
         $group->post('/ticket/ajax', App\Controllers\Admin\TicketController::class . ':ajax');
         // Ann

+ 30 - 78
resources/views/tabler/admin/ticket/view.tpl

@@ -15,15 +15,16 @@
                 <div class="col-auto">
                     <div class="btn-list">
                         {if $ticket->status !== 'closed'}
-                            <button href="#" class="btn btn-red" data-bs-toggle="modal"
-                                    data-bs-target="#close_ticket_confirm_dialog">
-                                <i class="icon ti ti-x"></i>
-                                关闭
-                            </button>
+                        <button href="#" class="btn btn-red" data-bs-toggle="modal"
+                                data-bs-target="#close_ticket_confirm_dialog">
+                            <i class="icon ti ti-x"></i>
+                            关闭
+                        </button>
                         {/if}
-                        <button id="add_ai_reply" href="#" class="btn btn-primary">
+                        <button href="#" class="btn btn-primary" hx-post="/admin/ticket/{$ticket->id}/llm_reply"
+                                hx-swap="none">
                             <i class="icon ti ti-robot"></i>
-                            AI 回复
+                            LLM 回复
                         </button>
                         <button href="#" class="btn btn-primary" data-bs-toggle="modal"
                                 data-bs-target="#add-reply">
@@ -52,23 +53,23 @@
                         <div class="card-body">
                             <div class="divide-y">
                                 {foreach $comments as $comment}
-                                    <div>
-                                        <div class="row">
-                                            <div class="col">
-                                                <div>
-                                                    {nl2br($comment->comment)}
-                                                </div>
-                                                <div class="text-secondary my-1">{$comment->commenter_name}
-                                                    回复于 {$comment->datetime}
-                                                </div>
+                                <div>
+                                    <div class="row">
+                                        <div class="col">
+                                            <div>
+                                                {$comment->comment}
+                                            </div>
+                                            <div class="text-secondary my-1">{$comment->commenter_name}
+                                                回复于 {$comment->datetime}
                                             </div>
-                                            <div class="col-auto">
-                                                <div>
-                                                    # {$comment->comment_id + 1}
-                                                </div>
+                                        </div>
+                                        <div class="col-auto">
+                                            <div>
+                                                # {$comment->comment_id + 1}
                                             </div>
                                         </div>
                                     </div>
+                                </div>
                                 {/foreach}
                             </div>
                         </div>
@@ -93,7 +94,13 @@
                 </div>
                 <div class="modal-footer">
                     <button type="button" class="btn me-auto" data-bs-dismiss="modal">取消</button>
-                    <button id="reply" type="button" class="btn btn-primary" data-bs-dismiss="modal">回复</button>
+                    <button class="btn btn-primary" data-bs-dismiss="modal"
+                        hx-post="/admin/ticket/{$ticket->id}" hx-swap="none"
+                        hx-vals='js:{
+                            comment: document.getElementById("reply-comment").value,
+                        }'>
+                        回复
+                    </button>
                 </div>
             </div>
         </div>
@@ -104,7 +111,8 @@
             <div class="modal-content">
                 <div class="modal-header">
                     <h5 class="modal-title">关闭工单</h5>
-                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                    <button class="btn-close" data-bs-dismiss="modal" aria-label="Close"
+                            hx-post="/admin/ticket/{$ticket->id}/close" hx-swap="none"></button>
                 </div>
                 <div class="modal-body">
                     <div class="mb-3">
@@ -122,60 +130,4 @@
         </div>
     </div>
 
-    <script>
-        $("#reply").click(function () {
-            $.ajax({
-                url: "/admin/ticket/{$ticket->id}",
-                type: 'PUT',
-                dataType: "json",
-                data: {
-                    comment: $('#reply-comment').val()
-                },
-                success: function (data) {
-                    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 {
-                        $('#fail-message').text(data.msg);
-                        $('#fail-dialog').modal('show');
-                    }
-                }
-            })
-        });
-
-        $("#confirm_close").click(function () {
-            $.ajax({
-                url: "/admin/ticket/{$ticket->id}/close",
-                type: 'PUT',
-                dataType: "json",
-                success: function (data) {
-                    if (data.ret === 1) {
-                        $('#success-message').text(data.msg);
-                        $('#success-dialog').modal('show');
-                    } else {
-                        $('#fail-message').text(data.msg);
-                        $('#fail-dialog').modal('show');
-                    }
-                }
-            })
-        });
-    </script>
-
 {include file='admin/footer.tpl'}

+ 1 - 1
resources/views/tabler/user/invoice/index.tpl

@@ -25,7 +25,7 @@
                                 <thead>
                                 <tr>
                                     {foreach $details['field'] as $key => $value}
-                                        <th>{$value}</th>
+                                    <th>{$value}</th>
                                     {/foreach}
                                 </tr>
                                 </thead>

+ 1 - 1
resources/views/tabler/user/order/index.tpl

@@ -25,7 +25,7 @@
                                 <thead>
                                 <tr>
                                     {foreach $details['field'] as $key => $value}
-                                        <th>{$value}</th>
+                                    <th>{$value}</th>
                                     {/foreach}
                                 </tr>
                                 </thead>

+ 33 - 35
resources/views/tabler/user/ticket/index.tpl

@@ -14,8 +14,7 @@
                 </div>
                 <div class="col-auto">
                     <div class="btn-list">
-                        <button href="#" class="btn btn-primary" data-bs-toggle="modal"
-                                data-bs-target="#create-ticket">
+                        <button href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#create-ticket">
                             <i class="icon ti ti-plus"></i>
                             创建工单
                         </button>
@@ -31,45 +30,45 @@
                     <div class="row row-cards row-deck">
                         {if $tickets !== 0}
                             {foreach $tickets as $ticket}
-                                <div class="col-md-4 col-sm-12">
-                                    <div class="card">
-                                        <div class="card-body">
-                                            <div class="card-stamp">
-                                                {if $ticket->status !== 'closed'}
-                                                    <div class="card-stamp-icon bg-yellow">
-                                                        <i class="ti ti-clock"></i>
-                                                    </div>
-                                                {else}
-                                                    <div class="card-stamp-icon bg-green">
-                                                        <i class="ti ti-check"></i>
-                                                    </div>
-                                                {/if}
+                            <div class="col-md-4 col-sm-12">
+                                <div class="card">
+                                    <div class="card-body">
+                                        <div class="card-stamp">
+                                            {if $ticket->status !== 'closed'}
+                                            <div class="card-stamp-icon bg-yellow">
+                                                <i class="ti ti-clock"></i>
                                             </div>
-                                            <h3 class="card-title" style="font-size: 20px;">
-                                                #{$ticket->id}
-                                            </h3>
-                                            <p class="text-secondary text-truncate" style="height: 100px;">
-                                                {$ticket->title}
-                                            </p>
-                                        </div>
-                                        <div class="card-footer">
-                                            <div class="d-flex">
-                                                <span class="status status-grey">{$ticket->status}</span>
-                                                <span class="status status-grey">{$ticket->type}</span>
-                                                <a href="/user/ticket/{$ticket->id}/view"
-                                                   class="btn btn-primary ms-auto">查看</a>
+                                            {else}
+                                            <div class="card-stamp-icon bg-green">
+                                                <i class="ti ti-check"></i>
                                             </div>
+                                            {/if}
+                                        </div>
+                                        <h3 class="card-title" style="font-size: 20px;">
+                                            #{$ticket->id}
+                                        </h3>
+                                        <p class="text-secondary text-truncate" style="height: 100px;">
+                                            {$ticket->title}
+                                        </p>
+                                    </div>
+                                    <div class="card-footer">
+                                        <div class="d-flex">
+                                            <span class="status status-grey">{$ticket->status}</span>
+                                            <span class="status status-grey">{$ticket->type}</span>
+                                            <a href="/user/ticket/{$ticket->id}/view"
+                                               class="btn btn-primary ms-auto">查看</a>
                                         </div>
                                     </div>
                                 </div>
+                            </div>
                             {/foreach}
                         {else}
-                            <div class="card">
-                                <div class="card-header">
-                                    <h3 class="card-title">没有任何工单</h3>
-                                </div>
-                                <div class="card-body">如需帮助,请点击右上角按钮开启新工单</div>
+                        <div class="card">
+                            <div class="card-header">
+                                <h3 class="card-title">没有任何工单</h3>
                             </div>
+                            <div class="card-body">如需帮助,请点击右上角按钮开启新工单</div>
+                        </div>
                         {/if}
                     </div>
                 </div>
@@ -98,8 +97,7 @@
                         <input id="ticket-title" type="text" class="form-control" placeholder="请输入工单主题">
                     </div>
                     <div class="mb-3">
-                        <textarea id="ticket-comment" class="form-control" rows="12"
-                                  placeholder="请输入工单内容"></textarea>
+                        <textarea id="ticket-comment" class="form-control" rows="12" placeholder="请输入工单内容"></textarea>
                     </div>
                 </div>
                 <div class="modal-footer">

+ 21 - 21
resources/views/tabler/user/ticket/view.tpl

@@ -13,15 +13,15 @@
                     </div>
                 </div>
                 {if $ticket->status !== 'closed'}
-                    <div class="col-auto">
-                        <div class="btn-list">
-                            <a href="#" class="btn btn-primary" data-bs-toggle="modal"
-                               data-bs-target="#add-reply">
-                                <i class="icon ti ti-plus"></i>
-                                添加回复
-                            </a>
-                        </div>
+                <div class="col-auto">
+                    <div class="btn-list">
+                        <a href="#" class="btn btn-primary" data-bs-toggle="modal"
+                           data-bs-target="#add-reply">
+                            <i class="icon ti ti-plus"></i>
+                            添加回复
+                        </a>
                     </div>
+                </div>
                 {/if}
             </div>
         </div>
@@ -77,22 +77,22 @@
                         <div class="card-body">
                             <div class="divide-y">
                                 {foreach $comments as $comment}
-                                    <div>
-                                        <div class="row">
-                                            <div class="col">
-                                                <div>
-                                                    {nl2br($comment->comment)}
-                                                </div>
-                                                <div class="text-secondary my-1">{$comment->commenter_name}
-                                                    回复于 {$comment->datetime}</div>
+                                <div>
+                                    <div class="row">
+                                        <div class="col">
+                                            <div>
+                                                {$comment->comment}
                                             </div>
-                                            <div class="col-auto">
-                                                <div>
-                                                    # {$comment->comment_id + 1}
-                                                </div>
+                                            <div class="text-secondary my-1">{$comment->commenter_name}
+                                                回复于 {$comment->datetime}</div>
+                                        </div>
+                                        <div class="col-auto">
+                                            <div>
+                                                # {$comment->comment_id + 1}
                                             </div>
                                         </div>
                                     </div>
+                                </div>
                                 {/foreach}
                             </div>
                         </div>
@@ -117,7 +117,7 @@
                 <div class="modal-footer">
                     <button type="button" class="btn me-auto" data-bs-dismiss="modal">取消</button>
                     <button id="reply" class="btn btn-primary" data-bs-dismiss="modal"
-                            hx-put="/user/ticket/{$ticket->id}" hx-swap="none"
+                            hx-post="/user/ticket/{$ticket->id}" hx-swap="none"
                             hx-vals='js:{ comment: document.getElementById("reply-comment").value }'>
                         回复
                     </button>

+ 70 - 74
src/Controllers/Admin/TicketController.php

@@ -9,18 +9,20 @@ use App\Models\Ticket;
 use App\Models\User;
 use App\Services\LLM;
 use App\Services\Notification;
+use App\Utils\ResponseHelper;
 use App\Utils\Tools;
-use Exception;
 use GuzzleHttp\Exception\GuzzleException;
 use Psr\Http\Client\ClientExceptionInterface;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
+use Smarty\Exception;
 use Telegram\Bot\Exceptions\TelegramSDKException;
 use function array_merge;
 use function count;
 use function json_decode;
 use function json_encode;
+use function nl2br;
 use function time;
 
 final class TicketController extends BaseController
@@ -38,11 +40,7 @@ final class TicketController extends BaseController
             ],
         ];
 
-    private static string $err_msg = '请求失败';
-
     /**
-     * 后台工单页面
-     *
      * @throws Exception
      */
     public function index(ServerRequest $request, Response $response, array $args): ResponseInterface
@@ -54,106 +52,115 @@ final class TicketController extends BaseController
         );
     }
 
-    /**
-     * @throws TelegramSDKException
-     * @throws GuzzleException
-     * @throws ClientExceptionInterface
-     */
-    public function update(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function reply(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $id = $args['id'];
         $comment = $request->getParam('comment') ?? '';
 
         if ($comment === '') {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            return ResponseHelper::error($response, '请输入评论内容');
         }
 
         $ticket = (new Ticket())->where('id', $id)->first();
 
         if ($ticket === null) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            return ResponseHelper::error($response, '工单不存在');
         }
 
         $content_old = json_decode($ticket->content, true);
         $content_new = [
             [
                 'comment_id' => $content_old[count($content_old) - 1]['comment_id'] + 1,
+                'commenter_type' => 'admin',
                 'commenter_name' => 'Admin',
                 'comment' => $comment,
                 'datetime' => time(),
             ],
         ];
 
-        $user = (new User())->find($ticket->userid);
-
-        Notification::notifyUser(
-            $user,
-            $_ENV['appName'] . '-工单被回复',
-            '你好,有人回复了<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' => '提交成功',
-        ]);
+        try {
+            Notification::notifyUser(
+                (new User())->find($ticket->userid),
+                $_ENV['appName'] . '-工单被回复',
+                '你好,有人回复了<a href="' . $_ENV['baseUrl'] . '/user/ticket/' . $ticket->id . '/view">工单</a>,请你查看。'
+            );
+        } catch (TelegramSDKException|GuzzleException|ClientExceptionInterface $e) {
+            return $response->withHeader('HX-Refresh', 'true');
+        }
+
+        return $response->withHeader('HX-Refresh', 'true');
     }
 
-    /**
-     * @throws GuzzleException
-     * @throws TelegramSDKException
-     * @throws ClientExceptionInterface
-     */
-    public function updateAI(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function llmReply(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $id = $args['id'];
-
         $ticket = (new Ticket())->where('id', $id)->first();
 
         if ($ticket === null) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            return ResponseHelper::error($response, '工单不存在');
         }
 
         $content_old = json_decode($ticket->content, true);
-        // 获取用户的第一个问题,作为 LLM 的输入
-        $ai_reply = LLM::genTextResponse($content_old[0]['comment']);
+
+        if (count($content_old) === 1) {
+            $context = [
+                [
+                    'role' => 'user',
+                    'content' => $ticket->title,
+                ],
+                [
+                    'role' => 'user',
+                    'content' => $content_old[0]['comment'],
+                ]
+            ];
+        } else {
+            $context = [
+                [
+                    'role' => 'user',
+                    'content' => $ticket->title,
+                ],
+            ];
+
+            foreach ($content_old as $comment) {
+                $context[] = [
+                    'role' => $comment['commenter_type'] ?? $comment['commenter_name'] === 'Admin' ? 'admin' : 'user',
+                    'content' => $comment['comment'],
+                ];
+            }
+
+        }
+
+        $llm_response = LLM::genTextResponseWithContext($context);
+
         $content_new = [
             [
                 'comment_id' => $content_old[count($content_old) - 1]['comment_id'] + 1,
-                'commenter_name' => 'AI Admin',
-                'comment' => $ai_reply,
+                'commenter_type' => 'llm',
+                'commenter_name' => 'AI Assistant',
+                'comment' => $llm_response,
                 'datetime' => time(),
             ],
         ];
 
-        $user = (new User())->find($ticket->userid);
-
-        Notification::notifyUser(
-            $user,
-            $_ENV['appName'] . '-工单被回复',
-            '你好,AI 回复了<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' => '提交成功',
-        ]);
+        try {
+            Notification::notifyUser(
+                (new User())->find($ticket->userid),
+                $_ENV['appName'] . '-工单被回复',
+                '你好,AI助理回复了<a href="' . $_ENV['baseUrl'] . '/user/ticket/' . $ticket->id . '/view">工单</a>,请你查看。'
+            );
+        } catch (TelegramSDKException|GuzzleException|ClientExceptionInterface $e) {
+            return $response->withHeader('HX-Refresh', 'true');
+        }
+
+        return $response->withHeader('HX-Refresh', 'true');
     }
 
     /**
@@ -173,6 +180,7 @@ final class TicketController extends BaseController
         $comments = json_decode($ticket->content);
 
         foreach ($comments as $comment) {
+            $comment->comment = nl2br($comment->comment);
             $comment->datetime = Tools::toDateTime((int) $comment->datetime);
         }
 
@@ -193,26 +201,17 @@ final class TicketController extends BaseController
         $ticket = (new Ticket())->where('id', '=', $id)->first();
 
         if ($ticket === null) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            return ResponseHelper::error($response, '工单不存在');
         }
 
         if ($ticket->status === 'closed') {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            return ResponseHelper::error($response, '工单已关闭,无需重复操作');
         }
 
         $ticket->status = 'closed';
         $ticket->save();
 
-        return $response->withJson([
-            'ret' => 1,
-            'msg' => '关闭成功',
-        ]);
+        return ResponseHelper::success($response, '工单关闭成功');
     }
 
     /**
@@ -223,10 +222,7 @@ final class TicketController extends BaseController
         $id = $args['id'];
         (new Ticket())->where('id', '=', $id)->delete();
 
-        return $response->withJson([
-            'ret' => 1,
-            'msg' => '删除成功',
-        ]);
+        return ResponseHelper::success($response, '工单删除成功');
     }
 
     /**

+ 12 - 24
src/Controllers/User/TicketController.php

@@ -9,24 +9,24 @@ use App\Models\Config;
 use App\Models\Ticket;
 use App\Services\Notification;
 use App\Services\RateLimit;
+use App\Utils\ResponseHelper;
 use App\Utils\Tools;
-use Exception;
 use GuzzleHttp\Exception\GuzzleException;
 use Psr\Http\Client\ClientExceptionInterface;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
+use Smarty\Exception;
 use Telegram\Bot\Exceptions\TelegramSDKException;
 use function array_merge;
 use function count;
 use function json_decode;
 use function json_encode;
+use function nl2br;
 use function time;
 
 final class TicketController extends BaseController
 {
-    private static string $err_msg = '请求失败';
-
     /**
      * @throws Exception
      */
@@ -69,15 +69,13 @@ final class TicketController extends BaseController
             $comment === '' ||
             $type === ''
         ) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            return ResponseHelper::error($response, '工单创建失败');
         }
 
         $content = [
             [
                 'comment_id' => 0,
+                'commenter_type' => 'user',
                 'commenter_name' => $this->user->user_name,
                 'comment' => $this->antiXss->xss_clean($comment),
                 'datetime' => time(),
@@ -100,10 +98,7 @@ final class TicketController extends BaseController
             );
         }
 
-        return $response->withJson([
-            'ret' => 1,
-            'msg' => '提交成功',
-        ]);
+        return $response->withHeader('HX-Redirect', '/user/ticket/' . $ticket->id . '/view');
     }
 
     /**
@@ -111,7 +106,7 @@ final class TicketController extends BaseController
      * @throws TelegramSDKException
      * @throws ClientExceptionInterface
      */
-    public function update(ServerRequest $request, Response $response, array $args): ResponseInterface
+    public function reply(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
         $id = $args['id'];
         $comment = $request->getParam('comment') ?? '';
@@ -120,25 +115,20 @@ final class TicketController extends BaseController
             $this->user->is_shadow_banned ||
             $comment === ''
         ) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            ResponseHelper::error($response, '工单回复失败');
         }
 
         $ticket = (new Ticket())->where('id', $id)->where('userid', $this->user->id)->first();
 
         if ($ticket === null) {
-            return $response->withJson([
-                'ret' => 0,
-                'msg' => self::$err_msg,
-            ]);
+            ResponseHelper::error($response, '工单不存在');
         }
 
         $content_old = json_decode($ticket->content, true);
         $content_new = [
             [
                 'comment_id' => $content_old[count($content_old) - 1]['comment_id'] + 1,
+                'commenter_type' => 'user',
                 'commenter_name' => $this->user->user_name,
                 'comment' => $this->antiXss->xss_clean($comment),
                 'datetime' => time(),
@@ -158,10 +148,7 @@ final class TicketController extends BaseController
             );
         }
 
-        return $response->withJson([
-            'ret' => 1,
-            'msg' => '提交成功',
-        ]);
+        return $response->withHeader('HX-Refresh', 'true');
     }
 
     /**
@@ -183,6 +170,7 @@ final class TicketController extends BaseController
         $comments = json_decode($ticket->content);
 
         foreach ($comments as $comment) {
+            $comment->comment = nl2br($comment->comment);
             $comment->datetime = Tools::toDateTime((int) $comment->datetime);
         }