TicketController.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controllers\Admin;
  4. use App\Controllers\BaseController;
  5. use App\Models\Ticket;
  6. use App\Models\User;
  7. use App\Services\LLM;
  8. use App\Utils\Tools;
  9. use Exception;
  10. use Psr\Http\Message\ResponseInterface;
  11. use Slim\Http\Response;
  12. use Slim\Http\ServerRequest;
  13. use function array_merge;
  14. use function count;
  15. use function json_decode;
  16. use function json_encode;
  17. use function time;
  18. final class TicketController extends BaseController
  19. {
  20. public static array $details =
  21. [
  22. 'field' => [
  23. 'op' => '操作',
  24. 'id' => '工单ID',
  25. 'title' => '主题',
  26. 'status' => '工单状态',
  27. 'type' => '工单类型',
  28. 'userid' => '提交用户',
  29. 'datetime' => '创建时间',
  30. ],
  31. ];
  32. /**
  33. * 后台工单页面
  34. *
  35. * @throws Exception
  36. */
  37. public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  38. {
  39. return $response->write(
  40. $this->view()
  41. ->assign('details', self::$details)
  42. ->fetch('admin/ticket/index.tpl')
  43. );
  44. }
  45. /**
  46. * 后台更新工单内容
  47. */
  48. public function update(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  49. {
  50. $id = $args['id'];
  51. $comment = $request->getParam('comment') ?? '';
  52. if ($comment === '') {
  53. return $response->withJson([
  54. 'ret' => 0,
  55. 'msg' => '工单回复不能为空',
  56. ]);
  57. }
  58. $ticket = Ticket::where('id', $id)->first();
  59. if ($ticket === null) {
  60. return $response->withJson([
  61. 'ret' => 0,
  62. 'msg' => '工单不存在',
  63. ]);
  64. }
  65. $content_old = json_decode($ticket->content, true);
  66. $content_new = [
  67. [
  68. 'comment_id' => $content_old[count($content_old) - 1]['comment_id'] + 1,
  69. 'commenter_name' => 'Admin',
  70. 'comment' => $comment,
  71. 'datetime' => time(),
  72. ],
  73. ];
  74. $user = User::find($ticket->userid);
  75. $user->sendMail(
  76. $_ENV['appName'] . '-工单被回复',
  77. 'warn.tpl',
  78. [
  79. 'text' => '你好,有人回复了<a href="' .
  80. $_ENV['baseUrl'] . '/user/ticket/' . $ticket->id . '/view">工单</a>,请你查看。',
  81. ],
  82. []
  83. );
  84. $ticket->content = json_encode(array_merge($content_old, $content_new));
  85. $ticket->status = 'open_wait_user';
  86. $ticket->save();
  87. return $response->withJson([
  88. 'ret' => 1,
  89. 'msg' => '提交成功',
  90. ]);
  91. }
  92. /**
  93. * 喊 LLM 帮忙回复工单
  94. */
  95. public function updateAI(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  96. {
  97. $id = $args['id'];
  98. $ticket = Ticket::where('id', $id)->first();
  99. if ($ticket === null) {
  100. return $response->withJson([
  101. 'ret' => 0,
  102. 'msg' => '工单不存在',
  103. ]);
  104. }
  105. $content_old = json_decode($ticket->content, true);
  106. // 获取用户的第一个问题,作为 LLM 的输入
  107. $ai_reply = LLM::genTextResponse($content_old[0]['comment']);
  108. $content_new = [
  109. [
  110. 'comment_id' => $content_old[count($content_old) - 1]['comment_id'] + 1,
  111. 'commenter_name' => 'AI Admin',
  112. 'comment' => $ai_reply,
  113. 'datetime' => time(),
  114. ],
  115. ];
  116. $user = User::find($ticket->userid);
  117. $user->sendMail(
  118. $_ENV['appName'] . '-工单被回复',
  119. 'warn.tpl',
  120. [
  121. 'text' => '你好,AI 回复了<a href="' .
  122. $_ENV['baseUrl'] . '/user/ticket/' . $ticket->id . '/view">工单</a>,请你查看。',
  123. ],
  124. []
  125. );
  126. $ticket->content = json_encode(array_merge($content_old, $content_new));
  127. $ticket->status = 'open_wait_user';
  128. $ticket->save();
  129. return $response->withJson([
  130. 'ret' => 1,
  131. 'msg' => '提交成功',
  132. ]);
  133. }
  134. /**
  135. * 后台查看指定工单
  136. *
  137. * @throws Exception
  138. */
  139. public function ticketView(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  140. {
  141. $id = $args['id'];
  142. $ticket = Ticket::where('id', '=', $id)->first();
  143. if ($ticket === null) {
  144. return $response->withRedirect('/admin/ticket');
  145. }
  146. $comments = json_decode($ticket->content);
  147. foreach ($comments as $comment) {
  148. $comment->datetime = Tools::toDateTime((int) $comment->datetime);
  149. }
  150. return $response->write(
  151. $this->view()
  152. ->assign('ticket', $ticket)
  153. ->assign('comments', $comments)
  154. ->fetch('admin/ticket/view.tpl')
  155. );
  156. }
  157. /**
  158. * 后台关闭工单
  159. */
  160. public function close(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  161. {
  162. $id = $args['id'];
  163. $ticket = Ticket::where('id', '=', $id)->first();
  164. if ($ticket === null) {
  165. return $response->withJson([
  166. 'ret' => 0,
  167. 'msg' => '工单不存在',
  168. ]);
  169. }
  170. if ($ticket->status === 'closed') {
  171. return $response->withJson([
  172. 'ret' => 0,
  173. 'msg' => '操作失败,工单已关闭',
  174. ]);
  175. }
  176. $ticket->status = 'closed';
  177. $ticket->save();
  178. return $response->withJson([
  179. 'ret' => 1,
  180. 'msg' => '关闭成功',
  181. ]);
  182. }
  183. /**
  184. * 后台删除工单
  185. */
  186. public function delete(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  187. {
  188. $id = $args['id'];
  189. Ticket::where('id', '=', $id)->delete();
  190. return $response->withJson([
  191. 'ret' => 1,
  192. 'msg' => '删除成功',
  193. ]);
  194. }
  195. /**
  196. * 后台工单页面 Ajax
  197. */
  198. public function ajax(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  199. {
  200. $tickets = Ticket::orderBy('id', 'desc')->get();
  201. foreach ($tickets as $ticket) {
  202. $ticket->op = '<button type="button" class="btn btn-red" id="delete-ticket"
  203. onclick="deleteTicket(' . $ticket->id . ')">删除</button>';
  204. if ($ticket->status !== 'closed') {
  205. $ticket->op .= '
  206. <button type="button" class="btn btn-orange" id="close-ticket"
  207. onclick="closeTicket(' . $ticket->id . ')">关闭</button>';
  208. }
  209. $ticket->op .= '
  210. <a class="btn btn-blue" href="/admin/ticket/' . $ticket->id . '/view">查看</a>';
  211. $ticket->status = $ticket->status();
  212. $ticket->type = $ticket->type();
  213. $ticket->datetime = Tools::toDateTime((int) $ticket->datetime);
  214. }
  215. return $response->withJson([
  216. 'tickets' => $tickets,
  217. ]);
  218. }
  219. }